2015-01-21 3 views
8

32 비트 dll (및 응용 프로그램)을 64 비트로 이식하려고하는데 오류없이 빌드 할 수있었습니다. 내 64 비트 응용 프로그램으로로드하려고 할 때 내 보낸 함수 이름이 다르다는 것을 알았습니다. 이것은 내가 기능 내보내는 방법은 다음과 같습니다 종속성 워커에서x64 DLL 내보내기 함수 이름

#ifdef __cplusplus 
extern "C" { 
#endif 

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

#ifdef __cplusplus 
} 
#endif 

을 내 보낸 함수의 형식은 다음과 같습니다

32 비트 : [email protected]

64 비트 다음에 Connect

을 DLL을 사용하는 응용 프로그램을 명시 적으로로드 (LoadLibrary 성공)하지만 GetProcAddress는 제공된 이름의 함수를 찾을 수 없기 때문에 64 비트에 대해 실패합니다. 다음과 같이 우리의 응용 프로그램에서

나는 함수 이름을 유지 :

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

는 32 비트 및 64 비트 DLL을 동일한 함수 이름을 내보낼 수 또는 이것을 경우 궁금 해서요 나쁜 생각? 또는 내 응용 프로그램에서 다음을 수행해야합니까 :

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

감사합니다.

답변

7

당신이 어떤 장식 (독립적으로 당신이 86에서 사용 된 특정 호출 규칙에서, __stdcall, __cdecl, 기타)없이 함수 이름을 내보낼 수있는 옵션을 선택하고 x86 및 x64 빌드 모두에서 같은 장식을 가지지 이름DEF files을 사용하여 DLL 함수를 내보내는 것입니다.

예. 당신은 당신의 프로젝트에이 같은 .DEF 파일을 추가 할 수 있습니다

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

생식은 다음

Visual Studio에서 빈 솔루션을 (내가 VS2013를 사용)를 작성하고, 그 안에 빈을 생성 Win32 콘솔 프로젝트 (테스트 클라이언트) 및 빈 Win32 DLL 프로젝트 (테스트 DLL).

이 내용을 추가하십시오. NativeDll.def.

/////////////////////////////////////////////////////////////////////////////// 
// 
// 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++ 소스 코드를 추가

LIBRARY NATIVEDLL 
EXPORTS 
    SayHello @1 

DLL 프로젝트NativeDll.cpp C++ 소스 코드를 추가는 def DLL 프로젝트 파일 :

/////////////////////////////////////////////////////////////////////////////// 
// 
// 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 모두 빌드 꾸며지지 않은 함수 이름 (단지 SayHello)와 작동합니다.

+0

그래서 내가 올바르게 이해한다면 나는 이것을 dll-projects에 추가하면된다. 내 응용 프로그램과 DLL을 사용하는 C# PInvokes가 변경없이 작동합니까? 그렇다면이 솔루션에 대한 단점이 다른 제안 된 솔루션과 비교하여 있습니까? – dbostream

+0

알겠습니다. 입력 해 주셔서 감사합니다. – dbostream

+0

@dbostream : 순수 C 인터페이스가있는 함수를 기본 C++ DLL에서 내보내려면 .DEF 파일을 사용하여 _undecorated_ 함수 이름을 얻는 것이 편리합니다. –

2

여러분도 알다시피 64 비트 Windows에서는 이름이 장식되어 있지 않습니다.

32 비트 __cdecl__stdcall 기호에서 기호 이름 앞에 밑줄이 표시됩니다. 예제 함수의 32 비트 버전에 대한 내 보낸 이름의 후미 '@ 8'은 매개 변수 목록의 바이트 수입니다. 당신이 __stdcall을 지정했기 때문에 거기에 있습니다. __cdecl 호출 규칙 (C/C++ 코드의 기본값)을 사용하면 알 수 없습니다. 그럼 그냥

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

또는 이와 유사한 (오류가 예를 생략 검사)로 전화

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

