Reputation: 43
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
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
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
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:
Msg
parameter is unsigned, but the Delphi translation is signed.hWnd_p
parameter is a pointer to HWND
. In the Delphi you pass the window handle by value.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