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;
};
}

main 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

main 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 not at class создание экземпляра, но при первом обращении к статическому члену. Это должно быть связано с так называемой отложенной инициализацией, которая была введена с .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.

main VS2008 new
Теперь тестовая программа создает объект производного класса. Он работает, как ожидалось, в обоих способах выполнения (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

main VS2013 new
Теперь тестовая программа создает объекты обоих производных классов. Он работает должным образом при запуске из среды 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 - нет.

Кроме того, в верхних базовых классах:
Почему cctor .net 4.0 вызывается при создании экземпляра класса, а cctor .net2.0 вызывается по запросу?

Редактировать 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, а внутри самой среды CLR .net 4.0 ...


person Tobias Knauss    schedule 07.04.2015    source источник
comment
WRT - это ошибка в компиляторе, я опубликовал отчет об ошибке в Microsoft Connect несколько лет назад. В то время существовали противоречивые требования к языку и спецификациям CLR. К сожалению, я больше не могу получить доступ к этому отчету об ошибке.   -  person Ben Voigt    schedule 07.04.2015
comment
это (связано с) connect.microsoft.com/VisualStudio/feedback/details/611716/?   -  person Tobias Knauss    schedule 07.04.2015
comment
Не могу сказать, у меня страница не найдена. Запрошенный вами контент не может быть найден или у вас нет разрешения на его просмотр. по этой ссылке. Позвольте мне попробовать еще раз после выхода из системы ...   -  person Ben Voigt    schedule 07.04.2015
comment
Похоже, они взломали мою учетную запись, я могу просмотреть больше проблем, когда не вошел в систему. Это похоже на связь, но это было не то, о чем я думал.   -  person Ben Voigt    schedule 07.04.2015
comment
поскольку вы более или менее подтвердили, что это ошибка, я создал отчет об ошибке в VS2013 сейчас: connect.microsoft.com/VisualStudio/Feedback/details/1231715   -  person Tobias Knauss    schedule 07.04.2015


Ответы (1)


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

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

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

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

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

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

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

person Tobias Knauss    schedule 08.04.2015
comment
Я столкнулся с такой же или очень похожей проблемой в C # (.Net 4.0), и явный вызов конструктора класса был единственным способом решить ее так, как я хотел. В моем случае использовались абстрактные и производные классы, статический конструктор и инициализация статического поля. - person tomosius; 19.03.2016