2015-01-21 27 views
8

मैं 32-बिट डीएल (और एप्लिकेशन) को 64-बिट पर पोर्ट करने का प्रयास कर रहा हूं और मैंने त्रुटियों के बिना इसे बनाने में कामयाब रहा है। मेरे 64-बिट अनुप्रयोग के साथ इसे लोड करने का प्रयास करते समय मैंने देखा कि निर्यात किए गए फ़ंक्शन नाम अलग-अलग हैं। यह मैं कैसे काम करता है निर्यात है:x64 डीएलएल निर्यात फ़ंक्शन नाम

#ifdef __cplusplus 
extern "C" { 
#endif 

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

#ifdef __cplusplus 
} 
#endif 

निर्भरता वाकर में निर्यात कार्यों निम्न स्वरूप है:

32-बिट: [email protected]

64-बिट: Connect

में डीएलएल का उपयोग करके एप्लिकेशन मैं स्पष्ट रूप से डीएल लोड करता हूं (लोड लाइब्रेरी सफल है) लेकिन GetProcAddress 64-बिट के लिए विफल रहता है क्योंकि यह प्रदान किए गए नाम के साथ कोई फ़ंक्शन नहीं ढूंढ सकता है।

हमारे आवेदन में इस प्रकार मैं फ़ंक्शन नाम रखें:

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

तो अगर यह संभव है 32-बिट और 64-बिट DLLs के लिए एक ही समारोह के नाम निर्यात करने के लिए या यह एक है मैं सोच रहा था बुरा विचार? या मुझे अपने अनुप्रयोगों में निम्नलिखित करने की ज़रूरत है:

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

मैं किसी भी मदद की सराहना करता हूं।

उत्तर

7

आप किसी भी सजावट (स्वतंत्र रूप से विशेष रूप से बुला सम्मेलन आप 86 में प्रयोग किया जाता से, __stdcall, __cdecl, या अन्य) के बिना फ़ंक्शन नाम निर्यात करना होगा एक विकल्प है और दोनों x86 और x64 बनाता में ही असज्जित नाम साथ , DEF files का उपयोग कर अपने डीएलएल कार्यों को निर्यात करना है।

उदा। आप अपने प्रोजेक्ट के लिए इस तरह की एक .def फ़ाइल जोड़ सकते हैं:

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

रेप्रो अनुसरण करता

दृश्य स्टूडियो में एक खाली समाधान (मैं VS2013 प्रयुक्त) बनाएँ, और है कि अंदर एक खाली बनाने Win32 कंसोल प्रोजेक्ट (परीक्षण क्लाइंट) और एक खाली Win32 DLL प्रोजेक्ट (परीक्षण DLL)।

यह NativeDll.defजोड़ें।

LIBRARY NATIVEDLL 
EXPORTS 
    SayHello @1 

DLL परियोजना में इस NativeDll.cpp सी ++ स्रोत कोड जोड़ें:: डीईएफ़ 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 सी ++ स्रोत कोड जोड़ें :

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

यह संशोधनों के बिना और असज्जित समारोह नाम (बस SayHello) पर दोनों x86 और x64 बनाता साथ काम करता है।

+0

इसलिए यदि मैं सही ढंग से समझता हूं तो मुझे केवल इसे डीएल-प्रोजेक्ट में जोड़ना होगा और फिर मेरे अनुप्रयोग और सी # PInvokes डीएलएस का उपयोग कर परिवर्तन के बिना काम करेंगे? यदि अन्य प्रस्तावित समाधानों की तुलना में इस समाधान के लिए कोई डाउनसाइड है? – dbostream

+0

ठीक है, मैं इनपुट के लिए धन्यवाद। – dbostream

+0

@dbostream: मूल सी ++ डीएलएल से शुद्ध सी इंटरफेस के साथ फ़ंक्शंस निर्यात करने के लिए, मुझे _DEecorated_ फ़ंक्शन नाम प्राप्त करने के लिए सुविधाजनक .DEF फ़ाइलें मिलती हैं। –

2

जैसा कि आप बता सकते हैं, 64-बिट विंडोज़ नामों में सजाए गए नहीं हैं।

32-बिट __cdecl और __stdcall प्रतीकों में, प्रतीक नाम अंडरस्कोर द्वारा प्रीपेड किया जाता है। आपके उदाहरण फ़ंक्शन के 32-बिट संस्करण के लिए निर्यात किए गए नाम में पिछला '@ 8' पैरामीटर सूची में बाइट्स की संख्या है। यह वहां है क्योंकि आपने __stdcall निर्दिष्ट किया है। यदि आप __cdecl कॉलिंग कन्वेंशन (सी/सी ++ कोड के लिए डिफ़ॉल्ट) का उपयोग करते हैं, तो आपको वह नहीं मिलेगा।

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

तो बस

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

या कुछ इसी तरह की (त्रुटि उदाहरण में छोड़े गए जाँच) के साथ फोन: आप __cdecl का उपयोग करते हैं, यह बहुत आसान है की तरह कुछ के साथ GetProcAddress() रैप करने के लिए बनाता है। आसान जा रहा है बनाए रखने के लिए, अगर विकास के दौरान एक निर्यात समारोह परिवर्तन के हस्ताक्षर, आप अपने #define के साथ चारों ओर पेंच की जरूरत नहीं है करने के लिए

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

इसके अलावा: ऐसा करने के लिए, के रूप में अपने निर्यात कार्यों घोषित करने के लिए याद रैपर।

