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, но в то время я был слишком занят.