从C++ .dll到德尔福表单应用程序的PostMessage问题

Problems with PostMessage from C++ .dll to Delphi Forms application

本文关键字:应用程序 PostMessage 问题 表单 德尔福 C++ dll      更新时间:2023-10-16

我有在Delphi 7上编写的Windows Forms应用程序,C++ .dll使用MFC编写的。

目前,我正在尝试实现从.dll到主可执行文件的基本消息发布,以在进度条上显示用户计算过程,但遇到了几个问题。

让我先描述一下我的方法。我在德尔福应用程序中注册了简单的消息,例如:

WM_MSG := RegisterWindowMessage('WM_MSG');

并在库部分执行相同的操作:

UINT nMsgID = RegisterWindowMessage(_T("WM_MSG"));

这没关系:调试时我可以在两端看到相同的值。

我的库函数看起来像这样(只是一个测试进度条的虚拟示例):

extern "C" __declspec(dllexport) int MyFunction() {  
  UINT nMsgID = RegisterWindowMessage(_T("WM_MSG"));
  HWND hWnd = FindWindow(NULL, "Form1");
  if (hWnd > 0)
    for (int i = 0; i < 100000; i++) {
      int param = ceil(100 * (double) i / (double) 100000);
      PostMessage(hWnd, nMsgID, param, NULL);
    }
  return 1;
}

可执行消息事件:

procedure TForm1.OnMessageEvent(var Msg: tagMSG; var Handled: Boolean);
begin
  Handled := True;
  if Msg.message = WM_MSG then
    ProgressBar1.Position := Msg.wParam
  else Handled := False;
end;

从可执行文件调用C++函数:

procedure TMyFunctionDLL.Execute;
var
  i: Integer;
  tHWND: HWND;
begin
  tHWND := FindWindow(nil, 'mainF');
  i := Func;
end;

第一个问题是 tHWNDhWnd 变量值莫名其妙地不同。经过一些研究,我发现了3种情况:1.负或正巨大hWnd2. 零 hWnd3. 未定义("???")

在所有情况下,变量 hWnd 都被标记为未使用,我不知道这是什么意思。最有趣的是,如果我以非常简单的 Delphi 形式(只有一个单元)测试它,代码确实有效。这个简单的 Delphi 表单与计算真实数据的真实C++ .dll代码配合得很好。但是当我使用我的一般 Delphi 应用程序(许多单元但仍然是一个表单)时,似乎主应用程序 OnMessage 事件不会从 dll C++捕获任何事件。

所以,有两个问题:1. 为什么hWnd值总是不同的,为什么它们是"未使用"的?2. 如何强制我的主应用程序与进度条一起正常工作?

我一直在使用不同的方法来解决这个问题。例如将 Application.HandleForm1.Handle 作为函数参数传递给C++库。他们都没有工作,甚至没有说在传递时更改了参数值(我想这应该是单独的问题)。我也尝试使用::FindWindow()和::P ostMessage()而不是FindWindow()和PostMessage()(它们之间有什么区别?),这也无济于事。我已经在尝试改善一整天的定位,但不知道如何解决它。请帮我提出任何想法。

除了其他人所说的之外,更好的设计是让 EXE 将其HWND直接传递到 DLL 中,这样 DLL 就不必去寻找它。 这还有一个额外的好处,即 EXE 可以决定 DLL 应将其消息发布到哪个HWND。 我会使用AllocateHWnd()为此创建一个专用窗口。

试试这个:

UINT nMsgID = RegisterWindowMessage(_T("WM_MSG")); 
extern "C" __declspec(dllexport) int __stdcall MyFunction(HWND hWnd) {   
    if ((nMsgID != 0) && (hWnd != NULL)) {
        for (int i = 0; i < 100000; i++) { 
            int param = ceil(100 * (double) i / (double) 100000); 
            PostMessage(hWnd, nMsgID, param, 0); 
        } 
    }
    return 1; 
} 

.

unit Unit1;
interface
...
var
  DllWnd: HWND = 0;
implementation
var
  WM_MSG: UINT = 0;
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  DllWnd := AllocateHWnd(DllWndProc);
end; 
procedure TForm1.FormDestroy(Sender: TObject); 
begin 
  if DllWnd <> 0 then
  begin
    DeallocateHWnd(DllWnd);
    DllWnd := 0;
  end;
end; 
procedure TForm1.DllWndProc(var Message: TMessage); 
begin 
  if (Message.Msg = WM_MSG) and (WM_MSG <> 0) then 
    ProgressBar1.Position := Message.WParam
  else
    Message.Result := DefWindowProc(DllWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 
...
initialization
  WM_MSG := RegisterWindowMessage('WM_MSG');     
end.

.

uses
  Unit1;
function DllFunc(Wnd: HWND): Integer; stdcall; external 'My.dll' name 'MyFunction'; 
procedure TMyFunctionDLL.Execute;   
var   
  i: Integer;   
begin   
  i := DllFunc(DllWnd);   
end;   

FindWindow的结果可以是零,也可以是非零。句柄值不在数字行上。它们只是不同的值,因此对它们应用不等式运算符是没有意义的。换句话说,句柄值可能显示为负数,因此不要假设有效句柄将始终大于零。

如果窗口句柄值不匹配,那么难怪其他方法都不起作用。您无法调试消息的功能,因为您甚至不确定是否将它们发送到正确的窗口。首先专注于解决这个问题。

仅将FindWindow用作最后的手段。它不提供检测何时有多个窗口符合您的搜索条件的功能。它总是只返回一个结果。如果可能,请完全避免搜索。相反,请准确告诉发件人要将消息发送到哪个窗口。你说你尝试过这个,但失败了,但我敦促你再追求这条路。您遇到的问题可能是不匹配的调用约定。确保 DLL 和主机应用程序都使用 stdcall。


确定将消息

发送到正确的窗口后,您可以担心为什么消息无法正确操作进度条。我至少可以看到两个原因:

  1. 当 DLL 函数运行时,称为 DLL 的主程序不是。它正在等待 DLL 代码返回。这意味着您的主程序不处理任何消息。DLL 正在发布一堆消息,但它们尚未得到处理。在程序返回到其消息循环之前,它们不会得到处理。

  2. Windows 消息队列的默认大小为 10,000。您向队列发布的消息数量是队列的 10 倍,并且在停止之前没有处理任何消息,因此即使队列在开始之前完全为空(这不太可能,因为您可能从键盘或鼠标输入触发此功能),您也只能收到十分之一的消息。当队列已满时,PostMessage只是丢弃该消息。由于您随消息一起发送的值是 0 到 100 之间的整数,因此当其中只有 101 个包含有意义的信息时,发送 100,000 个是毫无意义的。

如果对 FindWindow 的相同调用返回不同的窗口,则必须具有多个名为 Form1 的窗口。尝试为这些不同的窗口指定不同的名称,以便可以唯一地标识它们。

未使用的问题有点不清楚。也许你的意思是编译器已经注意到分配给tHWND的值从未被使用过,因此毫无意义。

我想最后评论一下,这个问题不准确,这可能是你问题的一部分。例如,您说所有变量都未使用,但我们不清楚您的意思。如果您更精确和有条不紊,您将在调试中取得更大的成功。

好吧,看来我已经解决了问题...我尝试了 Remy 声明导出函数的建议

function MyFunction (fHWND: HWND): Integer; cdecl; external 'My.dll'

按照大卫的建议,与 cdecl 召集惯例。我以前的函数声明如下所示

TMyFunction = function (fHWND: HWND): Integer;

我想这就是问题所在。谢谢大家的帮助!

附言现在,我该如何关闭问题?