डाउनसाइड: यदि विकास के दौरान किसी दिए गए फ़ंक्शन की पैरामीटर सूची में बाइट्स की संख्या में परिवर्तन होता है, तो यह फ़ंक्शन आयात करने वाले एप्लिकेशन द्वारा पकड़ा नहीं जाएगा क्योंकि हस्ताक्षर बदलने से नाम बदल नहीं जाएगा। निजी तौर पर, मुझे नहीं लगता कि यह एक मुद्दा है क्योंकि 64-बिट बिल्ड उसी परिस्थितियों में उड़ाएगा, वैसे भी नामों को सजाया नहीं गया है। आपको बस यह सुनिश्चित करना होगा कि आपका एप्लिकेशन डीएलएल के सही संस्करण का उपयोग कर रहा है।

यदि डीएलएल का उपयोगकर्ता सी ++ का उपयोग कर रहा है, तो आप सी ++ क्षमताओं का उपयोग करके चीजों को बेहतर तरीके से लपेट सकते हैं (पूरे स्पष्ट रूप से लोड लाइब्रेरी को रैपर वर्ग में लपेटें, उदा।):

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

वास्तव में आप इस तरह के एक रैपर वर्ग के साथ बहुत कुछ कर सकते हैं, यह सिर्फ एक उदाहरण है।

संपादित करें पर: के बाद से ओपी टिप्पणी में PInvoke का उपयोग कर उल्लेख किया है - अगर किसी को ऐसा करने के लिए फैसला करता है, मत भूलना जब PInvoke का उपयोग कर [DllImport] घोषणा में CallingConvention = CallingConvention.Cdecl जोड़ने के लिए। __cdecl अप्रबंधित सी/सी ++ के लिए डिफ़ॉल्ट हो सकता है, लेकिन प्रबंधित कोड के लिए डिफ़ॉल्ट नहीं है।

+0

धन्यवाद मुझे यह विचार पसंद है, हालांकि एक सवाल। क्या '__cdecl' में परिवर्तन डीएलएस का उपयोग कर सॉफ़्टवेयर पर कोई दुष्प्रभाव हो सकता है? हमारे टूल सूट में हमारे पास कई डीएलएस और एप्लिकेशन हैं जिन्हें बदलना होगा क्योंकि हम हर जगह stdcall का उपयोग करते हैं। इसके अलावा हमारे पास सी # डीएलएस हैं जो अप्रबंधित डीएलएस (वर्तमान में stdcall का उपयोग कर) को PInvoke है, यह सिर्फ कॉलिंग कन्वेंशन को cdecl में बदलने का मामला है या अब अन्य समस्याएं होंगी कि 32-बिट और 64-बिट का उपयोग करते समय निर्यात किए गए नाम अलग-अलग होंगे। – dbostream

+0

यदि आप निर्यात किए गए कार्यों के लिए नए (अलग-अलग कॉलिंग कन्वेंशन) घोषणाओं और डीएलएल का पुनर्निर्माण करने वाले शीर्षलेख फ़ाइलों को बदलते हैं, तो आपको केवल उन निर्यात कार्यों का उपयोग करने वाली सभी चीज़ों का पुनर्निर्माण करना होगा ताकि वे नए कॉलिंग सम्मेलन का भी उपयोग कर सकें। यदि सभी एक ही पृष्ठ पर हैं, तो कॉल-कन्वेंशन-वार, आपको ठीक होना चाहिए। – frasnian

+0

मैंने कनेक्ट फ़ंक्शन को '__cdecl' में बदल दिया है और निर्भरता वॉकर का उपयोग करके अब 32-बिट और 64-बिट डीएलएस, अर्थात् _Connect_ दोनों के लिए समान नाम दिखाएं। अगर मैं समझता हूं [लिंक] (https://msdn.microsoft.com/en-us/library/zkwh89ks.aspx) सही ढंग से मुझे कोई उपसर्ग अंडरस्कोर नहीं मिलता है क्योंकि मैं 'बाहरी "सी'' का उपयोग करता हूं; इस प्रकार मुझे 'DecorateSymbolName' की आवश्यकता नहीं है। क्या यह उचित लगता है या मैंने कुछ गलत किया है? – dbostream

0

__stdcall x64 पर समर्थित नहीं है (और अनदेखा किया गया है)। MSDN का हवाला देते हुए:

एआरएम और x64 प्रोसेसर पर, __stdcall स्वीकार कर लिया और संकलक द्वारा नजरअंदाज कर दिया जाता है; सम्मेलन द्वारा एआरएम और एक्स 64 आर्किटेक्चर पर, रजिस्ट्रार में तर्क संभव होने पर पारित होते हैं, और बाद के तर्क स्टैक पर पारित होते हैं।

x64 पर कॉलिंग सम्मेलन pretty much __fastcall है।

चूंकि कॉलिंग सम्मेलन और नाम x86 और x64 पर सजावट नियम भिन्न हैं, इसलिए आपको इसे किसी भी तरह से सार करना होगा। तो #if _WIN64 के साथ आपका विचार सही दिशा में चला जाता है।

आप x86 कॉलिंग सम्मेलनों और अपनी आवश्यकताओं की जांच कर सकते हैं और शायद एक मैक्रो तैयार कर सकते हैं जो नाम चयन प्रक्रिया को स्वचालित कर सकता है।

संबंधित मुद्दे