使用消息处理程序将c ++ dll调用转换为delphi

时间:2013-10-09 08:13:27

标签: c++ delphi dll delphi-xe3

我需要在delphi XE3应用程序中使用DLL,我收到了一个用c ++编写的演示应用程序,它显示了如何调用DLL。
我已成功调用DLL和DLL的Initialize方法,但我没有从DLL中收到任何消息。
这是c ++的来源:
创建消息处理程序:

#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 },

Initialize方法的定义:

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

HMODULE         hPosDll;

加载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);
}

调用Initialize方法:

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

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

在Initialize调用之后,来自DLL到应用程序的消息由OnPos方法处理:

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

在德尔福,我到目前为止:

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.

使用此解决方案,我可以激活连接到com3的设备,但永远不会触发消息处理程序 我认为在c ++中使用Initialize方法发送的&m_hWnd与我在Delphi中发送的Self.Handle不同。
谁能帮我吗?提前谢谢。

3 个答案:

答案 0 :(得分:3)

C ++函数声明为:

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

您的Delphi等效项被声明为:

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

我可以在Delphi声明中看到以下不同之处:

  1. Msg参数未签名,但Delphi转换已签名。
  2. hWnd_p参数是指向HWND的指针。在Delphi中,您按值传递窗口句柄。
  3. C ++代码的调用约定是cdecl,但您将Delphi代码声明为stdcall
  4. 所以在Delphi中声明应该是:

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

    其中PHWND是声明为^HWND的类型。我怀疑这样的类型是在Windows单元中声明的,但如果没有,那么你可以很容易地声明它。

    或者更简单的翻译可能是:

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

    当然,在转录C ++代码时省略了调用约定是有道理的。也许这个函数确实是stdcall,即使你在问题中提出的声明没有这么说。

    除此之外,您的代码正在使用表单的窗口句柄。这可能有风险。在某些情况下,VCL重新创建窗口。这将导致您传递给DLL的句柄被销毁。 DLL会继续向该窗口发送消息,但是由于窗口已经改变,你的表单会停止接收它们。

    解决这个问题的方法是使用VCL不会重新创建的窗口句柄。通过致电AllocateHwnd获取其中一个。

    致电Initialize时要小心。您传递PAnsiChar('com3')作为端口名称。您需要确保文字'com3'确实是ANSI编码的。理想情况下,您应将'com3'直接传递给Initialize。如果这不能编译(如果不管,我不确定是否在我的头顶)然后通过PAnsiChar(AnsiString('com3'))

答案 1 :(得分:1)

乍一看,它看起来像是你的宣言

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

不符合

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

请注意,C声明需要指针指向持有句柄的变量。

尝试

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

看看你能走多远。

答案 2 :(得分:0)

在Hanno和David的帮助下,我改变了我的代码,现在正在运作。

我所做的改变:

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

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

遵循David Hefferman的建议我会在最终的应用程序中创建一个单独的组件,这样我就可以使用AllocateHwnd来创建一个固定的句柄。       ...