dllexport를 사용하여 DLL에서 함수 내보내기


105

C ++ Windows DLL에서 함수를 내보내는 간단한 예제를 원합니다.

헤더, .cpp파일 및 .def파일 (절대적으로 필요한 경우) 을보고 싶습니다 .

내 보낸 이름을 꾸미지 않고 싶습니다 . 가장 표준적인 호출 규칙 ( __stdcall?) 을 사용하고 싶습니다 . 나는 사용을 원 __declspec(dllexport)하고 사용할 필요가 없습니다.def 파일을.

예를 들면 :

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

링커가 이름에 밑줄 및 / 또는 숫자 (바이트 수?)를 추가하지 않도록하려고합니다.

나는 지원하지 않는으로 OK예요 dllimportdllexport같은 헤더를 사용. C ++ 클래스 메서드를 내보내는 데 대한 정보가 아니라 C 스타일 전역 함수 만 필요합니다.

최신 정보

호출 규칙을 포함하지 않고를 사용 extern "C"하면 원하는대로 내보내기 이름이 제공되지만 그게 무슨 뜻입니까? 어떤 기본 호출 규칙이 내가 어떤 pinvoke (.NET), 선언 (vb6)을 얻고 있으며 GetProcAddress예상합니까? ( GetProcAddress호출자가 만든 함수 포인터에 따라 달라질 것입니다).

이 DLL이 헤더 파일없이 사용되기를 원하기 때문에 #defines호출자가 헤더를 사용할 수 있도록 만드는 데는 많은 공상이 필요하지 않습니다 .

*.def파일 을 사용해야한다는 대답은 괜찮습니다 .


내가 잘못 기억하고있을 수도 있지만 다음과 같이 생각합니다. a) extern C함수의 매개 변수 유형을 설명하는 장식은 제거하지만 함수의 호출 규칙을 설명하는 장식은 제거하지 않습니다. b) 장식을 모두 제거 하려면 DEF 파일에 (장식되지 않은) 이름을 지정해야합니다.
ChrisW

이것은 내가 본 것입니다. 아마도 이것을 완전한 대답으로 추가해야할까요?
Aardvark

답변:


134

일반 C 내보내기를 원하면 C ++가 아닌 C 프로젝트를 사용하십시오. C ++ DLL은 모든 C ++ ism (네임 스페이스 등)에 대해 이름 변경에 의존합니다. C / C ++-> Advanced 아래의 프로젝트 설정으로 이동하여 코드를 C로 컴파일 할 수 있습니다. 컴파일러 스위치 / TP 및 / TC에 해당하는 "Compile As"옵션이 있습니다.

여전히 C ++를 사용하여 lib의 내부를 작성하고 싶지만 일부 함수를 C ++ 외부에서 사용하기 위해 얽 히지 않은 상태로 내보내려면 아래 두 번째 섹션을 참조하십시오.

VC ++에서 DLL 라이브러리 내보내기 / 가져 오기

정말로 원하는 것은 DLL 프로젝트의 모든 소스 파일에 포함될 헤더에 조건부 매크로를 정의하는 것입니다.

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

그런 다음 내보내려는 함수에서 다음을 사용하십시오 LIBRARY_API.

LIBRARY_API int GetCoolInteger();

라이브러리 빌드 프로젝트에서 정의를 생성하면 LIBRARY_EXPORTSDLL 빌드를 위해 함수를 내보낼 수 있습니다.

이후 LIBRARY_EXPORTS 해당 프로젝트가 기능을 대신 가져옵니다 모두의 라이브러리의 헤더 파일을 포함하는 DLL을 소모 프로젝트에 정의되지 않습니다.

라이브러리가 교차 플랫폼 인 경우 Windows가 아닌 경우 LIBRARY_API를 아무것도 정의하지 않을 수 있습니다.

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

dllexport / dllimport를 사용할 때 DEF 파일을 사용할 필요가 없습니다. DEF 파일을 사용하는 경우 dllexport / dllimport를 사용할 필요가 없습니다. 두 가지 방법은 동일한 작업을 다른 방식으로 수행하므로 dllexport / dllimport가 두 가지 중에서 권장되는 방법이라고 생각합니다.

LoadLibrary / PInvoke 용 C ++ DLL에서 얽 히지 않은 함수 내보내기

LoadLibrary 및 GetProcAddress를 사용하거나 다른 언어 (예 : .NET의 PInvoke 또는 Python / R의 FFI 등)에서 가져 오기 위해 필요한 경우 다음을 사용할 수 있습니다. extern "C" dllexport와 함께 인라인을 하여 C ++ 컴파일러에 이름을 조작하지 않도록 지시 할 수 있습니다. 그리고 dllimport 대신 GetProcAddress를 사용하고 있으므로 위에서 ifdef 댄스를 수행 할 필요가 없습니다. 간단한 dllexport 만 있으면됩니다.

코드:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

다음은 Dumpbin / exports를 사용한 내보내기의 모습입니다.

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

따라서이 코드는 잘 작동합니다.

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern "C"는 C ++ 스타일 이름 맹 글링을 제거하는 것처럼 보였습니다. 전체 수입 대 수출 일 (제가 질문에 포함하지 말라고 제안하려고했던 것)은 제가 요청하는 내용이 아닙니다 (하지만 좋은 정보). 나는 그것이 문제를 흐리게 할 것이라고 생각했습니다.
Aardvark

LoadLibrary 및 GetProcAddress가 필요하다고 생각할 수있는 유일한 이유는 이미 처리되어 있습니다. 제 답변 본문에서 설명하겠습니다 ...
joshperry

