Экспорт функций из DLL с помощью dllexport


105

Мне нужен простой пример экспорта функции из C ++ DLL Windows.

Я хотел бы увидеть заголовок, .cppфайл и .defфайл (если это абсолютно необходимо).

Я бы хотел, чтобы экспортируемое имя не было украшено . Я хотел бы использовать самое стандартное соглашение о вызовах ( __stdcall?). Я бы хотел использовать __declspec(dllexport)и не использовать .defфайл.

Например:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

Я пытаюсь не допустить, чтобы компоновщик добавил к имени символы подчеркивания и / или числа (количество байтов?).

Я нормально не поддерживаю dllimportи не dllexportиспользую тот же заголовок. Мне не нужна информация об экспорте методов класса C ++, только глобальные функции в стиле c.

ОБНОВИТЬ

Исключение соглашения о вызовах (и использовании extern "C") дает мне имена экспорта, которые мне нравятся, но что это значит? Независимо от соглашения о вызовах по умолчанию, которое я получаю, pinvoke (.NET), declare (vb6) и ожидаемый GetProcAddress? (Я думаю, GetProcAddressэто будет зависеть от указателя функции, созданного вызывающим).

Я хочу, чтобы эта DLL использовалась без файла заголовка, поэтому мне действительно не нужно много фантазий, #definesчтобы сделать заголовок пригодным для использования вызывающим.

Я согласен с тем, что мне нужно использовать *.defфайл.


Возможно, я неправильно запоминаю, но я думаю, что: a) extern Cудалит украшение, которое описывает типы параметров функции, но не украшение, которое описывает соглашение о вызове функции; б) чтобы удалить все декорации, вам необходимо указать (недекорированное) имя в файле DEF.
ChrisW

Это то, что я тоже видел. Может, стоит добавить это как полноценный ответ?
Aardvark

Ответы:


134

Если вам нужен простой экспорт на C, используйте проект C, а не C ++. Библиотеки DLL C ++ полагаются на изменение имен для всех измов C ++ (пространства имен и т. Д.). Вы можете скомпилировать свой код как C, зайдя в настройки вашего проекта в C / C ++ -> Advanced, там есть опция «Compile As», которая соответствует переключателям компилятора / TP и / TC.

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

Экспорт / импорт библиотек DLL в VC ++

На самом деле вам нужно определить условный макрос в заголовке, который будет включен во все исходные файлы в вашем проекте DLL:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

Затем для функции, которую вы хотите экспортировать, вы используете LIBRARY_API:

LIBRARY_API int GetCoolInteger();

В проекте сборки библиотеки создайте определение, LIBRARY_EXPORTSэто приведет к тому, что ваши функции будут экспортированы для сборки DLL.

Поскольку LIBRARY_EXPORTSне будет определен в проекте, использующем DLL, когда этот проект включает файл заголовка вашей библиотеки, вместо этого будут импортированы все функции.

Если ваша библиотека должна быть кросс-платформенной, вы можете определить LIBRARY_API как ничто, если не в Windows:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

При использовании dllexport / dllimport вам не нужно использовать файлы DEF, если вы используете файлы DEF, вам не нужно использовать dllexport / dllimport. Эти два метода решают одну и ту же задачу разными способами, я считаю, что dllexport / dllimport - это рекомендуемый метод из двух.

Экспорт несвязанных функций из библиотеки DLL C ++ для LoadLibrary / PInvoke

Если вам нужно использовать LoadLibrary и GetProcAddress или, возможно, импортировать с другого языка (например, PInvoke из .NET или FFI в Python / R и т. Д.), Вы можете использовать extern "C"встроенный с вашим dllexport, чтобы сообщить компилятору C ++ не искажать имена. И поскольку мы используем GetProcAddress вместо dllimport, нам не нужно выполнять танец ifdef сверху, просто простой dllexport:

Код:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

А вот как выглядит экспорт с помощью Dumpbin / exports:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

Итак, этот код отлично работает:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern «C», похоже, удаляет искажение имени стиля С ++. Весь вопрос об импорте и экспорте (который я пытался предложить не включать в вопрос) на самом деле не то, о чем я спрашиваю (но это хорошая информация). Я подумал, что это затуманит проблему.
Aardvark

