为什么导入Mixed native/CLR lib.dll的本机C++应用程序没有在Mixed lib.dll中的外部变

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

本文关键字:dll lib Mixed 外部 应用程序 C++ CLR 导入 为什么 本机 native      更新时间:2023-10-16

编写(在文本中进一步说明:logger)全面的记录器/诊断/性能探查器/调试器功能,包括Native stack walker/托管stack walker功能以及无数其他功能,并且库必须在Native或托管堆上的不同模块之间共享(用asm/Native C++/托管C++/.Net/…编写)。到目前为止,当这个记录器和导入程序/实现程序/调用程序都用本机代码或托管代码编译时,一切都很好。但是,如果我将记录器库编译为使用/CLR托管,并在本机C++项目W/O/CLR中使用它,则在dll初始化期间,从带有"extern"的记录器库导出的类不会调用记录器dll内的构造函数或析构函数。事实上,记录器库中从来没有调用构造函数/析构函数(注意,即使是扩展类构造函数也没有调用),只有半初始化的类的空壳存在。

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

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

此外,我可以重写外部类,不使用构造函数/析构函数,并编写一些相同的弹性模拟,但在浪费了一整天的时间之后,我想知道这个问题背后的原因,还有没有其他更优雅的解决方案,或者如果我在某个地方犯了错误:即使用#pragma-managed(push,off)会产生这种症状或类似症状?

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

有两个魔鬼使代码丢失行为。

第一个魔鬼:警告消息是第一条线索,即在第一次执行MANAGED CODE之前,导出的初始化程序将不会运行(猜测编译托管代码时,加载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)

现在编译得很好,但在这种情况下,所有的"本机"类实际上都只是以本机的形式存在,从来没有调用过托管代码——从来没有创建过托管VFTABLE。托管函数在加载之前具有不同的VFTABLE,在执行实际托管函数之前,每个函数都会被重写以加载CLR。你只能用IDA分解器或类似的实用程序来发现。。。但也在下页底部简要说明:https://msdn.microsoft.com/en-us/library/ms173266.aspx?f=255&MSPPError=-2147217396"混合程序集的初始化"。因为从来没有调用过外部本机类的初始值设定项,静态存储在LIB/DLL中的内容被保存在内存中(空壳)。因此,调用混合代码LIB/DLL(从不使用/触发CLR代码)的纯本机应用程序永远不会在本机编译的类中调用构造函数/析构函数。

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

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

这是CLR代码收到初始化通知并开始加载LIB/DLL导出的本机类的实际时刻。我能够通过将属于本机类的空函数放入托管代码中来触发这一点:

...
#pragma managed(push, off)
...
#pragma managed(pop)
void CBla1::ManagedCall()
{ }
#pragma managed(push, off)
...

调用此函数导致执行CLR加载程序,以返回初始化我的extern vars。

我不确定为什么要这样做,可能是因为CLR在使用之前从不加载任何内容。不知道如果我使用NGen将CLR编译为本机代码,情况会是一样的吗?但这可能是另一次冒险。

这就完成了我对为什么外部变量没有在混合LIB/DLL中调用构造函数/析构函数的回答。