EXTERN_DLL_EXPORT == extern "C"__declspec (dllexport)입니까? SDK에 있습니까?
Aardvark

3
모듈 정의 파일을 프로젝트의 링커 설정에 추가하는 것을 잊지 마십시오. "기존 항목을 프로젝트에 추가"하는 것만으로는 충분하지 않습니다!
Jimmy

1
이것을 사용하여 VS로 DLL을 컴파일 한 다음 .C를 사용하여 R에서 호출했습니다. 큰!
Juancentro

33

C ++의 경우 :

나는 방금 같은 문제에 직면했고 하나가 __stdcall(또는 WINAPI) extern "C" 다음을 모두 사용할 때 문제가 발생한다고 언급하는 것이 가치가 있다고 생각합니다 .

아시다시피 extern "C"장식을 제거하여 대신 다음을 수행하십시오.

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

장식되지 않은 기호 이름을 얻습니다.

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

그러나 _stdcall(= 매크로 WINAPI, 호출 규칙을 변경) 또한 이름을 장식하므로 둘 다 사용하면 다음을 얻을 수 있습니다.

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

extern "C"기호가 장식되어 있기 때문에 의 이점 이 손실됩니다 (_ @bytes 포함).

참고이는 것을 단지 때문에 x86 아키텍처 발생 __stdcall대회가 64 무시됩니다 ( MSDN : 가능하면 규칙에 의해 64 아키텍처에 인수 레지스터에 전달하고, 이후의 인수는 스택에 전달됩니다 .).

x86 및 x64 플랫폼을 모두 대상으로하는 경우 특히 까다 롭습니다.


두 가지 솔루션

  1. 정의 파일을 사용하십시오. 그러나 이렇게하면 def 파일의 상태를 유지해야합니다.

  2. 가장 간단한 방법 : 매크로 정의 ( msdn 참조 ) :

#define EXPORT 주석 (링커, "/ EXPORT :"__FUNCTION__ "="__FUNCDNAME__)

그런 다음 함수 본문에 다음 pragma를 포함합니다.

#pragma EXPORT

전체 예 :

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

이렇게하면 x86에 대한 __stdcall규칙을 유지하면서 x86 및 x64 대상 모두에 대해 데코 레이팅되지 않은 함수를 내 보냅니다 . 가 __declspec(dllexport) 되지 않는 이 경우이 필요합니다.


5
이 중요한 힌트에 감사드립니다. 64 비트 DLL이 32 비트 DLL과 다른 이유가 무엇인지 이미 궁금했습니다. 나는 당신의 대답이 대답으로 받아 들여진 것보다 훨씬 더 유용하다고 생각합니다.
Elmue

1
저는이 접근 방식을 정말 좋아합니다. 내 유일한 권장 사항은 매크로가 __FUNCTION__함수에서만 작동 하기 때문에 매크로 이름을 EXPORT_FUNCTION으로 변경하는 것 입니다.
Luis

3

나는 정확히 같은 문제가 있었는데, 내 해결책은 __declspec(dllexport)내보내기를 정의하는 대신 모듈 정의 파일 (.def)을 사용하는 것이 었습니다 ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). 왜 이것이 작동하는지 모르겠지만 작동합니다.


사용 : 사람에게 참고 다른이에 실행 .def파일이 모듈 수출 않는 일을하지만, 제공 할 수있는 희생 extern예에 대한 헤더 파일에 정의를 글로벌 데이터 인 경우, 당신은 제공해야 extern수동으로 정의를 내부 의 용도 그 데이터. (예, 필요할 때가 있습니다.) 일반적으로 그리고 특히 크로스 플랫폼 코드의 경우 단순히 __declspec()매크로와 함께 사용 하여 데이터를 정상적으로 전달할 수있는 것이 좋습니다.
Chris Krycho

2
그 이유는 당신이 사용하는 경우 아마도 때문입니다 __stdcall, 다음 __declspec(dllexport)없는 장식을 제거합니다. .def그러나 의지에 기능을 추가합니다 .
Björn Lindqvist 2015 년

1
@ BjörnLindqvist +1, x86의 경우에만 해당됩니다. 내 대답을 참조하십시오.
Malick

-1

_naked가 원하는 것을 얻을 수 있다고 생각하지만 컴파일러가 함수에 대한 스택 관리 코드를 생성하지 못하게합니다. extern "C"는 C 스타일 이름 장식을 발생시킵니다. 그것을 제거하면 _가 제거됩니다. 링커는 밑줄을 추가하지 않으며 컴파일러는 추가합니다. stdcall은 인수 스택 크기가 추가되도록합니다.

자세한 내용은 다음을 참조하십시오. http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

더 큰 질문은 왜 그렇게 하시겠습니까? 이름이 엉망이 된 게 뭐가 문제 야?


잘린 이름은 호출 될 때 LoadLibrary / GetProcAddress 또는 ac / c ++ 헤더에 의존하지 않는 다른 메서드를 사용하면보기 흉합니다.
Aardvark

4
이는 도움이되지 않습니다. 매우 특수한 상황에서만 컴파일러 생성 스택 관리 코드를 제거하려고합니다. (- _ 접두사와 __cdecl 방법 보통을 포함하지 않는 것) 기본 __declspec (dllexport로 그냥 __cdecl을 사용하여 장식을 잃는 덜 해로운 방법이 될 것입니다.)
이안 그리피스

나는 그것이 도움이 될 것이라고 말하지 않았기 때문에 다른 효과에 대한 나의 경고와 그가 왜 그것을 원했는지 의문을 제기했습니다.
Rob K
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.