Ошибка блокировки загрузчика с управляемой C++ dll, статически связанной с нативной C++ lib

У меня есть управляемый C++ dll с несколькими управляемыми классами, которые в свою очередь вызывают собственный код C++ в библиотеке, которую я статически связал с dll. Тем не менее, если я пытаюсь запустить RegAsm.exe на dll, инструмент правильно сообщает "нет типов, которые мы зарегистрировали", но затем зависает. Я очень уверен, что это проблема блокировки загрузчика, и моя DLL зависает, когда RegAsm пытается загрузить его. Я использую Visual Studio 2008, экспресс-издание.

Что меня озадачивает, что все отлично работает при помещении нативного кода в dll, а не при статической привязке его из библиотеки. Я знаю, что этот пост похож на этот вопрос, но у меня нет DllMain в моей dll, нет риска того, что я буду запускать код MSIL из DllMain. Также не помогло следование советам настройки /clr для отдельных файлов.

Компиляция dll с /NOENTRY решает проблему блокировки, но вызывает разрыв приложения с Type initializer for <Module> threw exception исключение и, по-видимому, рекомендуется только.NET 2003.

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

Просто чтобы уточнить: хотя мне не нужно запускать RegAsm.exe на dll, я использую его для проверки проблемы блокировки загрузчика. На самом деле я использую dll в ассемблере aC#, который реализует несколько COM-видимых классов - поэтому мне нужно сделать COM-регистрацию для этого. В конце C# IDE вылетает при регистрации взаимодействия COM, сообщая об ошибке времени выполнения R6033 C++: попытайтесь использовать код MSIL из этой сборки во время инициализации собственного кода. Это указывает на ошибку в вашем приложении. Скорее всего, это результат вызова MSIL-скомпилированной (/clr) функции из собственного конструктора или из DllMain.

Решил проблему, но мало что неясно, и мне любопытно:

Я заметил, что две статические переменные были добавлены в заголовочный файл в статически связанной библиотеке в то время, когда все перестало работать, это выглядело так:

// 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 (и изменение static в extern) исправляет блокировку загрузчика. Может ли кто-нибудь указать, почему инициализатор вообще скомпилируется в MSIL?

До исправления, если я только #include заголовочный файл из управляемого DLL, все работало нормально. Но если бы я включил заголовок И также связанный с библиотекой, вещи не работали. Поскольку библиотека также использует заголовок внутри, я в итоге получил два экземпляра статической переменной? В любом случае, почему жалоба на запуск кода MSIL?

Хотя сейчас все работает, любое понимание будет приветствоваться.

2 ответа

Решение

На следующей странице (особенно в разделах " Инициализация статических объектов и реализация в заголовках") сказано следующее:

Поскольку один и тот же заголовок может быть включен как в файлы CPP с включенным и отключенным параметром /clr, так и #include может быть заключен в неуправляемый блок #pragma, возможно иметь как MSIL, так и собственные версии функций, которые обеспечивают реализации в заголовках,

а также

В Visual C++ 2005 для удобства пользователей, имеющих дело с блокировкой загрузчика, компоновщик будет выбирать нативную реализацию вместо управляемой, когда представлен обеими. ... Однако в этом выпуске есть два исключения из этого правила из-за двух нерешенных проблем с компилятором:
...
- Вызов встроенной функции осуществляется через глобальный статический указатель на функцию. Этот сценарий особенно примечателен тем, что виртуальные функции вызываются через глобальные указатели функций.

При размещении статической переменной непосредственно в управляемой dll (или, в этом отношении, при компиляции с \ clr) блокировка загрузчика исключается, поскольку в DllMain происходит только инициализация собственных статических переменных. Однако любая статическая переменная, скомпилированная как нативная, блокируется при выполнении кода MSIL. В нашем случае MSIL генерируется для части STL, которая используется конструктором статического объекта, из-за неожиданного поведения компоновщика при представлении как в собственной реализации, так и в реализации MSIL.

Решением будет:

  • Скомпилируйте все с /clr (для нас это невозможно, так как нам нужно использовать статическую библиотеку)
  • Убедитесь, что всем # включенным сторонним заголовкам (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
    

Компилятор помещает пользовательский DllMain в свой собственный код, который используется для инициализации библиотеки времени выполнения и глобальных переменных (чтобы пользовательский DllMain мог работать с уже созданными глобальными объектами и использовать стандартную библиотеку). Даже если у вас нет DllMain, он все равно будет сгенерирован компилятором. С /NOENTRY вы указываете компоновщику игнорировать его.

Таким образом, выполнение чего-либо в конструкторе статической переменной по сути аналогично выполнению этого в DllMain, и, похоже, именно здесь.

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