Написание C ++ предназначено для вызова из C #? - PullRequest
2 голосов
/ 24 марта 2012

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

В C # я использовал DllImport много раз, чтобы получить информацию из user32.dll или других DLL, которыеЯ не писал, но я хочу лучше понять, как реализована другая половина (половина C ++), чтобы это произошло.

Код C ++, который у меня есть, прост и просто для проверки того, что вызовпрошел успешно:

#include <iostream>

using namespace std;

__declspec(dllexport) void HelloWorld() {
    cout << "Hello, World" << endl;
}

Я не знаю, какова важность __declspec(dllexport), но я видел это на паре веб-сайтах , которыене особо затронул его важность.

Мой C # не сильно отличается от предыдущих DllImport s, которые я делал ранее:

[DllImport("TestDLL.dll")]
static extern void HelloWorld();

static void Main(string[] args) {
    HelloWorld();
}

Я скомпилировал C ++ DLLи поместите его в проект C #, и он будет скопирован в папку bin.Когда я запускаю проект C #, я получаю EntryPointNotFoundException при вызове HelloWorld() внутри основной функции.

Я предполагаю, что мне нужно либо изменить код C ++, либо флаги компиляции проекта C ++,В настоящее время «Использование MFC» установлено на «Использовать стандартные библиотеки Windows», и ATL или CLR здесь не используются.Любая помощь будет принята с благодарностью.

Ответы [ 3 ]

5 голосов
/ 24 марта 2012

C ++ - это язык, который поддерживает перегрузку.Другими словами, вы можете иметь более одной версии HelloWorld ().Вы также можете экспортировать HelloWorld (int), отдельную версию.Это также язык, который требует компоновщика.Чтобы не путать компоновщик с одинаковыми именами для разных функций, компилятор декорирует имя функции.Aka "name mangling".

Инструмент, который вы хотите использовать для устранения подобных проблем, это Dumpbin.exe.Запустите его из командной строки Visual Studio в вашей DLL с параметром / exports.Вы увидите это:

ordinal hint RVA      name

      1    0 000110EB ?HelloWorld@@YAXXZ = @ILT+230(?HelloWorld@@YAXXZ)

Куча gobbledegook, экспортированное имя показано в скобках.Обратите внимание ?спереди и @@ YAXXZ после имени, поэтому CLR не может найти экспортированную функцию.Функция, которая принимает аргумент int, будет экспортирована как? HelloWorld @@ YAXH @ Z (попробуйте).

Директива [DllImport] поддерживает это, вы можете использовать свойство EntryPoint, чтобы присвоить экспортируемое имя.Или вы можете сказать компилятору C ++, что он должен генерировать код, который может использовать компилятор C.Поставьте extern "C" перед объявлением, и компилятор C ++ подавит оформление имени.И, конечно, больше не будет поддерживать перегрузки функций.Dumpbin.exe теперь показывает это:

ordinal hint RVA      name

      1    0 00011005 HelloWorld = @ILT+0(_HelloWorld)

Обратите внимание, что имя все еще не просто "HelloWorld", перед именем есть подчеркивание.Это украшение, которое помогает поймать ошибки в соглашении о вызовах.В 32-битном коде есть 5 различных способов вызова функции.Три из них являются общими с DLL, __cdecl, __stdcall и __thiscall.Компилятор C ++ по умолчанию равен __cdecl для обычных свободных функций.

Это также свойство атрибута [DllImport], свойства CallingConvention.По умолчанию используется, если он не указан, CallingConvention.StdCall.Что соответствует соглашению о вызовах для многих библиотек DLL, особенно Windows, но не соответствует стандартному компилятору C ++, поэтому у вас все еще есть проблема.Просто используйте свойство или объявите свою функцию C ++ следующим образом:

extern "C" __declspec(dllexport) 
void __stdcall HelloWorld() {
    // etc..
}

И вывод Dumpbin.exe теперь выглядит так:

ordinal hint RVA      name

      1    0 000110B9 _HelloWorld@0 = @ILT+180(_HelloWorld@0)

Обратите внимание на добавленный @ 0, он описывает размеркадра активации стека.Другими словами, сколько байтов стоит аргументов.Это помогает отследить ошибку объявления во время соединения, такие ошибки чрезвычайно трудно диагностировать во время выполнения.

Теперь вы можете использовать атрибут [DllImport], как он был у вас изначально, маршаллер pinvoke достаточно умен, чтобы разобратьсяукрашение фактической функции.Вы можете помочь ему со свойствами ExactSpelling и EntryPoint, это будет немного быстрее, но вы ничего не заметите.

Первый вопрос последний: __declspec (dllexport) - это просто подсказка компилятору, который вы собираетесь экспортироватьфункция из DLL.Он сгенерирует лишний код, который поможет ускорить вызов экспортированной функции (ничего, что использует CLR).И передает инструкции компоновщику, что функция должна быть экспортирована.Экспорт функций также можно выполнить с помощью файла .def, но это сложно сделать.

1 голос
/ 24 марта 2012

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

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

1 голос
/ 24 марта 2012

Вероятно, это лучший способ сделать это: Как импортировать и использовать неуправляемый класс C ++ из C #?

Я бы порекомендовал вам создать проект C ++ / CLI, который статически связан с вашим чистым C ++. Проект C ++ / CLI сгенерирует DLL, и вы будете использовать ее так же, как и любую другую DLL в C #. Опять же, см. Ссылку выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...