본문 바로가기

Research/Windows

calling convention, name mangling, C/C++ DLL Export 에 대해.






제목이 긴데... 윈도우 파트에만 한정하기도 그렇고... 하여튼.

이런 것들이 연관되어서 문제가 꽤 있었고 골머리를 오래 싸맸다.

C++ 로 만든 DLL 을 C 에서 사용하고 싶다거나

C 로 만든 DLL 을 C++ 에서 사용하고 싶다거나.

지금 생각하면 큰 문제는 아닌 것 같은데..; 정리 삼아.





먼저 Calling Convention 에 대해 간단히 요약해보자.

주로 사용되는 stdcall / cdecl 과 fastcall 이라고 볼 수 있겠다.

c++ 을 자주 사용하다 보니 thiscall 도 자주 보게 된다만 잠시 논외로 하고..


글을 보는 사람들 모두가 알고 있겠지만, 이 Calling Convention 이 중요하게 다루어지는 이유는

함수를 호출함에 있어서 스택의 정리 주체가 누구냐 ?

stdcall 은 CALLEE 가, cdecl 은 CALLER 가 스택을 정리한다.

이상하게 난 이런거 잘 못외워서 노래를 불러댔는데 '자기의 일은 스스로 하자~'

이런 마음가짐이 좋은 어린이의 기준이기 때문에 stdcall-좋은 함수는 스택을 알아서 정리한다.

이렇게 외웠고 아직 잘 써먹고 있다-_-;;


별 어려운 내용도 아니기야 하지만, 이걸 모르고 넘어가면

나중에 두 개 이상의 모듈을 사용해서 Export / Import 형식으로 코딩을 해야할 때 머리 쥐어 짠다.

어셈블리로 함수호출부 구현 중에 이걸 모르고 에러가 나면 재앙 수준.


자 여기에 설명이 하나가 추가가 되는데 stdcall 은 WINAPI 및 C++ 에서 사용된다. 라는 부분이다.

이게 귀찮다. stdcall 은 C++ 의 표준 호출 규약이다.






여기서 발생하는 문제가 Name Mangling 

간단히 설명하면, C++ 에서 지원하는 함수 오버로딩의 특징 때문에

같은 이름의 함수라도 Argument 와 Return 형에 따라 이름을 다르게 주어야 한다. 그래서 생긴게 이 규칙.

간단히 예를 들어보면...


 void __declspec(dllexport) test(int a, int b);

 ?test@@YGXHH@Z

 int __declspec(dllexport) test(int a, int b, char c);

 ?test@@YGHHHD@Z

C++ 프로젝트에서의 네임 맹글링


뭐 이렇다. 맹글링 된 함수명을 돌려보려면 Visual Studio 의 undname을 활용하면 편리하다.



stdcall 은 C++ 에서 채택한 표준 호출규약인데, 이 C++ 의 오버로딩 특징 때문에

stdcall 을 인자로 준 함수들은 다 네임 맹글링이 걸려 버린다. C 프로젝트에서도 네임 맹글링을 걸 수 있다!


void __declspec(dllexport) 

__stdcall Function1(int a, int b)

_Function1@8

void __declspec(dllexport) 

__cdecl Function2(int a, int b)

Function2

C 프로젝트에서 __stdcall 을 주었을 때의 네임 맹글링


그렇다고 해서 C 프로젝트에서 stdcall 을 주면 오버로딩이 되느냐 ?

그건 재정의 에러가 발생해서 안됩니다.



하나 더 해보면, C++ 프로젝트에서 __stdcall 로 정의된 함수와 __cdecl 로 정의된 함수도 맹글링이 다르다.

뭐 당연한 얘기.


void __declspec(dllexport) 

__stdcall Function1(int a, int b)

?Function1@@YGXHH@Z

void __declspec(dllexport) 

__cdecl Function2(int a, int b)

?Function2@@YAXHH@Z

C++ 프로젝트에서 __stdcall 과 __cdecl 의 차이



중간정리 하면, 

stdcall 로 함수를 만들면 네임 맹글링이 걸린다. 

C++ 에서는 stdcall 과 cdecl 로 걸리는 네임 맹글링이 다르다. 이정도가 되겠다.





저기 이상한 게 있는데요 ?

C++ 프로젝트에서의 네임 맹글링과 C 프로젝트에서의 맹글링이 다른가요 ?

어라 그러네요.


void test(int, int)             ==> ?test@@YGXHH@Z

void Function1(int, int)     ==> _Function1@8


이래서 함수를 찾을 때 잘 찾아야 한다.

C 프로젝트냐, C++ 프로젝트냐, cdecl 이냐, stdcall 이냐에 따라 네임 맹글링이 완전히 바뀐다.




이런 이유 때문에, 오픈 소스 DLL 이 C++ 인데 난 C로 짜야 한다면 ? 혹은 그 반대라면 ? 정말 굉장히 매우 귀찮아진다.

이런 네임 맹글링을 막기 위해 C++ 프로젝트에서는 아래와 같은 전처기리 명령어를 사용한다.


#ifdef __cplusplus

extern "C"{

#endif

...

#ifdef __cplusplus

}

#endif



이러면 C++ 로 만들어진 프로젝트에서, extern 블록 안에 있는 함수들이 네임 맹글링을 사용하지 않는다.

extern C 에 대한 설명은 인터넷 도처에 매우 많으니..



그래서, C++ DLL 함수를 에서 C에서 사용하려면 extern C를 활용하면 되고

Calling Convention 이 뭔지 모르겠으면 undname 을 사용해서 어떤 호출을 사용하는지 보면 된다.

C DLL 을 C++ 에서 사용하고 싶다면 ? 네임 맹글링이 박혀 있으면 stdcall, 안 박혀 있으면 cdecl.



아 원래 이걸 정리하려는게 아니었는데 마무리가 이상하네. 음 뭔가 정리할 게 더 있었는데. 나중에~