: 당신이 __cdecl를 사용하는 경우, 그것은 훨씬 쉽게와 같은 뭔가 GetProcAddress()을 포장 할 수 있습니다. 개발 과정에서 내 보낸 함수 변경의 서명, 당신은 당신의 #define 주위에 나사를하지 않는 경우에, 더 쉽게 유지 관리 할 수있을뿐 아니라

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

: 이렇게하려면로 내 보낸 함수를 선언 기억 포장지.

단점 : 개발 중에 지정된 함수의 매개 변수 목록에있는 바이트 수가 변경되면 서명을 변경해도 이름이 변경되지 않으므로 함수를 가져 오는 응용 프로그램에서 캐치되지 않습니다. 개인적으로, 나는 이것이 문제라고 생각하지 않는다. 왜냐하면 이름이 장식되어 있지 않기 때문에 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. 
    // ... 
}; 

실제로 이와 같은 래퍼 클래스로 할 수있는 일은 훨씬 많습니다. 편집에

: OP는 의견의 PInvoke를 사용하여 언급하기 때문에 - 사람이 작업을 수행하기로 결정하면, 는 PInvoke를 사용시 [DllImport] 선언에 CallingConvention = CallingConvention.Cdecl을 추가을 잊지 마세요. __cdecl은 관리되지 않는 C/C++의 기본값 일 수 있지만 관리되는 코드의 기본값은 아닙니다.

+0

감사합니다.이 아이디어가 한 가지 질문에 감사드립니다. '__cdecl'에 대한 변경으로 인해 dll을 사용하는 소프트웨어에 부작용이 생길 수 있습니까? 우리는 도구 모음에 여러 dll과 응용 프로그램을 가지고 있습니다. 우리는 어디에서 stdcall을 사용하기 때문에 변경해야합니다. 또한 C# dll은 비표준 dll (현재 stdcall 사용)을 pInvoke하면 호출 규칙을 cdecl로 변경하는 것일 뿐이며 32 비트 및 64 비트를 사용할 때 내 보낸 이름이 달라 지므로 다른 문제가 발생할 것입니다. – dbostream

+0

내 보낸 함수에 대한 새로운 (다른 호출 규칙) 선언을 포함하는 헤더 파일을 변경하고 DLL을 다시 작성하는 경우 내 보낸 함수를 사용하는 모든 것을 다시 작성하면 새 호출 규칙도 사용됩니다. 모두가 같은 페이지에 있고 전화 회의에서 현명하다면 괜찮을 것입니다. – frasnian

+0

Connect 함수를'__cdecl'로 변경하고 Dependency Walker를 사용하여 32 비트 및 64 비트 dll에 동일한 이름 즉 _Connect_를 표시합니다. [link] (https://msdn.microsoft.com/en-us/library/zkwh89ks.aspx)를 올바르게 이해했다면 'extern "C"'를 사용하기 때문에 접두사가 붙지 않습니다. 따라서'DecorateSymbolName'이 필요하지 않습니다. 이게 합리적인 것인가, 아니면 내가 잘못한 것을 했습니까? – dbostream

0

__stdcall은 x64에서 지원되지 않으며 무시됩니다. MSDN 인용 :

ARM 및 x64 프로세서에

, __stdcall 허용 및 컴파일러에 의해 무시된다; ARM 및 x64 아키텍처에서 규칙에 따라 인수는 가능한 경우 레지스터에 전달되고 이후 인수는 스택에 전달됩니다.

x64의 호출 규칙은 pretty much __fastcall입니다.

x86 및 x64의 호출 규칙 및 이름 장식 규칙이 서로 다르기 때문에이 방법을 추상화해야합니다. 따라서 #if _WIN64을 사용한 귀하의 아이디어는 올바른 방향으로 나아갑니다.

x86 호출 규칙과 요구 사항을 검토하고 이름 선택 프로세스를 자동화 할 수있는 매크로를 고안 할 수 있습니다.

관련 문제