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 8

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-клиент.
Вот что я получаю на своем компьютере:

Вызов функции DLL в действии

Он работает без модификаций и с именем функции 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++, но не является значением по умолчанию для управляемого кода.