성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그런 부분은 클라우드 업체 쪽에 문의를 하는 것이 더 좋지 않을...
[정성태] 정적 분석과 함께, 이제는 실행 시 성능 분석까지 (비록 Azu...
[정성태] .NET Source Browser를 이용해 Roslyn 소스 ...
[정성태] Experimental C# Interceptors: AOT &...
[정성태] .NET Conf 2023 (Day 2) - Tiny, fast...
[정성태] The end of the Tye Experiment #1622...
[정성태] This is a simple app that converts ...
[정성태] Wrathmark: An Interesting Compute W...
[정성태] FFmpeg Filters Every Youtuber Needs...
[정성태] 일단, PInvokeStackImbalance 오류가 발생했다는...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C++로 만든 DLL을 C#에서 사용하기</h1> <p> 우연히, 다음의 글을 접하게 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C++로 만든 DLL을 C#에서 사용하기 ; <a target='tab' href='http://pay114.cafe24.com/?m=bbs&bid=008&uid=706'>http://pay114.cafe24.com/?m=bbs&bid=008&uid=706</a> </pre> <br /> 오호~~~ 그러고 보니, 제 블로그에서 한 번도 이에 대한 설명을 한 적이 없군요. 대신, 다음의 글들에서 당연하게 사용한 정도입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C#에서 C/C++ 함수로 콜백 함수를 전달하는 예제 코드 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11099'>http://www.sysnet.pe.kr/2/0/11099</a> Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/737'>http://www.sysnet.pe.kr/2/0/737</a> C#에서 Union 구조체 다루기 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/728'>http://www.sysnet.pe.kr/2/0/728</a> </pre> <br /> 그런데, "<a target='tab' href='http://pay114.cafe24.com/?m=bbs&bid=008&uid=706'>C++로 만든 DLL을 C#에서 사용하기</a>" 글을 보면 이상한 설명이 끼어 있습니다. 바로 "C++ 프로젝트 Property Setting"에서 "Common Language Runtime support"의 "/clr" 옵션을 지정하라는 것입니다. 만약 이 제약이 사실이라면, 기존 제작된 모든 Win32 DLL들은 모두 새로 컴파일해야만 합니다. 게다가 소스 코드가 없는 외부 DLL이라면 이런 경우 방법이 없다는 것이고.<br /> <br /> 물론, 저 단계는 필요 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 간단하게 해당 블로그에서 했던 내용을 재현해 볼까요? 소스 코드는 지난번 글의 예제를 그대로 사용하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <a target='tab' href='https://www.sysnet.pe.kr/2/0/11099'>C#에서 C/C++ 함수로 콜백 함수를 전달하는 예제 코드</a> - pinvoke_callback_sample.zip ; <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1080&boardid=331301885'>http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1080&boardid=331301885</a> </pre> <br /> 위의 예제 프로젝트의 Win32Project1에 다음의 코드를 각각 추가하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Win32Project1.h ...[생략]... extern "C" { // ...[생략]... __declspec(dllexport) int fnWin32Project2(int value); }; </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Win32Project1.cpp ...[생략]... __declspec(dllexport) int fnWin32Project2(int value) { return value; } </pre> <br /> 빌드한 후, C#에서 이렇게 불러주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Win32Project1.dll")] static extern int fnWin32Project2(int value); static void Main(string[] args) { Console.WriteLine(fnWin32Project2(52)); } </pre> <br /> 다음과 같은 예외가 발생합니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in 'C:\...\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe'.<br /> <br /> Additional information: A call to PInvoke function 'ConsoleApplication1!Program::fnWin32Project2' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. <span style='color: blue; font-weight: bold'>Check that the calling convention</span> and parameters of the PInvoke signature match the target unmanaged signature. <br /> If there is a handler for this exception, the program may be safely continued.<br /> </div><br /> <br /> 이유는 간단합니다. 위의 오류 메시지에서도 나오듯이, "calling convention"이 틀리기 때문입니다. 실제로, 새로 추가된 fnWin32Project2 함수를 depends.exe로 확인해 보면,<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='cpp_dll_for_cs_1.png' src='/SysWebRes/bbs/cpp_dll_for_cs_1.png' /><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > _fnWin32Project1@8 <span style='color: blue; font-weight: bold'>fnWin32Project2</span> </pre> <br /> 함수명 이름 앞에 "_" 밑줄도 없고 '@' + '[인자에 소비된 바이트 수]' 표기도 없습니다. 우리가 작성한 함수의 경우 호출 규약이 "cdecl"로 설정되어 있기 때문입니다. 이런 경우, 그냥 C# 함수에서 호출 규약만 맞춰주면 문제는 해결됩니다. 따라서, DllImport에 그것만 다음과 같이 명시해 줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Win32Project1.dll", <span style='color: blue; font-weight: bold'>CallingConvention=CallingConvention.Cdecl)</span>] static extern int fnWin32Project2(int value); </pre> <br /> 이렇게 하고 실행하면 아무런 예외 없이 잘 실행됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 당연하겠지만, DllImport로 지정된 C# 메서드 선언은 기본 호출 규약이 StdCall입니다. 따라서 위와 같이 C# 측에서 바꿔주어도 되지만, C++ 측에서 애당초 다음과 같이 __stdcall로 명시해주는 것도 한 방법입니다. (물론, 이 방법은 C/C++ 소스 코드가 자신의 관리하에 있다는 조건이 필요합니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Win32Project1.h ...[생략]... extern "C" { // ...[생략]... __declspec(dllexport) <span style='color: blue; font-weight: bold'>__stdcall</span> int fnWin32Project2(int value); }; </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Win32Project1.cpp ...[생략]... __declspec(dllexport) <span style='color: blue; font-weight: bold'>__stdcall</span> int fnWin32Project2(int value) { return value; } </pre> <br /> 다시 처음의 의문으로 돌아가서, 그렇다면 왜 /clr 옵션을 추가해서 C++ DLL 프로젝트를 빌드하면 C#에서 잘 호출되었을까요?<br /> <br /> 사실 잘 모르겠습니다. ^^; 아마도 해당 블로그의 저자가 사용한 Visual Studio의 컴파일러에서는, /clr 옵션이 걸리면 호출 규약이 명시되지 않은 export 함수들에 대해 __cdecl이 아닌 __stdcall로 한 것이 아닌가 생각됩니다. 만약, 그렇게 처리를 해준 것이라면 C# 측의 DllImport에 호출 규약을 Cdecl로 바꾸지 않았는데도 제대로 호출된 것에 대한 설명이 가능합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, 혹시나 한마디 더!<br /> <br /> x64로 빌드하면 기본 호출 규약이 "__fastcall"로 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > x64 software conventions ; <a target='tab' href='https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions'>https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions</a> </pre> <br /> 그렇다면 C# 소스 코드에서 DllImport에 명시한 CallingConvention=CallingConvention.Cdecl 설정을 x64로 빌드할 때는 바꿔주어야 하는 것일까요? 그럴 필요 없습니다. x64로 빌드되면, 모든 호출 규약 설정을 무시하고 암시적으로 FastCall 규약을 사용하기 때문입니다.<br /> <br /> 참고로, 전에 제가 겪은 시행착오 하나 소개하면서 끝을 맺습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > JNI DLL 컴파일 시 x86과 x64의 Export된 함수의 이름이 왜 다를까요? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1199'>http://www.sysnet.pe.kr/2/0/1199</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1868
(왼쪽의 숫자를 입력해야 합니다.)