当运行用C编写的应用程序,它使用在Delphi XE7中编写的一些dll时,我在以下代码中遇到访问冲突,该代码位于vcl库的vcl.forms.pas中。 / p>
procedure TCustomForm.CMAppSysCommand(var Message: TMessage);
{$IF NOT DEFINED(CLR)}
type
PWMSysCommand = ^TWMSysCommand;
{$ENDIF}
begin
Message.Result := 0;
if (csDesigning in ComponentState) or (FormStyle = fsMDIChild) or
(Menu = nil) or Menu.AutoMerge then
{$IF DEFINED(CLR)}
with TWMSysCommand.Create(Message) do
{$ELSE}
with PWMSysCommand(Message.lParam)^ do
{$ENDIF}
begin
SendCancelMode(nil);
if SendAppMessage(CM_APPSYSCOMMAND, CmdType, Key) <> 0 then //Here the debugger shows the access violation
Message.Result := 1;
end;
end;
访问冲突发生在使用SendAppMessage的行上,并且似乎是由Message.LParam为0引起的。该消息是WM_SYSCOMMAND消息。有没有办法跟踪此消息的来源?在调用堆栈中,所有函数都是VCL或系统文件的一部分。
This answer建议跟踪Windows消息的发件人通常很难。但是,因为在我的情况下,所有内容都在同一个应用程序中,我希望这可能会使它变得更容易。
以前,这个相同的bug出现在forms.pas中,并通过将该文件的副本添加到项目中然后检查该LParam&lt;&gt;来修复。此功能为0。 我尝试使用现在使用的vcl.forms.pas执行相同的操作,但这会导致编译错误。即使答案为here,我也无法构建它。然而,许多谷歌点击率也表明改变vcl中的内容通常是一个坏主意,所以我尽量避免这种选择。
This article给了我关于底层系统的好信息以及Message.LParam为0可能发生的情况。但是,我不知道如何找到消息的来源或我应该是什么类寻找那就产生了它。
正如Remy在下面接受的答案所述,通过让类提供CMAppSysCommand函数来防止LParam = 0,可以解决当前的问题。
答案 0 :(得分:4)
在正常情况下,您所描述的内容是不可能的。
整个VCL中只有两个位置发送CM_APPSYSCOMMAND
:
TWinControl.WMSysCommand()
,在UI控件收到WM_SYSCOMMAND
消息时调用。 LParam
消息的CM_APPSYSCOMMAND
永远不会设置为0,它会设置为指向原始TMessage
消息的WM_SYSCOMMAND
记录的指针:
Form := GetParentForm(Self);
if (Form <> nil) and
(Form.Perform(CM_APPSYSCOMMAND, 0, Winapi.Windows.LPARAM(@Message)) <> 0) then
Exit;
TCustomForm.CMAppSysCommand()
,在表单收到CM_APPSYSCOMMAND
消息时调用。它会将消息转发到TApplication
窗口(使用SendAppMessage()
,只需使用提供的参数调用SendMessage(Application.Handle, ...)
):
with PWMSysCommand(Message.lParam)^ do
begin
...
if SendAppMessage(CM_APPSYSCOMMAND, CmdType, Key) <> 0 then
Message.Result := 1;
end;
你提到的other question解释了VCL如何使用CM_APPSYSCOMMAND
,但没有说明LParam
在TCustomForm.CMAppSysCommand()
中TApplication.WndProc()
如何为0的任何内容,因为在正常情况下它不能为0。它可以在CM_APPSYSCOMMAND
中为0,但这完全没问题。
我能想到的唯一可能性是,如果有人手动将假CM_BASE + 23 = $B017
条消息(WM_APP + $3017
,即TForm
)直接发送到您的TWinControl
窗口。只有TWinControl
应该这样做。由于Perform()
使用SendMessage()
代替TWinControl.WMSysCommand()
进行该发送,因此您应该在TCustomForm.CMAppSysCommand()
的调用堆栈上看到SendMessage()
。如果你不这样做,那么这个消息是假的。如果使用Perform()
而不是TForm
发送,则无法知道邮件的来源。
然而,在任何情况下,这都很容易防范,而不会改变任何VCL源代码。只需让您的DLL的CM_APPSYSCOMMAND
类为message
提供自己的消息处理程序,可以使用WndProc()
指令,也可以覆盖虚拟LParam
方法。无论哪种方式,如果type
TMyForm = class(TForm)
...
private
procedure CMAppSysCommand(var Message: TMessage); message CM_APPSYSCOMMAND;
...
end;
procedure TMyForm.CMAppSysCommand(var Message: TMessage);
begin
if Message.LParam = 0 then
Message.Result := 0
else
inherited;
end;
为0,您可以丢弃该消息,例如:
type
TMyForm = class(TForm)
...
protected
procedure WndProc(var Message: TMessage); override;
...
end;
procedure TMyForm.WndProc(var Message: TMessage);
begin
if (Message.Msg = CM_APPSYSCOMMAND) and (Message.LParam = 0) then
Message.Result := 0
else
inherited;
end;
col1 col2 col3
12 34 35
56 34 35
13 56 35
56 34 35
12 56 34