断言在库初始化期间没有被阻塞除掉

Mar*_*ema 6 .net c# delphi pinvoke delphi-xe2

更新

进一步调查表明,由于一些不正确的配置文件,断言没有使用Delphi主机触发.一旦解决了这个问题,Delphi主机就像C#主机一样死机.

请注意,所有这些都与XE2 64位版本相关联.我们已经注意到断言杀死了Delphi 64位调试器,而他们没有使用32位平台.

替换AssertErrorProc和只是做一些日志记录可以解决这种情况,对于Delphi调试器来说都是我们的C#和Delphi主机.

两个主机都可以在断言位置抛出异常.唯一的例外由抓住除了到位块.

使用Delphi XE3无法重现这个问题(感谢@David帮助解决这个问题),因此目前的状态是这与Delphi XE2中的(错误)异常/断言处理有关,特别是在64位平台上.


我有一个Delphi DLL,它可以从一个自托管的C#web服务中调用.出于调试目的,也可以从Delphi可执行文件中调用此DLL.

DLL可以并且已经成功地从Delphi和C#主机中使用.

今天我遇到了在初始化DLL时执行的代码中触发断言的情况,并发现在Delphi进程托管时成功阻止断言离开DLL,但是没有被捕获并导致主机死掉是一个C#进程.

Delphi DLL

Delphi DLL有自己的DllProc过程,可以从dpr手动调用,因为Delphi RTL"劫持"入口点以允许单元初始化.有关详细信息,请参见http://docwiki.embarcadero.com/VCL/XE/en/System.DLLProc.

Delphi DLL dpr代码:

begin
  DllProc := MyDllMain;
  MyDllMain(DLL_PROCESS_ATTACH);
end.
Run Code Online (Sandbox Code Playgroud)

自定义dll主程序只是确保在首次加载DLL时初始化某些结构,并在最后一个"加载器"离开时完成.

procedure MyDllMain(Reason: Integer);
begin
  // ...
  //  DLL_PROCESS_ATTACH:
      begin
        if _RefCount < 1 then
          InitializeDLL;

        Inc(_RefCount);
      end;
  // ...
end;
Run Code Online (Sandbox Code Playgroud)

InitializeDLL过程受try except块保护,专门用于防止异常转义DLL.

procedure InitializeDLL;
begin
  try
    // Some code triggering an Assert(False, 'Whatever');
  except
    on E: Exception do
      TLogger.LogException('InitializeDLL');
    // Don't raise through. Exceptions should not leave DLL.
  end;
end;
Run Code Online (Sandbox Code Playgroud)

Delphi主机

Delphi主机手动调用Delphi DLL的LoadLibrary,检索指向它所需函数的指针并使用它们调用DLL.

procedure InternalSetup;
begin
  FLibrary := LoadLibrary(CLibraryPath);

  GetResource := GetProcAddress(FLibrary, 'GetResource');
  PostResource := GetProcAddress(FLibrary, 'PostResource');
  PutResource := GetProcAddress(FLibrary, 'PutResource');
  DeleteResource := GetProcAddress(FLibrary, 'DeleteResource');
end;
Run Code Online (Sandbox Code Playgroud)

调用:结果:= GetResource(INVALID_URI,{aQueryParams =}'',{out} ResponseBody);

C#主机

C#主机包含以下代码来调用DLL

    [DllImport("EAConfigurationEngine.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
    // Delphi:
    // function GetResource(aURI: string; aQueryParams: string; out aResponseBody: WideString): THTTPStatusCode; stdcall; export;
    private static extern int GetResource(
        string uri,
        string queryParams,
        [MarshalAs(UnmanagedType.BStr)] out string responseBody); 
Run Code Online (Sandbox Code Playgroud)

如上所述,当DLL的主机是Delphi可执行文件时,会触发except块.但是,当从C#主机调用Delphi DLL时,断言触发,未到达 except块(未记录消息,而未初始化的记录器记录有关断言的未处理异常),以及C#进程在"vshost.exe已停止工作"对话框中死亡.

造成这种情况的原因是什么?如何防止这种情况发生?

Del*_*ics 2

断言是一种特殊的异常,需要编译器的一些额外的支架和支持。

如果您查看所涉及的例程(在 SysUtils 单元中),则会发现许多假设,例如:

{ This code is based on the following assumptions:                         }
{  - Our direct caller (AssertErrorHandler) has an EBP frame               }
{  - ErrorStack points to where the return address would be if the         }
{    user program had called System.@RaiseExcept directly                  }
Run Code Online (Sandbox Code Playgroud)

此评论只是讨论ASSERT()机制涉及的假设的众多评论之一。

无论是否涉及 Delphi 编译器中断言实现的这个方面,在我看来,这些假设在 C# 主机进程中运行时可能是无效的。如果问题背后正是这些假设,那么以“正常”方式引发异常可能会避免问题,而无需强制您更改除引发异常本身的方式之外的任何内容。

尝试用简单的调用替换ASSERT(FALSE, 'Whatever')以直接引发EAssertionFailed异常(即不涉及ASSERT()调用的编译器脚手架和假设)。

您仍然可以使代码接受条件编译,以达到与使用Enable Assertions编译器选项(编译器选项“C”)相同的效果:

{$ifopt C+} // only if compiling with assertions enabled
  raise EAssertionFailed.Create('Whatever');
{$endif}
Run Code Online (Sandbox Code Playgroud)