使用Delphi VCL表单中的LParam 0确定WM_SYSCOMMAND的发送方 问题

使用Delphi VCL表单中的LParam 0确定WM_SYSCOMMAND的发送方 问题,delphi,access-violation,vcl,Delphi,Access Violation,Vcl,当运行一个用C编写的应用程序(使用Delphi XE7编写的dll)时,我在下面的代码中遇到了访问冲突,该代码位于vcl库的vcl.forms.pas中 procedure TCustomForm.CMAppSysCommand(var Message: TMessage); {$IF NOT DEFINED(CLR)} type PWMSysCommand = ^TWMSysCommand; {$ENDIF} begin Message.Result := 0; if (csDes

当运行一个用C编写的应用程序(使用Delphi XE7编写的dll)时,我在下面的代码中遇到了访问冲突,该代码位于vcl库的vcl.forms.pas中

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;
过程TCustomForm.CMAppSysCommand(var消息:TMessage);
{$如果未定义(CLR)}
类型
PWMSysCommand=^TWMSysCommand;
{$ENDIF}
开始
消息。结果:=0;
如果(csDesigning in ComponentState)或(FormStyle=fsMDIChild)或
(Menu=nil)或Menu.AutoMerge然后
{$IF-DEFINED(CLR)}
使用TWMSysCommand.Create(Message)do
{$ELSE}
使用PWMSysCommand(Message.lParam)^do
{$ENDIF}
开始
发送取消模式(无);
如果SendAppMessage(CM_APPSYSCOMMAND,CmdType,Key)0,则//调试器在此处显示访问冲突
消息。结果:=1;
终止
终止
访问冲突发生在带有SendAppMessage的行上,似乎是由Message.LParam为0这一事实引起的。该消息是WM_SYSCOMMAND消息。是否有方法跟踪此消息的来源?在调用堆栈中,所有函数都是VCL或系统文件的一部分

建议通常很难跟踪windows邮件的发件人。但是,因为在我的情况下,所有内容都在同一个应用程序中,我希望这可能会使它变得更容易

我试过什么? 否决vcl源 以前,forms.pas中也出现了相同的错误,通过向项目中添加该文件的副本,然后在此函数中检查LParam 0,可以修复该错误。 我尝试过对现在使用的vcl.forms.pas执行相同的操作,但这会导致编译错误。即使有答案,因为我无法构建它。然而,许多谷歌点击也表明,改变vcl中的内容通常是一个坏主意,所以我尽量避免这种选择

关于StackOverFlow的其他问题 向我提供了有关底层系统的详细信息,以及Message.LParam为0时可能发生的情况。但是,我不知道如何找到消息的来源,也不知道生成消息的类应该是什么

解决方案
如下面Remy接受的答案所述,通过让类提供CMAppSysCommand函数来防止LParam=0,可以解决眼前的问题

您所描述的内容在正常情况下不可能实现

整个VCL中只有两个位置发送
CM\u APPSYSCOMMAND

  • TWinControl.WMSysCommand()
    ,当UI控件收到
    WM\u SYSCOMMAND
    消息时调用该命令。
    CM_APPSYSCOMMAND
    消息的
    LParam
    从未设置为0,它被设置为指向原始
    WM_SYSCOMMAND
    消息的
    TMessage
    记录的指针:

    Form := GetParentForm(Self);
    if (Form <> nil) and
      (Form.Perform(CM_APPSYSCOMMAND, 0, Winapi.Windows.LPARAM(@Message)) <> 0) then
      Exit;
    
  • 您提到的解释了VCL如何使用
    CM\u APPSYSCOMMAND
    ,但没有说明它的
    LParam
    TCustomForm.CMAppSysCommand()
    中如何可能为0,因为在正常情况下它不可能为0。在
    TApplication.WndProc()
    中它可以是0,但这完全可以

    我能想到的唯一可能性是,如果有人手动将一条假的
    CM_APPSYSCOMMAND
    消息(即
    CM_BASE+23=$B017
    ,又称
    WM_APP+$3017
    )直接发送到您的
    TForm
    窗口。只有TWinControl才应该这样做。由于
    TWinControl
    使用
    Perform()
    而不是
    SendMessage()
    进行发送,您应该在
    TCustomForm.CMAppSysCommand()
    的调用堆栈上看到
    TWinControl.WMSysCommand()
    。如果你没有,那么信息是假的。如果它是使用
    SendMessage()
    而不是
    Perform()
    发送的,则无法知道消息来自何处

    然而,在任何情况下,这都是很容易防范的,不需要修改任何VCL源代码。只需使用
    message
    指令或重写virtual
    WndProc()
    方法,让DLL的
    TForm
    类为
    CM\u APPSYSCOMMAND
    提供自己的消息处理程序。无论哪种方式,如果
    LParam
    为0,您都可以丢弃该消息,例如:

    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;
    


    你所描述的在正常情况下是不可能的

    整个VCL中只有两个位置发送
    CM\u APPSYSCOMMAND

  • TWinControl.WMSysCommand()
    ,当UI控件收到
    WM\u SYSCOMMAND
    消息时调用该命令。
    CM_APPSYSCOMMAND
    消息的
    LParam
    从未设置为0,它被设置为指向原始
    WM_SYSCOMMAND
    消息的
    TMessage
    记录的指针:

    Form := GetParentForm(Self);
    if (Form <> nil) and
      (Form.Perform(CM_APPSYSCOMMAND, 0, Winapi.Windows.LPARAM(@Message)) <> 0) then
      Exit;
    
  • 您提到的解释了VCL如何使用
    CM\u APPSYSCOMMAND
    ,但没有说明它的
    LParam
    TCustomForm.CMAppSysCommand()
    中如何可能为0,因为在正常情况下它不可能为0。在
    TApplication.WndProc()
    中它可以是0,但这完全可以

    我能想到的唯一可能性是,如果有人手动将一条假的
    CM_APPSYSCOMMAND
    消息(即
    CM_BASE+23=$B017
    ,又称
    WM_APP+$3017
    )直接发送到您的
    TForm
    窗口。只有TWinControl才应该这样做。由于
    TWinControl
    使用
    Perform()
    而不是
    SendMessage()
    进行发送,您应该在
    TCustomForm.CMAppSysCommand()
    的调用堆栈上看到
    TWinControl.WMSysCommand()
    。如果你没有,那么信息是假的。如果它是使用
    SendMessage()
    而不是
    Perform()
    发送的,则无法知道消息来自何处

    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;