x64 DLL导出函数名称

时间:2015-01-21 08:16:06

标签: c++ dll 64-bit name-mangling

我正在尝试将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

我感谢任何帮助。

4 个答案:

答案 0 :(得分:7)

您必须导出函数名称而不进行任何修饰(独立来自您在x86中使用的特定调用约定,__stdcall__cdecl或其他)以及x86和x64版本中相同的未修饰名称是使用DEF files导出DLL函数。

E.g。你可以将这样的.DEF文件添加到你的项目中:

LIBRARY YOURDLL
EXPORTS
   Connect          @1
   AnotherFunction  @2
   ... etc. ...   

Repro关注

在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客户端 这是我在电脑上得到的:

The DLL Function Call in Action

x86和x64 x86和x64 构建

答案 1 :(得分:3)

正如您所知,在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 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:由于OP在评论中提到使用PInvoke - 如果有人决定这样做,不要忘记在使用PInvoke时在CallingConvention = CallingConvention.Cdecl声明中添加[DllImport]__cdecl可能是非托管C / C ++的默认值,但不是托管代码的默认值。

答案 2 :(得分:3)

x64上不支持(并忽略)

__stdcall。引用MSDN

  

在ARM和x64处理器上,编译器接受并忽略 __ stdcall ;在ARM和x64体系结构上,按照惯例,参数尽可能在寄存器中传递,后续参数在栈上传递。

x64上的调用约定是pretty much __fastcall

由于x86和x64上的调用约定和名称修饰规则不同,因此必须以某种方式对其进行抽象。所以#if _WIN64的想法朝着正确的方向发展。

您可以检查x86调用约定和您的需求,并可能设计一个可以自动执行名称选择过程的宏。

答案 3 :(得分:0)

对于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项目中。 /DEF:"XXX.def"窗口中的链接器命令行All Options会静默添加一个链接选项。而且,如果以后由于任何原因更改了.def文件名,此链接选项也不会相应更改。您需要在项目属性窗口中手动更改def文件名。