2015-01-21 13 views
8

Ich versuche, eine 32-Bit-DLL (und Anwendung) auf 64-Bit zu portieren, und ich habe es geschafft, es ohne Fehler zu bauen. Beim Versuch, es mit meiner 64-Bit-Anwendung zu laden, bemerkte ich, dass die exportierten Funktionsnamen sich unterscheiden. Dies ist, wie ich die Funktionen exportieren:x64 DLL-Exportfunktionsnamen

#ifdef __cplusplus 
extern "C" { 
#endif 

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

#ifdef __cplusplus 
} 
#endif 

In Dependency Walker die exportierten Funktionen haben folgendes Format:

32-Bit: [email protected]

64-Bit: Connect

In der Anwendung mit der DLL Ich lade explizit die DLL (LoadLibrary ist erfolgreich), aber GetProcAddress scheitert für 64-Bit, weil es eine Funktion mit dem angegebenen Namen nicht finden kann.

In unserer Anwendung behalte ich die Funktionsnamen wie folgt:

#define ConnectName "[email protected]" 
... 
GetProcAddress(Dll, ConnectName); 

Also ich frage mich, ob es möglich ist, für beide die gleichen Funktionsnamen zu exportieren 32-Bit- und 64-Bit-DLLs oder ist dies ein schlechte Idee? Oder muss ich in meinen Anwendungen Folgendes tun:

#if _WIN64 
#define ConnectName "Connect" 
#else 
#define ConnectName "[email protected]" 
#endif 

Ich freue mich über jede Hilfe.

Antwort

7

Eine Option, die Sie Funktionsnamen zu exportieren haben ohne Dekoration (unabhängig aus der jeweiligen Aufrufkonvention Sie in x86 verwendet, __stdcall, __cdecl oder andere) und mit den gleichen undecorated Namen sowohl x86 und x64 baut , exportieren Sie Ihre DLL-Funktionen mit DEF files.

z.

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

Repro Folgt

Erstellen Sie eine leere Lösung in Visual Studio (I verwendet VS2013) und im Inneren, die eine leere erstellen: Sie könnten eine DEF-Datei wie diese zu Ihrem Projekt hinzufügen Win32-Konsolenprojekt (Testclient) und ein leeres Win32-DLL-Projekt (Test DLL).

Fügen Sie diese NativeDll.defhinzu.Datei DEF im DLL-Projekt:

LIBRARY NATIVEDLL 
EXPORTS 
    SayHello @1 

Dieses NativeDll.cpp C++ Quellcode im DLL-Projekt:

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

Dieses NativeClient.cpp C++ Quellcode im Client Testprojekt :

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

Erstellen Sie die gesamte Lösung (sowohl die EXE als auch die DLL), und führen Sie den systemeigenen EXE-Client aus.
Dies ist, was ich auf meinem Computer zu erhalten:

The DLL Function Call in Action

Es funktioniert ohne Modifikationen und mit dem Namen undecorated Funktion (nur SayHello) auf beide x86 und x64 bauen.

+0

Also wenn ich das richtig verstehe, müsste ich das nur zu den dll-projekten hinzufügen und dann Meine Anwendungen und C# PInvoke mit den DLLs funktionieren ohne Änderungen? Wenn ja, gibt es Nachteile gegenüber den anderen vorgeschlagenen Lösungen? – dbostream

+0

Ok ich sehe, danke für die Eingabe. – dbostream

+0

@dbostream: Um Funktionen mit reinen C-Schnittstellen aus nativer C++ - DLL zu exportieren, finde ich .DEF-Dateien praktisch, um _undecorated_ Funktionsnamen zu erhalten. –

2

Wie Sie sehen können, sind in 64-Bit-Windows-Namen nicht verziert.

In den 32-Bit-Symbolen __cdecl und __stdcall wird dem Symbolnamen ein Unterstrich vorangestellt. Der nachgestellte '@ 8' im exportierten Namen für die 32-Bit-Version Ihrer Beispielfunktion ist die Anzahl der Bytes in der Parameterliste. Es ist da, weil Sie __stdcall angegeben haben. Wenn Sie die Aufrufkonvention __cdecl (die Standardeinstellung für C/C++ - Code) verwenden, erhalten Sie das nicht. Wenn Sie __cdecl verwenden, macht es viel einfacher zu wickeln GetProcAddress() mit so etwas wie:

#if _WIN64 
#define DecorateSymbolName(s) s 
#else 
#define DecorateSymbolName(s) "_" ## s 
#endif 

dann rufen Sie einfach mit

