【问题标题】:c++/cli static constructor of derived class is not called不调用派生类的c++/cli静态构造函数
【发布时间】:2015-06-12 03:52:00
【问题描述】:

正如in another SO post of me 所述,在从 VS 2008 (.net 3.5) 迁移到 VS 2013(并使用 .net 4.0,而不是 4.5)后,我看到了我的应用程序的一个奇怪行为。我发现一个类的静态构造函数(cctor)不再被调用。因此,我将应用程序分解为一个小测试程序:

DLL testAssembly_2-0 和 testAssembly_4-0
(类似的内容;testAssembly_4-0 的名称是40而不是20

namespace testAssembly_20
{
public ref class Class20
{
public:
  Class20 ()
  { Console::WriteLine (__FUNCTION__"()"); }

  static Class20 ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue);
    ms_iValue = 2;
    Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

  void func20 ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

protected:
  static int ms_iValue = 1;
};
}

主要的 VS2008
在VS 2008中编译testAssembly_2-0main时(制作.net 2.0汇编和应用),两种执行方式都按预期运行(在IDE中启动调试模式,直接启动exe):

int main ()
{
  testAssembly_20::Class20^ oC20 = gcnew testAssembly_20::Class20;
  oC20->func20 ();
}
// output:
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20::func20() ms_iValue=2

主要的 VS2013
在 VS 2013 中编译 testAssembly_4-0main(创建 .net 4.0 程序集和应用程序)并包括现有的 .net 2.0 testAssembly_2-0(使用 app.config,请参阅我的链接帖子)时,它仍然有效,但它与 IDE 调试和 exe 启动相比,其行为有所不同。
IDE 调试产生上述结果(一次使用 Class20,一次使用 Class40)。
exe start 不是在类实例化时调用cctor,而是在第一次访问静态成员时调用。这一定是由于 .net 4.0 引入的所谓的延迟初始化,据我在过去几个小时的研究中所知道的。

int main ()
{
  testAssembly_40::Class40^ oC40 = gcnew testAssembly_40::Class40;
  oC40->func40 ();
  testAssembly_20::Class20^ oC20 = gcnew testAssembly_20::Class20;
  oC20->func20 ();
}
// output of exe start:
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=1
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=2
// testAssembly_40::Class40::Class40()
// testAssembly_40::Class40::func40() ms_iValue=2
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20::func20() ms_iValue=2

DLL 增强
由于这还没有重现我的失败,我在类中添加了一个属性来访问静态成员,就像我在原始应用程序中所做的那样。在main() 中查询此属性只会导致函数调用顺序不同(Class20 cctor 现在首先被调用,直接在main() 的开头)。但行为是正确的。

因此,我向我的原始应用程序更进一步,并将派生类添加到两个程序集:

public ref class Class20derived : Class20
{
public:
  Class20derived ()
  { Console::WriteLine (__FUNCTION__"()"); }

  static Class20derived ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue);
    ms_iValue = 3;
    Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

  void func20derived ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }
};

Class40derived is similar again.

主要的 VS2008 新
测试程序现在创建派生类的对象。它以两种执行方式(IDE,exe直接)按预期运行:

int main ()
{
  testAssembly_20::Class20derived^ oC20D = gcnew testAssembly_20::Class20derived;
  oC20D->func20 ();
}
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20derived::Class20derived (static class constructor)() ms_iValue=2
// testAssembly_20::Class20derived::Class20derived (static class constructor)() ms_iValue=3
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20derived::Class20derived()
// testAssembly_20::Class20::func20() ms_iValue=3

主要的 VS2013 新
测试程序现在创建两个派生类的对象。从 IDE 启动时按预期运行(结果与 VS2008 新版相同,一次使用 Class40,一次使用 Class20)。
但是在启动 exe 时,结果是错误的: p>

int main ()
{
  testAssembly_40::Class40derived^ oC40D = gcnew testAssembly_40::Class40derived;
  oC40D->func40 ();
  testAssembly_20::Class20derived^ oC20D = gcnew testAssembly_20::Class20derived;
  oC20D->func20 ();
}
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=1
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=2
// testAssembly_40::Class40derived::Class40derived (static class constructor)() ms_iValue=2
// testAssembly_40::Class40derived::Class40derived (static class constructor)() ms_iValue=3
// testAssembly_40::Class40::Class40()
// testAssembly_40::Class40derived::Class40derived()
// testAssembly_40::Class40::func40() ms_iValue=3
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20derived::Class20derived()
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
--> where is the Class20derived cctor??
// testAssembly_20::Class20::func20() ms_iValue=2

为什么不调用 .net 2.0 程序集的派生 cctor()?
这是.net 4.0延迟初始化的预期行为,还是我假设它是编译器中的错误?这里奇怪的是,.net 4.0 程序集使用正确,但 .net 2.0 程序集没有。

另外,在顶部的基类中:
为什么在类实例化时调用 .net 4.0 cctor,但按需调用 .net2.0 cctor?

编辑 1

我刚刚发现同一个应用程序(VS2008,DLL 增强)在作为 exe 执行时,无论是否带有 app.exe.config,行为都会有所不同!
当 app.config 存在时,应用程序就像在 VS2013 中编译一样工作,这意味着它是错误的。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

但只要我删除 app.config,应用程序就会运行良好。
所以我认为这个错误不在 VS C++/CLI 编译器内部,而是在 .net 4.0 CLR 本身内部......

【问题讨论】:

  • WRT “它是编译器中的错误吗”,我在几年前向 Microsoft Connect 发布了错误报告。当时在语言和 CLR 规范中存在相互冲突的要求。遗憾的是,我似乎无法再访问该错误报告了。
  • 不知道,我从该链接收到“找不到页面您请求的内容无法找到或您无权查看它。”。让我退出后重试...
  • 看起来他们已经破坏了我的帐户,我可以在未登录时查看 更多 个问题。这看起来很相关,但不是我想的那个。
  • 当您或多或少确认这是一个错误时,我现在在 VS2013 中创建了一个错误报告:connect.microsoft.com/VisualStudio/Feedback/details/1231715

标签: .net-4.0 c++-cli .net-2.0 derived-class static-constructor


【解决方案1】:

我通过手动调用静态构造函数找到了解决方法。直到我几分钟前读到它,我才知道这是可能的:

System::Runtime::CompilerServices::RuntimeHelpers::RunClassConstructor (
  testAssembly_20::Class20derived::typeid->TypeHandle);

编辑:
最近我遇到了一个问题,即我的程序的行为会因从 VS2008 (这次不是 VS2013!)或 exe 运行而有所不同,即使我手动调用了静态 cctor。
问题是 WRONG CLASS 的 cctor 被执行了! 很奇怪!
我的设计:

base A
derived B : A
derived C1 : B
derived C2 : B

我打电话给C2.cctor,但C1.cctor 已运行。添加一些任意日志记录命令时,它的行为再次不同并最终起作用。那时我决定完全删除 cctors 并引入static Init()

准备好遇到同样的事情!
您仍然可以手动拨打cctor,它已经为我工作了很长时间。

我很想进一步调查和分析 MSIL,但当时我太忙了。

【讨论】:

  • 我在 C# (.Net 4.0) 中遇到了相同或非常相似的问题,显式调用类构造函数是按照我想要的方式解决它的唯一方法。我的案例涉及抽象类和派生类以及静态构造函数和静态字段初始化。
猜你喜欢
  • 2011-09-17
  • 2020-11-28
  • 2015-08-18
  • 1970-01-01
  • 2016-07-19
  • 2012-11-06
  • 2018-07-21
  • 1970-01-01
  • 2011-03-12
相关资源
最近更新 更多