Экспортируйте метод dll из C ++ в C #. Зачем мне нужно: "extern" C "" - PullRequest
10 голосов
/ 16 декабря 2011

В моей DLL есть метод, который я хочу экспортировать.

// Работает:

extern "C" __declspec(dllexport)

// Не будет работать

__declspec(dllexport)

C ++ Export:

 extern "C" __declspec(dllexport) int Test();

C # импорт:

[DllImport("CircleGPU2_32.DLL", EntryPoint = "Test", 
    CallingConvention = CallingConvention.StdCall)]
public static extern int Test();

Зачем мне нужен внешний "C" ?

Ответы [ 4 ]

23 голосов
/ 16 декабря 2011

Это происходит из-за искажения имени, выполненного C ++.

extern "C" vs no extern "C"

В качестве примера приведу то, что CFF Explorer показывает для таблицы экспорта библиотеки DLL.Первый был построен с помощью компилятора Borland c ++.Вторая была построена с использованием msvc.

Ordinal     FunctionRVA     Name RVA    Name
00000001    0020E140        0032C2B6    createDevice
00000002    0020E244        0032C2C3    createDeviceEx
0000000D    00328DA4        0032C0C1    @irr@core@IdentityMatrix
0000000E    00328DE4        0032C28A    @irr@video@IdentityMaterial

0000000C    000F9C80        001EE1B6    createDevice
0000000D    000F9CE0        001EE1C3    createDeviceEx
00000001    00207458        001EDDC7    ?IdentityMaterial@video@irr@@3VSMaterial@12@A
00000002    001F55A0        001EDDF5    ?IdentityMatrix@core@irr@@3V?$CMatrix4@M@12@B

Первые 2 функции createDevice и createDeviceEx содержат сигнатуру extern "C" в своем прототипе, а другие нет.Обратите внимание на разницу в кодировании, когда используется искажение C ++.На самом деле разница на глубже .

ABI и стандартизация

Как объясняется в других ответах, стандарт C ++ не определяет A bstract B inary I nterface .Это означает, что поставщики, разрабатывающие свои инструменты, могут делать все, что им захочется, когда дело доходит до того, как обрабатываются вызовы функций и как перегрузка работает под капотом - при условии, что она демонстрирует ожидаемое поведение, как предписано стандартом.

При всех этих различных схемах кодирования у другого языка нет никакой надежды на работу с модулями, скомпилированными с C ++.Модули Heck, скомпилированные одним компилятором C ++, вряд ли будут работать с другим!Поставщики компиляторов могут по своему усмотрению менять кодировку между версиями.

Кроме того, отсутствие общего ABI означает, что не существует единого ожидаемого способа вызова этих функций / методов.Например, один компилятор может передавать свои аргументы в стеке, в то время как другой компилятор может передавать его в регистр.Один может передавать аргументы слева направо, а другой - наоборот.Если только один из этих аспектов не совпадает точно между вызывающим и вызываемым, то ваше приложение будет зависать ... то есть, если вам повезет.Вместо того, чтобы иметь дело с этим, поставщики просто говорят «нет», вызывая ошибку компоновки с различными кодировками.

OTOH, в то время как C также не имеет стандартизированного ABI, C является гораздо более простым языком для сравнения.,Большинство поставщиков компиляторов Си обрабатывают оформление функций и механизм вызова аналогичным образом.В результате существует своего рода «де-факто» стандарт, даже если ABI явно не указан в стандарте.Благодаря этой общности намного проще для других языков взаимодействовать с модулями, скомпилированными с C.

Например, украшение __stdcall в сигнатуре функции понимается в соответствии с определенным соглашением о вызовах.Аргументы передаются справа налево, и вызываемый абонент отвечает за очистку стека после этого.__cdecl похож, но понятно, что вызывающий отвечает за очистку стека.

Итог

Если данный модуль должен быть совместим с языками за пределамиC ++, правильно его декорировать и выставить как API C - ваш лучший выбор.Обратите внимание, что вы отказываетесь от некоторой гибкости, делая это.В частности, вы не сможете перегрузить эти функции, так как компилятор больше не может генерировать уникальные символы для каждой перегрузки с искажением имени.

Если совместимость не важна для рассматриваемого модуля - он только собираетсядля использования с тем же инструментом, с помощью которого он был построен, затем опустите украшение extern "C" в своих прототипах.

11 голосов
/ 16 декабря 2011

Основная причина состоит в том, чтобы запретить обработчику имен в C ++ название функции.

Попробуйте экспортировать его без extern "C" и проверьте полученную DLL в Dependency Walker, и вы увидите совершенно другое имя для экспортируемой функции.

1 голос
/ 16 декабря 2011

Компилятор обычно украшает ваши экспортированные имена для включения информации о классе и подписи.extern "C" говорит компилятору не делать этого.

0 голосов
/ 20 июня 2013

Я пробовал это с функцией с 1 параметром , и вам нужно использовать

[DllImportAttribute ("what.Dll", CallingConvention = CallingConvention. Cdecl )]

для импорта в C # для работы.Оператор Stdcall работает (в представленной ситуации, но не в целом) для функций без параметров, которые возвращают void.Протестировано в vs2012 Express Edition.

В качестве дополнительного примечания, средство обхода зависимостей можно загрузить с http://www.dependencywalker.com/

...