4

我有一个托管 c++ dll,其中包含几个托管类,这些托管类反过来调用我静态链接到 dll 的库中的本机 c++ 代码。但是,如果我尝试在 dll 上运行 RegAsm.exe,该工具会正确报告“没有我们注册的类型”,但随后会挂起。我很确定这是一个加载程序锁定问题,当 RegAsm 尝试加载它时,我的 dll 挂起。我正在使用 Visual Studio 2008,速成版。

令我困惑的是,将本机代码放入 dll 时一切正常,但从库中静态链接时却不行。我知道这篇文章与这个问题类似,但我的 dll 中没有 DllMain,我没有从 DllMain 运行 MSIL 代码的风险。此外,遵循在单个文件上设置 /clr 的建议也无济于事。

使用 /NOENTRY 编译 dll 可修复锁定问题,但会导致应用程序因Type initializer for <Module> threw exception异常而中断,并且显然仅在 .NET 2003 中推荐使用。

我怀疑静态成员的初始化可能是罪魁祸首,但为什么在我的静态库中将其编译为 MSIL 超出了我的范围。

澄清一下:虽然我不需要在 dll 上运行 RegAsm.exe,但我使用它来检查加载程序锁定问题。实际上,我正在使用 ac# 程序集中的 dll,它确实实现了几个 COM 可见的类 - 所以我需要在那个上进行 COM 注册。最后,C# IDE 在注册 COM 互操作期间崩溃,报告“R6033 c++ 运行时错误:尝试在本机代码初始化期间使用此程序集中的 MSIL 代码。这表明您的应用程序中存在错误。这很可能是从本机构造函数或 DllMain 调用 MSIL 编译 (/clr) 函数的结果。

解决了这个问题,但很少有事情不清楚,我很好奇:

我注意到当事情停止工作时,两个静态变量被添加到静态链接库中的头文件中,看起来像这样:

// The whole header is forced to compile as native 
#pragma managed(push, off)
....
static const std::locale commaSeparator(std::locale::classic(), 
                                        new DecimalSeparator<char>(','));;
....
#pragma managed(pop)

将初始化移动到 .cpp 文件(并更改staticextern)修复了加载程序锁定。谁能指出为什么初始化程序会被编译为 MSIL?

在修复之前,如果我只 #included 托管 dll 中的头文件,则一切正常。但是,如果我包含标题并且还链接到库,那么事情就不起作用了。由于 lib 也在内部使用了标头,所以我最终得到了静态变量的两个实例吗?无论如何,为什么要抱怨运行 MSIL 代码?

尽管现在一切正常,但欢迎任何见解。

4

2 回答 2

1

下一页,(尤其是初始化静态对象标题部分的实现)有以下内容要说:

由于启用和禁用 /clr 的 CPP 文件可能包含相同的标头,或者可以将 #include 包装在 #pragma 非托管块中,因此可以同时拥有 MSIL 和在标头中提供实现的函数的本机版本.

在 Visual C++ 2005 中,为了方便用户处理加载程序锁,链接器将选择本地实现而不是托管实现。... 但是,由于编译器存在两个未解决的问题,此版本中有两个例外情况:
...
- 对内联函数的调用是通过全局静态函数指针进行的。这种情况特别值得注意,因为虚函数是通过全局函数指针调用的。

当将静态变量直接放置在托管 dll 中时(或者当使用 \clr 编译时),加载器锁定被避免,因为只有本地静态变量的初始化发生在 DllMain 中。但是,如果执行 MSIL 代码,任何编译为本机的静态变量都会死锁。在我们的例子中,MSIL 是为静态对象的构造函数使用的 STL 的一部分生成的,这是由于在呈现本机和 MSIL 实现时令人惊讶的链接器行为。

一个解决方案是:

  • 编译所有东西/clr(对我们来说不可能,因为我们需要使用静态库)
  • 确保所有#included 3rd 方标头(STL)都以#pragma unmanaged(太复杂)开头
  • 从标头中删除静态变量初始化(通过外部链接)

    // Solved by replacing initialization in header file
    static const std::locale commaSeparator(...);
    // with 
    extern const std::locale commaSeparator;
    // and doing initialization in a cpp file
    
于 2011-08-22T08:53:45.393 回答
1

编译器用自己的代码包装用户定义的 DllMain,用于初始化运行时库和全局变量(以便用户定义的 DllMain 可以与已创建的全局对象一起运行并可以使用标准库)。即使您没有 DllMain,仍然会有编译器生成的。使用 /NOENTRY 可以指示链接器忽略它。

因此,在静态变量的构造函数中执行某些操作实际上与从 DllMain 执行操作相同,这里似乎就是这种情况。

于 2011-08-18T06:40:53.193 回答