x64 DLL 导出函数名称
x64 DLL export function names
我正在尝试将 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
感谢任何帮助。
如您所知,在 64 位 Windows 中名称没有修饰。
在 32 位 __cdecl
和 __stdcall
符号中,符号名称前面有一个下划线。示例函数的 32 位版本的导出名称中尾随的“@8”是参数列表中的字节数。它在那里是因为您指定了 __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,例如):
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.
// ...
};
实际上,您可以像这样使用包装器 class 做很多事情,这只是一个示例。
关于编辑:因为 OP 在评论中提到使用 PInvoke - 如果有人决定这样做,不要忘记 在 [=22= 中添加 CallingConvention = CallingConvention.Cdecl
] 使用 PInvoke 时的声明。 __cdecl
可能是非托管 C/C++ 的默认值,但不是托管代码的默认值。
__stdcall
在 x64 上不受支持(并被忽略)。引用 MSDN:
On ARM and x64 processors, __stdcall is accepted and ignored by the compiler; on ARM and x64 architectures, by convention, arguments are passed in registers when possible, and subsequent arguments are passed on the stack.
x64 上的调用约定是 pretty much __fastcall
。
由于 x86 和 x64 上的调用约定和名称修饰规则不同,因此您必须以某种方式对此进行抽象。所以你对 #if _WIN64
的想法是正确的。
您可以检查 x86 调用约定和您的需求,或许可以设计一个可以自动执行名称选择过程的宏。
一个选项,你必须导出没有任何装饰的函数名称(独立地你在 x86 中使用的特定调用约定,__stdcall
,__cdecl
,或其他)并在 x86 和 x64 版本中使用 相同的未修饰名称 ,是使用 DEF files.
导出 DLL 函数
例如您可以将这样的 .DEF 文件添加到您的项目中:
LIBRARY YOURDLL
EXPORTS
Connect @1
AnotherFunction @2
... etc. ...
复制关注
在 Visual Studio(我使用 VS2013)中创建一个空解决方案,并在其中创建一个空的 Win32 控制台项目(测试客户端)和一个空的 Win32 DLL项目(测试 DLL)。
在DLL项目中添加这个NativeDll.def
.DEF文件:
LIBRARY NATIVEDLL
EXPORTS
SayHello @1
在 DLL 项目中添加此 NativeDll.cpp
C++ 源代码:
///////////////////////////////////////////////////////////////////////////////
//
// 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)和 运行 本机 .EXE 客户端。
这是我在电脑上得到的:
它可以无需修改并且在未修饰函数名(只是SayHello
) =42=] x86 和 x64 版本。
对于 Win32 版本:
如果你使用 __stdcall
,你会得到这样的东西(用 dumpbin /exports
转储):
__declspec(dllexport) int __stdcall
->
ordinal hint RVA name
1 0 00001240 _F1@0 = _F1@0
2 1 0000124D _F2@0 = _F2@0
并且您必须使用GetProcAddress("_F1@0")
定位函数指针。
如果你使用__cdecl
,你会得到这样的东西:
__declspec(dllexport) int __cdecl
->
ordinal hint RVA name
1 0 00001240 F1 = _F1
2 1 0000124D F2 = _F2
并且可以使用GetProcAddress("F1")
定位函数指针。
顺便说一句,如果您将 XXX.def
文件添加到您的 Visual Studio 项目。另一个 link 选项将悄悄添加到 All Options
window 中的 linker 命令行 /DEF:"XXX.def"
。如果您以后出于某种原因更改 .def
文件名,则此 link 选项不会相应更改。您需要在项目属性中手动更改 def 文件名 window.
我正在尝试将 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
感谢任何帮助。
如您所知,在 64 位 Windows 中名称没有修饰。
在 32 位 __cdecl
和 __stdcall
符号中,符号名称前面有一个下划线。示例函数的 32 位版本的导出名称中尾随的“@8”是参数列表中的字节数。它在那里是因为您指定了 __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,例如):
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.
// ...
};
实际上,您可以像这样使用包装器 class 做很多事情,这只是一个示例。
关于编辑:因为 OP 在评论中提到使用 PInvoke - 如果有人决定这样做,不要忘记 在 [=22= 中添加 CallingConvention = CallingConvention.Cdecl
] 使用 PInvoke 时的声明。 __cdecl
可能是非托管 C/C++ 的默认值,但不是托管代码的默认值。
__stdcall
在 x64 上不受支持(并被忽略)。引用 MSDN:
On ARM and x64 processors, __stdcall is accepted and ignored by the compiler; on ARM and x64 architectures, by convention, arguments are passed in registers when possible, and subsequent arguments are passed on the stack.
x64 上的调用约定是 pretty much __fastcall
。
由于 x86 和 x64 上的调用约定和名称修饰规则不同,因此您必须以某种方式对此进行抽象。所以你对 #if _WIN64
的想法是正确的。
您可以检查 x86 调用约定和您的需求,或许可以设计一个可以自动执行名称选择过程的宏。
一个选项,你必须导出没有任何装饰的函数名称(独立地你在 x86 中使用的特定调用约定,__stdcall
,__cdecl
,或其他)并在 x86 和 x64 版本中使用 相同的未修饰名称 ,是使用 DEF files.
例如您可以将这样的 .DEF 文件添加到您的项目中:
LIBRARY YOURDLL
EXPORTS
Connect @1
AnotherFunction @2
... etc. ...
复制关注
在 Visual Studio(我使用 VS2013)中创建一个空解决方案,并在其中创建一个空的 Win32 控制台项目(测试客户端)和一个空的 Win32 DLL项目(测试 DLL)。
在DLL项目中添加这个NativeDll.def
.DEF文件:
LIBRARY NATIVEDLL
EXPORTS
SayHello @1
在 DLL 项目中添加此 NativeDll.cpp
C++ 源代码:
///////////////////////////////////////////////////////////////////////////////
//
// 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)和 运行 本机 .EXE 客户端。
这是我在电脑上得到的:
它可以无需修改并且在未修饰函数名(只是SayHello
) =42=] x86 和 x64 版本。
对于 Win32 版本:
如果你使用 __stdcall
,你会得到这样的东西(用 dumpbin /exports
转储):
__declspec(dllexport) int __stdcall
->
ordinal hint RVA name
1 0 00001240 _F1@0 = _F1@0
2 1 0000124D _F2@0 = _F2@0
并且您必须使用GetProcAddress("_F1@0")
定位函数指针。
如果你使用__cdecl
,你会得到这样的东西:
__declspec(dllexport) int __cdecl
->
ordinal hint RVA name
1 0 00001240 F1 = _F1
2 1 0000124D F2 = _F2
并且可以使用GetProcAddress("F1")
定位函数指针。
顺便说一句,如果您将 XXX.def
文件添加到您的 Visual Studio 项目。另一个 link 选项将悄悄添加到 All Options
window 中的 linker 命令行 /DEF:"XXX.def"
。如果您以后出于某种原因更改 .def
文件名,则此 link 选项不会相应更改。您需要在项目属性中手动更改 def 文件名 window.