Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

x64 DLL export function names

I am trying to port a 32-bit dll (and application) to 64-bit and I have managed to build it without errors. When trying to load it with my 64-bit application I noticed that the exported function names differ. This is how I export the functions:

#ifdef __cplusplus
extern "C" {
#endif

__declspec(dllexport) long __stdcall Connect(char * name, long size);

#ifdef __cplusplus 
}
#endif

In Dependency Walker the exported functions have the following format:

32-bit: _Connect@8

64-bit: Connect

In the application using the dll I explicitly load the dll (LoadLibrary is successful) but GetProcAddress fails for 64-bit because it cannot find a function with the provided name.

In our application I keep the function names as follows:

#define ConnectName "_Connect@8"
...
GetProcAddress(Dll, ConnectName);

So I was wondering if it is possible to export the same function names for both 32-bit and 64-bit dlls or is this a bad idea? Or do I need to do the following in my applications:

#if _WIN64
#define ConnectName "Connect"
#else
#define ConnectName "_Connect@8"
#endif

I appreciate any help.

like image 308
dbostream Avatar asked Jan 21 '15 08:01

dbostream


People also ask

How do I see exported functions in a DLL?

The exports table of a DLL can be viewed by using the DUMPBIN tool with the /EXPORTS option. You can export functions from a DLL using two methods: Create a module definition (. def) file and use the .

What does DLL export do?

The dllexport and dllimport storage-class attributes are Microsoft-specific extensions to the C and C++ languages. You can use them to export and import functions, data, and objects to or from a DLL.

What does __ Declspec Dllexport do?

__declspec(dllexport) adds the export directive to the object file so you do not need to use a . def file. This convenience is most apparent when trying to export decorated C++ function names.

How do I export a class from a DLL?

Use __declspec(dllexport) to export the function or method. You can export an entire C++ class by placing the __declspec(dllexport) before the class name, or you can export a single method by placing __declspec(dllexport) before the method name. Make class methods static and public.


1 Answers

An option you have to export function names without any decoration (independently from the particular calling convention you used in x86, __stdcall, __cdecl, or other) and with the same undecorated name in both x86 and x64 builds, is to export your DLL functions using DEF files.

E.g. you could add a .DEF file like this to your project:

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

Repro Follows

Create an empty solution in Visual Studio (I used VS2013), and inside that create an empty Win32 console project (the test client) and an empty Win32 DLL project (the test DLL).

Add this NativeDll.def .DEF file in the DLL project:

LIBRARY NATIVEDLL
EXPORTS
    SayHello @1

Add this NativeDll.cpp C++ source code in the DLL project:

///////////////////////////////////////////////////////////////////////////////
// 
// 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;
    }
}

Add this NativeClient.cpp C++ source code in the client test project:

///////////////////////////////////////////////////////////////////////////////
//
// 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;
}

Build the whole solution (both the .EXE and the .DLL) and run the native .EXE client.
This is what I get on my computer:

The DLL Function Call in Action

It works without modifications and with the undecorated function name (just SayHello) on both x86 and x64 builds.

like image 190
Mr.C64 Avatar answered Sep 27 '22 17:09

Mr.C64