C++/cli статический конструктор производного класса не вызывается

Как описано в другом моем SO-посте, я увидел странное поведение моего приложения после перехода с 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
При компиляции testAssembly_2-0 а также main в VS 2008 (сборка.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
При компиляции testAssembly_4-0 а также main в VS 2013 (создание сборки и приложения.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 new, один раз с Class40 и один раз с Class20).
Но при запуске exe результат ошибочен:

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

Почему производный cctor() сборки.net 2.0 не вызывается?
Это предполагаемое поведение отложенной инициализации.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...

1 ответ

Решение

Я нашел обходной путь, вызвав статический конструктор вручную. Я даже не знал, что это возможно, пока не прочитал это несколько минут назад:

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

Редактировать:
Недавно у меня возникла проблема, связанная с тем, что моя программа велась по-разному в зависимости от того, запускался ли он из VS2008 (на этот раз не из VS2013!) Или из exe, хотя я и вызвал статический cctor вручную.
Проблема заключалась в том, что исполнитель НЕПРАВИЛЬНОГО КЛАССА был казнен! Очень странно!
Мой дизайн:

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

я звонил C2.cctor, но C1.cctor был запущен. При добавлении некоторых произвольных команд ведения журнала он снова вел себя по-другому и в итоге работал. Вот тогда я решил полностью удалить cctor и ввести static Init() вместо.

Будьте готовы встретить то же самое!
Вы все еще можете позвонить cctor вручную, это работало для меня долгое время.

Я хотел бы продолжить расследование и проанализировать MSIL, но в то время я был слишком занят.

Другие вопросы по тегам