6

更新

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

请注意,所有这些都与 XE2 64 位构建相关联。我们已经注意到断言杀死了 Delphi 64 位调试器,而它们不使用 32 位平台。

替换AssertErrorProc并在那里做一些日志记录可以纠正这种情况,无论是对于作为我们的 C# 和 Delphi 主机的 Delphi 调试器。

两个主机也可以在断言位置抛出异常。异常被现有的异常块捕获。

使用 Delphi XE3 无法重现该问题(感谢@David 提供帮助),因此当前状态是这与 Delphi XE2 中的(错误)异常/断言处理有关,尤其是在 64 位平台上。


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

DLL 可以并且已经在 Delphi 和 C# 主机上成功使用。

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

德尔福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.

自定义 dll 主过程只是确保某些结构在第一次加载 DLL 时初始化,并在最后一个“加载器”离开时完成。

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

        Inc(_RefCount);
      end;
  // ...
end;

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;

德尔福主机

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;

调用:结果 := 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); 

问题

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

是什么导致这种情况发生,我该如何预防?

4

1 回答 1

2

Assertions are a particular flavour of exception that require some additional scaffolding and support from the compiler.

If you take a look at the routines involved (in the SysUtils unit) there are a lot of assumptions described such as for example:

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

This comment is just one of many discussing the assumptions involved with the ASSERT() mechanism.

Whether it is this aspect of the assertions implementation in the Delphi compiler that is involved or not, it seems likely to me that these assumptions are not valid when running in a C# host process. If it is these assumptions that are behind the problem, then raising an exception in the "normal" way may avoid the problem without forcing you to change anything other than the way that you raise the exception itself.

Try replacing your ASSERT(FALSE, 'Whatever') with a simple call to raise an EAssertionFailed exception directly (i.e. not involving the compiler scaffolding and assumptions that ASSERT() invokes).

You can still make the code subject to conditional compilation to achieve the same effect as using the Enable Assertions compiler option (compiler option "C"):

{$ifopt C+} // only if compiling with assertions enabled
  raise EAssertionFailed.Create('Whatever');
{$endif}
于 2013-11-06T23:53:29.237 回答