Експорт функцій з DLL з dllexport


105

Я хотів би простий приклад експорту функції з DLL C ++ 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"), мені надаються назви експорту, як мені подобається, але що це означає? Яка б умова виклику за замовчуванням я отримував те, що підключення (.NET), декларування (vb6), і GetProcAddressщо очікував? (Я думаю, для GetProcAddressцього це залежало б від вказівника функції, який викликав абонент).

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

Я все в порядку, відповідь полягає в тому, що я повинен використовувати *.defфайл.


Я можу неправильно запам’ятати, але я думаю, що: а) extern Cвидалить декор, який описує типи параметрів функції, але не декор, який описує умову виклику функції; б) для видалення всіх прикрас, які потрібно вказати (незабарвлене) ім'я у файлі DEF.
ChrisW

Це я і бачив. Можливо, ви повинні додати це як повноцінна відповідь?
Aardvark

Відповіді:


134

Якщо ви хочете простий експорт C, використовуйте проект C, а не C ++. DLL-адреси C ++ покладаються на керування іменами для всіх C ++ -ів (простори імен тощо). Ви можете скласти свій код як C, зайшовши в налаштування свого проекту в розділі C / C ++ -> Advanced, є опція "Compile As", яка відповідає перемикачам компілятора / TP та / TC.

Якщо ви все ще хочете використовувати C ++ для написання внутрішніх даних вашої lib, але експортувати деякі функції без розблокування для використання поза C ++, див. Другий розділ нижче.

Експорт / імпорт DLL Libs у 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 / експортом:

  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
зовнішнє "C", здавалося, видалило назву стилю c ++ mangling. Весь предмет про імпорт та експорт (який я намагався запропонувати не включати у запитання) насправді не про те, про що я прошу (але його хороша інформація). Я подумав, що це усуне проблему.
Aardvark

Єдина причина, по якій я можу подумати, що вам це знадобиться, це LoadLibrary та GetProcAddress ... Про це вже подбали, я викладу своє тіло відповідей ...
joshperry

EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Це в СДК?
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"втрачається через те, що символ прикрашений (з _ @ байтами)

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

Це особливо складно, якщо ви орієнтуєтесь як на платформи x86, так і на x64.


Два рішення

  1. Використовуйте файл визначення. Але це змушує вас підтримувати стан файлу def.

  2. найпростіший спосіб: визначити макрос (див. msdn ):

#define EXPORT коментар (посилання, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

а потім включіть таку прагму в тіло функції:

#pragma EXPORT

Повний приклад:

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

Це дозволить експортувати функцію незадекларованої як для цілей x86, так і для x64, зберігаючи __stdcallумову для x86. У цьому випадку __declspec(dllexport) це не потрібно.


5
Дякую за цей важливий натяк. Я вже задався питанням, чому мій 64Bit DLL відрізняється від 32-бітного. Я вважаю вашу відповідь набагато кориснішою, ніж відповідь, прийнята як відповідь.
Елмуе

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 може отримати те, що ви хочете, але це також заважає компілятору генерувати код управління стеком для функції. Зовнішній вигляд "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.)
Ian Griffiths

Я насправді не казав, що це буде корисно, тому мої застереження щодо інших наслідків і питання, чому він навіть хотів це зробити.
Роб К
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.