Экспорт функций из библиотеки DLL с помощью dllexport
-
22-08-2019 - |
Вопрос
Я хотел бы получить простой пример экспорта функции из C ++ Windows DLL.
Я хотел бы увидеть заголовок, файл 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.
Решение
Если вам нужен обычный экспорт C, используйте проект C, а не C ++.Библиотеки DLL C ++ полагаются на изменение имен для всех измов C ++ (пространств имен и т.д. ).Вы можете скомпилировать свой код как C, зайдя в настройки вашего проекта в разделе C / C ++-> Дополнительно, там есть опция "Скомпилировать как", которая соответствует переключателям компилятора / TP и /TC.
Экспорт / Импорт 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 как nothing, если не в 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 вы можете использовать extern "C"
встроенный в ваш dllexport.И поскольку мы используем GetProcAddress вместо dllimport, нам не нужно выполнять ifdef dance сверху, достаточно простого 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")
);
Другие советы
Для 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.
Два решения
Используйте файл определения.Но это вынуждает вас поддерживать состояние файла def.
самый простой способ :определите макрос (см. msdn) :
#определить комментарий К ЭКСПОРТУ(компоновщик, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
а затем включите следующую прагму в тело функции:
#pragma EXPORT
Полный Пример :
int WINAPI Test(void)
{
#pragma EXPORT
return 1;
}
Это позволит экспортировать функцию без изменений как для целевых объектов x86, так и для x64, сохраняя при этом __stdcall
соглашение для x86.В __declspec(dllexport)
не является требуется в данном случае.
У меня была точно такая же проблема, мое решение состояло в том, чтобы использовать файл определения модуля (.def) вместо __declspec(dllexport)
для определения экспорта(http://msdn.microsoft.com/en-us/library/d91k01sh.aspx).Я понятия не имею, почему это работает, но это так
Я думаю, что _naked может получить то, что вы хотите, но это также не позволяет компилятору генерировать код управления стеком для функции.внешняя буква "C" вызывает оформление имени стиля C.Удалите это, и это должно избавить вас от ваших _'s .Компоновщик не добавляет символы подчеркивания, это делает компилятор.stdcall приводит к добавлению размера стека аргументов.
Подробнее см.:http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx
Более важный вопрос заключается в том, почему вы хотите это сделать?Что не так с искаженными именами?