2013년 1월 14일 월요일

LNK4197 경고 해결하기

1>afc_scm.obj : warning LNK4197: export 'send_command' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'stop_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'start_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'uninstall_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'install_driver' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'free_scm_context' specified multiple times; using first specification
1>afc_scm.obj : warning LNK4197: export 'create_scm_context' specified multiple times; using first specification
32비트에서는 아무런 문제없이 빌드되던 프로젝트가 오늘 보니 4197 링크 경고를 뿌려대고 있었습니다.
http://support.microsoft.com/kb/835326/en-us 페이지에 경고에 대해서 설명된 내용이 있네요.

결론은 dllexport 를 사용하던지 def 파일을 사용하던지 하나만 하라는건데요...

문제가 좀 있어보입니다.
def 파일을 사용하지 않고, __declspec(dllexport) 만 사용하는 경우 __stdcall 호출방식을 사용할때 네임맹글링 문제로 인해서 원치않는 형태의 이름으로 export 되지요.
아래 그림처럼요...

그렇다면 def 파일을 이용해서 dll export 를 하면어떨까요?
이건 제 경우에만 문제가 될 수 있는 문제인데요. 저는 dll 을 만들때 아래와 같은 구조의 코드를 항상 사용합니다.
#ifdef AFC_SCM_EXPORTS
 #ifdef __cplusplus
  #define AFC_SCM_API   extern "C" __declspec(dllexport) 
  #define AFC_SCM_CLASS  __declspec(dllexport) 
 #else
  #define AFC_SCM_API   __declspec(dllexport) 
 #endif//__cplusplus
#else
 #ifdef __cplusplus
  #define AFC_SCM_API   extern "C" __declspec(dllimport)
  #define AFC_SCM_CLASS  __declspec(dllimport) 
 #else
  #define AFC_SCM_API   __declspec(dllimport)
 #endif//__cplusplus  
#endif//AFC_SCM_EXPORTS

AFC_SCM_API 
scm_ctx
__stdcall
create_scm_context(
 _In_z_ const wchar_t* driver_path, 
 _In_z_ const wchar_t* service_name, 
 _In_z_ const wchar_t* service_display_name,
 _In_ bool uninstall_service_on_free
 );
AFC_SCM_API 가 정의되어있는 경우 (dll 프로젝트 자체) create_scm_context() 함수는 dllexport 가 됩니다. 그렇지 않은 경우  create_scm_context() 함수는 dllimport 가 되죠. 즉 dll 을 묵시적으로 링크하는 프로젝트가 되겠죠.
dll 사용자는 dll, lib, header 파일만 있으면 별도로 손대지 않고, 그냥 dll 을 나름 편하게 사용할 수 있습니다. def 파일을 이용해서 dll 을 만드는 경우 이런 이점을 버려야 할것 같네요. (별도로 dllimport 를 해주거나 명시적 링킹을 해야 하니까요...)

대체 MS 는 왜 이런 짓을 하는거야..ㅆㅂ ㅆㅂ 하면서 잠시 생각해보니 나이스한 해결책이 있습니다. ( 사실은 해결책이 아니라 제가 바보였던거죠. )

x86 프로젝트에서는 예전처럼 dllexport 와 DEF 파일을 모두 사용하고, x64 프로젝트에서는 def 만 사용안하면 그만입니다. x64 는 호출규약이 하나뿐이라 네임맹글링 문제가 없어서 def 와 dllexport 를 함께 사용하지 않도록 (불필요한 실수를 줄이기 위해) 가이드하는거 같습니다... (아님 말구요)

결론: dll 을 만들때는
- dllexport 를 이용하고, 
- 32 비트에서 네임맹글링 문제가 있다면 def 파일을 함께 사용
- 64 비트에서는 그냥 dllexport 만 사용 (네임맹글링 문제가 아예 없다!)