pfnConnect = GetProcAddress(hDLL, DecorateSymbolName("Connect")); 
pfnOtherFunc = GetProcAddress(hDLL, DecorateSymbolName("OtherFunc")); 

oder etwas ähnliches (Fehler in Beispiel weggelassen Prüfung). Dazu merken Sie sich Ihre exportierten Funktionen zu erklären:

__declspec(dllexport) long __cdecl Connect(char * name, long size); 
__declspec(dllexport) long __cdecl OtherFunc(int someValue); 

Neben leichter zu halten, wenn die Signatur einer exportierten Funktion Änderungen während der Entwicklung, müssen Sie mit Ihrem #define nicht rumschraube Wrapper.

Nachteil: Wenn sich während der Entwicklung die Anzahl der Bytes in der Parameterliste einer bestimmten Funktion ändert, wird sie nicht von der Anwendung beim Importieren der Funktion abgefangen, da die Änderung der Signatur den Namen nicht ändert. Persönlich glaube ich nicht, dass dies ein Problem ist, da der 64-Bit-Build unter den gleichen Umständen sowieso explodieren würde, da die Namen nicht dekoriert sind. Sie müssen nur sicherstellen, dass Ihre Anwendung die richtige Version der DLL verwendet.

Wenn der Benutzer der DLL C++ verwendet, können Sie Dinge besser mit C++ - Funktionen (Umhüllen der gesamten explizit geladenen Bibliothek in einer Wrapper-Klasse, z.):

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

Es gibt tatsächlich viel mehr können Sie mit einer Wrapper-Klasse wie dies zu tun, es ist nur ein Beispiel.

Auf edit: da OP mit PInvoke in den Kommentaren erwähnt - wenn jemand dies zu tun entscheidet, vergessen Sie nichtCallingConvention = CallingConvention.Cdecl in der [DllImport] Erklärung hinzuzufügen, wenn PInvoke verwenden. __cdecl ist möglicherweise der Standardwert für nicht verwaltetes C/C++, ist jedoch nicht der Standardwert für verwalteten Code.

+0

Danke Ich mag diese Idee, eine Frage obwohl. Könnte die Änderung zu "__cdecl" irgendwelche Nebenwirkungen auf die Software haben, die die DLLs verwendet? Wir haben mehrere DLLs und Anwendungen in unserer Tool-Suite, die geändert werden müssten, da wir überall stdcall verwenden. Auch haben wir C# dlls, dass PInvoke die nicht verwalteten dlls (derzeit mit stdcall) ist es nur eine Frage der Änderung der Aufrufkonvention zu cdecl oder wird es andere Probleme jetzt, dass die exportierten Namen unterscheiden sich bei der Verwendung von 32-Bit und 64-Bit. – dbostream

+0

Wenn Sie die Headerdateien ändern, die die neuen Deklarationen für die exportierten Funktionen enthalten, und die DLL neu erstellen, müssen Sie nur alles neu erstellen, das diese exportierten Funktionen verwendet, so dass auch die neue Aufrufkonvention verwendet wird. Wenn alle auf der gleichen Seite sind, rufen Sie konventions-weise an, es sollte Ihnen gut gehen. – frasnian

+0

Ich habe die Connect-Funktion in '__cdecl' geändert und die Verwendung von Dependency Walker zeigt jetzt denselben Namen für 32-Bit- und 64-Bit-DLLs, nämlich _Connect_. Wenn ich [link] (https://msdn.microsoft.com/en-us/library/zkwh89ks.aspx) richtig verstehe, bekomme ich keinen vorangestellten Unterstrich, weil ich 'extern" C "'; also brauche ich 'DecorateSymbolName' nicht. Scheint das vernünftig oder habe ich etwas falsch gemacht? – dbostream

0

__stdcall wird auf x64 nicht unterstützt (und wird ignoriert). Zitiert MSDN:

auf ARM und x64-Prozessoren, __stdcall akzeptiert und vom Compiler ignoriert; Bei ARM- und x64-Architekturen werden Argumente nach Möglichkeit nach Möglichkeit in Registern übergeben, und nachfolgende Argumente werden auf dem Stapel übergeben.

Die Aufrufkonvention für x64 ist pretty much __fastcall.

Da sich die Aufrufkonventionen und Namensdekorationsregeln auf x86 und x64 unterscheiden, müssen Sie dies irgendwie abstrahieren. So geht deine Idee mit #if _WIN64 in die richtige Richtung.

Sie können x86-Aufrufkonventionen und Ihre Bedürfnisse untersuchen und vielleicht ein Makro entwickeln, das den Namenauswahlprozess automatisieren könnte.