Единственная причина, по которой я могу подумать, что это вам понадобится, - это LoadLibrary и GetProcAddress ... Об этом уже позаботились, я объясню в своем теле ответа ...
Джошперри

EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Это в SDK?
Aardvark

3
Не забудьте добавить файл определения модуля в настройки компоновщика проекта - просто «добавить существующий элемент в проект» недостаточно!
Джимми

1
Я использовал это, чтобы скомпилировать DLL с VS, а затем вызвать ее из R с помощью .C. Большой!
Juancentro

33

Для C ++:

Я только что столкнулся с той же проблемой, и я думаю, что стоит упомянуть, что проблема возникает, когда вы используете оба __stdcall(или WINAPI) и extern "C" :

Как известно extern "C"убирает украшение так, чтобы вместо:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

вы получите имя символа без украшений:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

Однако _stdcall(= макрос WINAPI, который изменяет соглашение о вызовах) также украшает имена, так что, если мы используем оба, мы получаем:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

и преимущество extern "C"теряется, потому что символ украшен (_ @bytes)

Обратите внимание, что это происходит только для архитектуры x86, потому что __stdcallсоглашение игнорируется в x64 ( msdn : на архитектурах x64 по соглашению аргументы передаются в регистрах, когда это возможно, а последующие аргументы передаются в стек ).

Это особенно сложно, если вы ориентируетесь как на платформы x86, так и на x64.


Два решения

  1. Используйте файл определения. Но это заставляет вас поддерживать состояние файла def.

  2. Самый простой способ: определить макрос (см. msdn ):

#define EXPORT comment (компоновщик, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

а затем включите следующую прагму в тело функции:

#pragma EXPORT

Полный пример:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

Это экспортирует функцию без декорирования для целей x86 и x64 с сохранением __stdcallсоглашения для x86. В этом случае __declspec(dllexport) не требуется.


5
Спасибо за этот важный намек. Я уже задавался вопросом, почему моя 64-битная DLL отличается от 32-битной. Я считаю ваш ответ гораздо более полезным, чем тот, который был принят в качестве ответа.
Elmue

1
Мне очень нравится такой подход. Моя единственная рекомендация - переименовать макрос в EXPORT_FUNCTION, потому что __FUNCTION__макрос работает только в функциях.
Луис

3

У меня была точно такая же проблема, моим решением было использование файла определения модуля (.def) вместо __declspec(dllexport)определения экспорта ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Понятия не имею, почему это работает, но работает


Примечание для всех, кто сталкивается с этим: использование .defфайла экспорта модуля действительно работает, но за счет возможности предоставить externопределения в файле заголовка, например, для глобальных данных - в этом случае вы должны предоставить externопределение вручную при внутреннем использовании эти данные. (Да, бывают случаи, когда вам это нужно.) Как в целом, так и особенно для кроссплатформенного кода, лучше просто использовать __declspec()макрос, чтобы вы могли нормально передавать данные.
Крис Кричо

2
Причина видимо в том, что если пользуешься __stdcall, то украшения снимать не__declspec(dllexport) будешь . Однако добавление функции к завещанию. .def
Бьорн Линдквист,

1
@ BjörnLindqvist +1, обратите внимание, что это только для x86. Смотрите мой ответ.
Малик

-1

Я думаю, что _naked может получить то, что вы хотите, но он также не позволяет компилятору генерировать код управления стеком для функции. extern «C» вызывает оформление имени в стиле C. Удалите это, и это должно избавить вас от ваших _. Компоновщик не добавляет подчеркивания, а компилятор. stdcall вызывает добавление размера стека аргументов.

Для получения дополнительной информации см. Http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx.

Более важный вопрос - почему вы хотите это сделать? Что не так с искореженными именами?


Искаженные имена уродливы, когда вызываются с использованием LoadLibrary / GetProcAddress или других методов, которые не полагаются на наличие заголовка ac / c ++.
Aardvark

4
Это было бы бесполезно - вы хотите удалить код управления стеком, созданный компилятором, только в очень особых случаях. (Простое использование __cdecl было бы менее вредным способом потерять украшения - по умолчанию __declspec (dllexport), похоже, не включает обычный префикс _ с методами __cdecl.)
Ян Гриффитс

На самом деле я не говорил, что это будет полезно, поэтому мои предостережения по поводу других эффектов и сомнения, почему он вообще хотел это сделать.
Роб К
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.