c# - 断言在库初始化期间未被 except block 捕获

标签 c# .net delphi pinvoke delphi-xe2

更新

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

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

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

两台主机也都在断言处抛出异常。异常到位的 except block 捕获。

问题无法使用 Delphi XE3 重现(感谢@David 帮助解决这个问题),所以目前的状态是这与 Delphi XE2 中的(有问题的)异常/断言处理有关,尤其是在 64 位上平台。


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

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

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

德尔福动态链接库

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 block 保护,专门用于防止异常从 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 block 。但是,当从 C# 主机调用 Delphi DLL 时,断言触发,未到达 except block (未记录消息,而是未初始化的记录器记录未处理的异常断言),并且 C# 进程终止并显示“vshost.exe 已停止工作”对话框。

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

最佳答案

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

如果您看一下所涉及的例程(在 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                  }

此评论只是讨论与 ASSERT() 机制相关的众多假设之一。

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

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

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

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

关于c# - 断言在库初始化期间未被 except block 捕获,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19815426/

相关文章:

c# - 布局周期检测到两个 progressRings 错误

Delphi:滑动(动画)面板

delphi - Firemonkey自定义组件

c# - 如何获取枚举的基础值

C# - 在一些计算之后从另一个构造函数调用构造函数

c# - Array.Copy 在 C# 中是如何实现的?

c# - 用于查找属性差异的 Lambda,反射在 C# 中未正确返回

c# - 将一种语言的语音转换为另一种语言

.net - 如何在.NET中创建可继承的信号量?

Delphi 5 IDE 命令行返回代码