【问题标题】:Convert c++ dll call to delphi with message handler使用消息处理程序将 c++ dll 调用转换为 delphi
【发布时间】:2013-10-16 11:37:41
【问题描述】:

我需要在我的 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);
}

调用初始化方法:

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;
}

在 Delphi 中我已经走了这么远:

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 方法发送的 &amp;m_hWnd 与我在 Delphi 中发送的 Self.Handle 不同。
谁能帮我吗?提前致谢。

【问题讨论】:

    标签: c++ delphi dll delphi-xe3


    【解决方案1】:

    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

    所以在 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'))

    【讨论】:

      【解决方案2】:

      乍一看,它看起来像你的声明

      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;
      

      看看你能走多远。

      【讨论】:

      • @Hanno 您应该修复代码以便编译。您不能在参数列表中写入Handle: ^HWND
      【解决方案3】:

      在 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 创建一个固定句柄。 ...

      【讨论】:

      • 我们真的不需要这个答案。你只是在重复我在回答中所说的话。鉴于您接受了另一个答案,这似乎很奇怪。
      猜你喜欢
      • 2015-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多