为什么导入 Mixed Native/CLR lib/dll 的本机 C++ 应用程序不在 Mixed lib/dll 中的外部变量上调用 ctor/dtor

Why native C++ app that imports Mixed Native/CLR lib/dll is not calling ctor/dtor on extern vars in Mixed lib/dll

编写(在文本中更进一步:记录器)综合 logger/diagnostics/performance profiler/debugger 具有本机堆栈的功能 walker/managed stack walker 功能以及无数其他功能,并且库必须在不同模块之间共享生活在本机或托管堆上(用 asm/native C++/托管 C++/.Net/... 编写)。 到目前为止,当此记录器和 importer/implementer/caller 应用程序均以本机代码或托管代码编译时,一切正常。但是,如果我使用 /CLR 将记录器库编译为托管,并在本机 C++ 项目 W/O /CLR 中使用它,则从具有 'extern' 的记录器库导出的 classes 不会在内部调用构造函数或析构函数记录器 dll,在 dll 初始化期间。实际上 Constructors/Destructors 从未在记录器库中被调用(甚至还注意到扩展的 classes 构造函数未被调用),只有 class 的空 shell 存在半初始化。

更清楚地了解记录器库:本机部分实现了记录器库的全部功能。虽然托管部分实际上是本机代码的 "simple" 包装器,但出于可移植性和维护原因,我需要在同一个 dll 中完全实现它。 Logger 库旨在取代已有 10 年历史的类似库,该库仅具有此新记录器库功能的 20%。

现在这不是我第一次遇到这个或类似的问题,在过去的解决方案中要么拆分为纯本机代码和纯托管代码,一个为另一个包装(维护两个项目,便携性 none)。或者编译两个版本的库,一个用于本机应用程序,另一个用于托管应用程序。现在在这种情况下,这些不是解决方案,而是限制,因为我需要让代码通过相同的进程管道工作,无论它是本机 dll、本机应用程序、托管 dll、托管应用程序......,为了简单起见,我需要将所有功能合而为一清酒。

我也可以重写 extern classes 不使用 constructor/destructor 并写一些相同的弹性模拟,但是在浪费了一整天之后我想知道这个问题背后的原因,是否还有其他更优雅的解决方案,或者如果我在某个地方犯了错误:即。使用 #pragma managed(push, off) 是否会产生此症状或类似症状?

有人知道这背后的原因吗?

有两个魔鬼让代码表现不佳。

第一个魔鬼:警告消息是第一个线索,即在首次执行托管代码之前,导出的初始化程序将不会 运行(猜测在编译托管代码时,加载 DLL 时不会初始化任何内容)。 测试混合代码加载顺序时的警告示例:

1>CBla.cpp(8): warning C4835: '_bla1_' : the initializer for exported data will not be run until managed code is first executed in the host assembly.

我忽略了这一点,因为一切都很顺利。

二鬼:一些标准的c/c++代码无法被编译成托管的。虽然我的印象是这不应该被编译为托管。但是我开始在使用可变参数的函数中收到警告消息,并开始在本机代码编译指示周围到处放置以将本机代码编译为本机!!!

#pragma managed(push, off)
// native code
#pragma managed(pop)

现在编译正常了,但在这种情况下,所有 "native" classes 实际上只是作为本机存在,从未放置过对托管代码的调用 - 从未创建托管 VFTABLE。托管函数在加载之前具有不同的 VFTABLE,每个函数都被重写以在执行实际托管函数之前加载 CLR。 您只能使用 IDA 反汇编器或类似的实用程序来发现它……但在下一页的底部也有简要说明:https://msdn.microsoft.com/en-us/library/ms173266.aspx?f=255&MSPPError=-2147217396 "Initialization of Mixed Assemblies"。 没有调用外部原生 classes 的初始化程序,静态存储到 LIB/DLL 的内容被保存在内存中(空 shell)。 因此,调用混合代码 LIB/DLL 的纯本机应用程序永远不会 uses/triggers CLR 代码永远不会在本机编译的 classes.

中调用 Constructor/Destructor

解决方案: 克服这一限制的唯一方法是在所有这些中放置一些托管函数以强制加载 CLR,在这种情况下,您会在调试过程中注意到一个异常:

First-chance exception at 0x7620c41f in ManagedNativeNatTest.exe: 0x04242420: CLRDBG_NOTIFICATION_EXCEPTION_CODE.

这是 CLR 代码获取初始化通知并开始为 LIB/DLL 加载导出的本机 classes 的实际时刻。我能够通过将属于本机 class 的空函数放入托管代码来触发它:

...
#pragma managed(push, off)
...
#pragma managed(pop)

void CBla1::ManagedCall()
{ }

#pragma managed(push, off)
...

并且调用此函数导致在 return 初始化我的外部变量时执行 CLR 加载程序。

我不确定为什么这样做,可能是因为 CLR 在使用之前不会加载任何东西。想知道如果我使用 NGen 将 CLR 编译为本机代码,情况是否相同,但这可能是另一次冒险。

这完成了我的答案,为什么外部变量在混合 LIB/DLL.

中没有收到对 Constructor/Destructor 的调用