64-разрядных имен экспортировать функцию из DLL
Я пытаюсь портировать 32-битную dll (и приложение) на 64-битную, и мне удалось построить ее без ошибок. При попытке загрузить его с моим 64-битным приложением я заметил, что экспортированные имена функций отличаются. Вот как я экспортирую функции:
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) long __stdcall Connect(char * name, long size);
#ifdef __cplusplus
}
#endif
В Dependency Walker экспортируемые функции имеют следующий формат:
32-бит: _Connect@8
64-бит: Connect
В приложении, использующем dll, я явно загружаю dll (LoadLibrary успешно), но GetProcAddress завершается ошибкой для 64-разрядной версии, так как он не может найти функцию с указанным именем.
В нашем приложении я сохраняю имена функций следующим образом:
#define ConnectName "_Connect@8"
...
GetProcAddress(Dll, ConnectName);
Итак, мне было интересно, можно ли экспортировать одинаковые имена функций для 32-разрядных и 64-разрядных библиотек DLL или это плохая идея? Или мне нужно сделать следующее в моих приложениях:
#if _WIN64
#define ConnectName "Connect"
#else
#define ConnectName "_Connect@8"
#endif
Я ценю любую помощь.3 ответа:
Возможность экспортировать имена функций без каких-либо украшений (независимо от конкретного соглашения о вызовах, используемого в x86,
__stdcall
,__cdecl
, или другой) и с тем же недекорированным именем как в x86, так и в x64 сборках, является экспорт ваших функций DLL с помощью DEF файлы.Например, вы можете добавить a .DEF-файл для вашего проекта выглядит следующим образом:
LIBRARY YOURDLL EXPORTS Connect @1 AnotherFunction @2 ... etc. ...
Упрек Следует
Создайте пустое решение в Visual Studio (я использовал VS2013), а внутри этого создать пустой проект консоли Win32 (тестовый клиент ) и пустой проект библиотеки DLL Win32 (тестовая Библиотека ).
Добавьте это
NativeDll.def
.DEF-файл в проекте DLL:LIBRARY NATIVEDLL EXPORTS SayHello @1
Добавьте этот исходный код
NativeDll.cpp
C++ в проект DLL ://///////////////////////////////////////////////////////////////////////////// // // NativeDll.cpp -- DLL Implementation Code // /////////////////////////////////////////////////////////////////////////////// #include <Windows.h> #include <atldef.h> #include <atlstr.h> // // Test function exported from the DLL // extern "C" HRESULT WINAPI SayHello(PCWSTR name) { // // Check for null input string pointer // if (name == nullptr) { return E_POINTER; } try { // // Build a greeting message and show it in a message box // CString message; message.Format(L"Hello %s from the native DLL!", name); MessageBox(nullptr, message, L"Native DLL Test", MB_OK); // All right return S_OK; } // // Catch exceptions and convert them to HRESULT codes // catch (const CAtlException& ex) { return static_cast<HRESULT>(ex); } catch (...) { return E_FAIL; } }
Добавьте этот
NativeClient.cpp
исходный код C++ в клиентский тестовый проект ://///////////////////////////////////////////////////////////////////////////// // // NativeClient.cpp -- EXE Test Client Code // /////////////////////////////////////////////////////////////////////////////// #include <Windows.h> // // Prototype of the function to be loaded from the DLL // typedef HRESULT (WINAPI *SayHelloFuncPtr)(PCWSTR /* name */); // // Simple RAII wrapper on LoadLibrary()/FreeLibrary(). // class ScopedDll { public: // // Load the DLL // ScopedDll(PCWSTR dllFilename) throw() : m_hDll(LoadLibrary(dllFilename)) { } // // Unload the DLL // ~ScopedDll() throw() { if (m_hDll) { FreeLibrary(m_hDll); } } // // Was the DLL loaded successfully? // explicit operator bool() const throw() { return (m_hDll != nullptr); } // // Get the DLL handle // HINSTANCE Get() const throw() { return m_hDll; } // // *** IMPLEMENTATION *** // private: // // The wrapped raw DLL handle // HINSTANCE m_hDll; // // Ban copy // private: ScopedDll(const ScopedDll&) = delete; ScopedDll& operator=(const ScopedDll&) = delete; }; // // Display an error message box // inline void ErrorMessage(PCWSTR errorMessage) throw() { MessageBox(nullptr, errorMessage, L"*** ERROR ***", MB_OK | MB_ICONERROR); } // // Test code calling the DLL function via LoadLibrary()/GetProcAddress() // int main() { // // Return codes // static const int kExitOk = 0; static const int kExitError = 1; // // Load the DLL with LoadLibrary(). // // NOTE: FreeLibrary() automatically called thanks to RAII! // ScopedDll dll(L"NativeDll.dll"); if (!dll) { ErrorMessage(L"Can't load the DLL."); return kExitError; } // // Use GetProcAddress() to access the DLL test function. // Note the *undecorated* "SayHello" function name!! // SayHelloFuncPtr pSayHello = reinterpret_cast<SayHelloFuncPtr>(GetProcAddress(dll.Get(), "SayHello")); if (pSayHello == nullptr) { ErrorMessage(L"GetProcAddress() failed."); return kExitError; } // // Call the DLL test function // HRESULT hr = pSayHello(L"Connie"); if (FAILED(hr)) { ErrorMessage(L"DLL function call returned failure HRESULT."); return kExitError; } // // All right // return kExitOk; }
Построить целое решение (оба .Exe и .DLL) и запустить native .EXE-клиент.
Вот что я получаю на своем компьютере:Он работает без модификаций и с именем функции undecorated (просто
SayHello
) на как x86, так и x64 строит .
__stdcall
не поддерживается (и игнорируется) на x64. Цитирование MSDN:На процессорах ARM и x64, __stdcall принимается и игнорируется компилятором; на архитектурах ARM и x64, по соглашению, аргументы передаются в регистрах, когда это возможно, и последующие аргументы передаются в стек.
Соглашение о вызовах на x64-это в значительной степени
__fastcall
.Поскольку соглашения о вызовах и правила оформления имен на x86 и x64 различайтесь, вы должны как-то абстрагироваться от этого. Так что ваша идея с
#if _WIN64
идет в правильном направлении.Вы можете изучить соглашения о вызовах x86 и свои потребности и, возможно, разработать макрос, который мог бы автоматизировать процесс выбора имени.
Как вы можете сказать, в 64-битной Windows имена не оформлены.
В 32-разрядных символах
__cdecl
и__stdcall
имя символа предваряется символом подчеркивания. Конечное число '@8' в экспортированном имени для 32-разрядной версии функции примера - это число байт в списке параметров. Он есть, потому что вы указали__stdcall
. Если вы используете соглашение о вызове__cdecl
(по умолчанию для кода C/C++), вы этого не получите. Если вы используете__cdecl
, это делает его гораздо легче обернутьGetProcAddress()
с что-то вроде:#if _WIN64 #define DecorateSymbolName(s) s #else #define DecorateSymbolName(s) "_" ## s #endif
Тогда просто позвоните с
pfnConnect = GetProcAddress(hDLL, DecorateSymbolName("Connect")); pfnOtherFunc = GetProcAddress(hDLL, DecorateSymbolName("OtherFunc"));
Или что-то подобное (проверка ошибок опущена в Примере). Для этого не забудьте объявить экспортируемые функции как:
__declspec(dllexport) long __cdecl Connect(char * name, long size); __declspec(dllexport) long __cdecl OtherFunc(int someValue);
В дополнение к тому, что его легче поддерживать, если во время разработки сигнатура экспортируемой функции изменяется, вам не нужно возиться с вашими
#define
оболочками.Недостаток: если в процессе разработки изменяется количество байтов в списке параметров данной функции, то это не будет пойман приложением, импортирующим функцию, потому что изменение подписи не изменит имя. Лично я не думаю, что это проблема, потому что 64-битная сборка взорвалась бы при тех же обстоятельствах, так как имена не украшены. Вы просто должны убедиться,что ваше приложение использует правильную версию библиотеки DLL.
Если пользователь библиотеки DLL использует C++, вы можете обернуть вещи лучшим образом, используя возможности C++ (обернуть весь явно загруженная библиотека в классе-оболочке, например):
class MyDLLWrapper { public: MyDLLWrapper(const std::string& moduleName); // load library here ~MyDLLWrapper(); // free library here FARPROC WINAPI getProcAddress(const std::string& symbolName) const { return ::GetProcAddress(m_hModule, decorateSymbolName(symbolName)); } // etc., etc. private: HMODULE m_hModule; // etc. // ... };
На самом деле с таким классом-оболочкой можно сделать гораздо больше, это просто пример.
On edit: поскольку ОП упоминал использование PInvoke в комментариях - если кто-то решит это сделать, не забудьте добавить
CallingConvention = CallingConvention.Cdecl
в объявление[DllImport]
при использовании PInvoke.__cdecl
может быть значением по умолчанию для неуправляемого C / C++, но не является значением по умолчанию для управляемого кода.