为什么导入混合本机/ CLR lib / dll的本机c ++应用程序未在混合lib / dll中的外部变量上调用ctor / dtor

时间:2018-10-25 18:41:03

标签: c++ dll clr native

编写(在正文中进一步记录:记录器)全面的记录器/诊断/性能分析器/调试器功能以及不计其数的其他功能的本机堆栈遍历器/托管的堆栈遍历器功能,并且库必须在本机或托管堆上的不同模块之间共享(用asm / native c ++ / managed c ++ /。Net / ...编写)。 到目前为止,当此记录器和导入器/实现器/调用者应用程序都以本机或托管代码编译时,一切工作正常。但是,如果我将记录器库编译为“使用/ CLR管理”,并在本机C ++项目W / O / CLR中使用它,则在dll初始化期间,从记录器库导出的带有“ extern”的类不会在记录器dll中调用构造函数或析构函数。实际上,在记录器库中从未调用过构造方法/析构方法(也注意甚至没有扩展类的构造方法),只有空的类外壳存在一半初始化了。

要更清楚地了解记录器库:本机部分实现了记录器库的全部功能。尽管托管部分实际上是本地代码的“简单”包装器,但出于可移植性和维护的原因,我需要在同一dll中完全实现该托管器。记录器库旨在替换仅使用10年的同类库,而该库仅具有此新记录器库功能的20%。

现在这不是我第一次遇到此问题或类似问题,过去的解决方案要么拆分为纯本机代码,要么拆分为纯托管代码,一个解决方案包含另一个包装(两个项目需要维护,可移植性都没有) )。或者编译两个版本的库,一个用于本机应用程序,另一个用于托管应用程序。现在在这种情况下,这些不是解决方案,而是局限性,因为我需要使代码在同一个进程管道中运行,而不管它是本机dll,本机应用程序,托管dll,托管应用程序,...,为简单起见,我需要将所有功能集成缘故。

我也可以重写extern类而不使用构造函数/析构函数,并编写一些类似的弹性模拟,但是在浪费了整整一天的时间后,我想知道这个问题的原因,还有其他解决方案吗?优雅还是我在某个地方犯了错误:即使用#pragmamanaged(push,off)会产生这种症状还是类似现象?

有人知道背后的原因吗? 任何想法都值得赞赏。

1 个答案:

答案 0 :(得分:0)

有两个让代码遗漏行为的恶魔。

第一个魔鬼:警告消息是在首次执行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中。 因此,纯粹的本机应用程序调用永远不会使用/触发CLR代码的混合代码LIB / DLL绝不会在本机编译类中调用Constructor / Destructor。

解决方案: 克服此限制的唯一方法是在其中放置一些托管函数以强制加载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加载程序,以返回初始化我的外部变量。

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

这完成了我的答案,为什么extern var无法在混合的LIB / DLL中获得对构造函数/析构函数的调用。