MrSigge
MrSigge

Reputation: 43

Convert c++ dll call to delphi with message handler

I need to use a DLL in my delphi XE3 application, I've received a demo application written in c++ that shows how to call the DLL.
I've succeed in calling the DLL and the Initialize method of the dll, but I don't get any messages back from the DLL.
This is the c++ source:
Creating the message handler:

#define WM_POSSTATE  WM_APP+1
#define ON_WM_POSSTATE() \
{ WM_POSSTATE, 0, 0, 0, AfxSig_vwl, \
    (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT, LPARAM))&OnPOS },

Definition of the Initialize method:

typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );
INITIALIZE *Initialize;

HMODULE         hPosDll;

Loading the DLL:

if( (hPosDll = LoadLibrary( "posdll.dll" ) ) == NULL )
{
    MessageBox( "Error: can not open posdll.dll",NULL,MB_OK);
    exit(1);
}

if( (::Initialize = (INITIALIZE*)GetProcAddress( hPosDll, "Initialize" )) == NULL )
{
    MessageBox( "Error: can not find function",NULL,MB_OK);
    FreeLibrary( hPosDll );
    exit(1);
}

Calling the Initialize method:

res=::Initialize('com3', WM_POSSTATE , &m_hWnd);

if( res )
{
    MessageBox( "Error opening comms",NULL,MB_OK);
    FreeLibrary( hPosDll );
    exit(1);
}

After the Initialize call, messages from the DLL to the application are processed by the OnPos methos:

void CPosDemoDlg::OnPOS(UINT Result, LPARAM Param )
{
    DoStuff;
}

In Delphi I've come so far:

unit UFrmMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, MCPOSDLL;

const
  WM_POSSTATE = WM_APP + 1;
  MCDLL = 'posdll.dll';

type
  TInitialize = function(cport: PAnsiChar; Msg: Integer; Handle: HWND):Integer; stdcall;

  TFrmMain = class(TForm)
    EdCOMPort: TEdit;
    BtnConnect: TButton;
    LblCOMPort: TLabel;
    procedure ON_WM_POSSTATE(var Msg: TMessage); message WM_POSSTATE;
    procedure BtnConnectClick(Sender: TObject);
  private
    DLLHandle: THandle;
  public
    { Public declarations }
  end;

var
  FrmMain: TFrmMain;

implementation

{$R *.dfm}

{-------------------------------------------------------------------------------}
 procedure TFrmMain.BtnConnectClick(Sender: TObject);
 var
   MCInitialize: TInitialize;
   Res: Integer;
 begin
   DLLHandle := LoadLibrary(PChar(MCDLL));

   if DLLHandle <> 0 then
     begin
       @MCInitialize := getProcAddress(DLLHandle, 'Initialize');

       if @MCInitialize <> NIL then
         begin
           Res := MCInitialize(PAnsiChar('com3'), WM_POSSTATE, Self.Handle);

           if Res <> 0 then
             begin
               MessageDlg('Error opening comms', mtWarning, [mbOK], 0);
             end;
         end;
     end
   else
     begin
       MessageDlg('posdll.dll could not be located.', mtWarning, [mbOK], 0);
     end;
 end;

{-------------------------------------------------------------------------------}
 procedure TFrmMain.ON_WM_POSSTATE(var Msg: TMessage);
 begin
   showmessage('Message received');
 end;

end.

With this solution I'm able to activate the device connected to com3 but the messagehandler is never triggered.
I think the &m_hWnd that is send with the Initialize method in c++ is not the same as the Self.Handle I'm sending in Delphi.
Can anyone help me out? Thanks in advance.

Upvotes: 1

Views: 874

Answers (3)

MrSigge
MrSigge

Reputation: 43

With the help of Hanno and David, I changed my code and it's working now.

Changes I've made:

type
  PHWND = ^HWnd;    
  TInitialize = function(cport: PAnsiChar; Msg: Cardinal; Handle: PHWND):Integer; stdcall;

and

var 
  TmpHandle
begin
  ...
  TmpHandle := Self.Handle;
  Res := MCInitialize(PAnsiChar(AnsiString('com3')), WM_POSSTATE, @TmpHandle);

Following the advice of David Hefferman I'l create a separate component in the final application, this way I'll use AllocateHwnd to create a fixed handle. ...

Upvotes: 0

JimmyB
JimmyB

Reputation: 12640

At a glance, it looks like your declaration

TInitialize = function(cport: PAnsiChar; Msg: Integer; Handle: HWND):Integer;

does not match

typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );

Note that the C declaration expects a pointer to the variable holding the handle.

Try

TInitialize = function(cport: PAnsiChar; Msg: Integer; var Handle: HWND):Integer;

and see how far you get.

Upvotes: 1

David Heffernan
David Heffernan

Reputation: 613592

The C++ function is declared as:

typedef int (INITIALIZE)( char * cPort , UINT Msg, HWND *hWnd_p );

Your Delphi equivalent is declared as:

TInitialize = function(cport: PAnsiChar; Msg: Integer; 
  Handle: HWND): Integer; stdcall;

I can see the following differences in your Delphi declaration:

  1. The Msg parameter is unsigned, but the Delphi translation is signed.
  2. The hWnd_p parameter is a pointer to HWND. In the Delphi you pass the window handle by value.
  3. The calling convention of the C++ code is cdecl, but you declared the Delphi code as stdcall.

So in Delphi the declaration should be:

TInitialize = function(cport: PAnsiChar; Msg: Cardinal; 
  Handle: PHWND): Integer; cdecl;

where PHWND is a type declared to be ^HWND. I suspect that such a type is declared in the Windows unit but if not then you can readily declare it.

Or perhaps a simpler translation would be:

TInitialize = function(cport: PAnsiChar; Msg: Cardinal; 
  var Handle: HWND): Integer; cdecl;

Of course, it's plausible that you omitted the calling convention when you transcribed the C++ code. Perhaps the function really is stdcall even though the declaration you placed in the question did not say so.

Beyond that your code is using the window handle of the form. This can be risky. In some situations the VCL re-creates windows. This would lead to the handle that you passed to the DLL being destroyed. The DLL would carry on sending messages to that window but your form would stop receiving them because its window had changed.

The way around this is to use a window handle that the VCL will not re-create. Get one of those by calling AllocateHwnd.

Be careful when you call Initialize. You pass PAnsiChar('com3') as the port name. You need to be sure that the literal 'com3' really is ANSI encoded. Ideally you should pass 'com3' directly to Initialize. If that doesn't compile (I'm not sure off the top of my head if it will or not) then pass PAnsiChar(AnsiString('com3')).

Upvotes: 3

Related Questions