성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling</h1> <p> x64의 경우, 윈도우 자체가 ABI(Application Binary Interface)의 요구 사항 수준에서 calling convention을 __fastcall 단 한 개로 정리해 버렸습니다.<br /> <br /> 그런데, __fastcall이라고는 하지만 x86의 __fastcall과는 약간 다릅니다. 가령, x86의 경우 처음 2개의 인자만 ecx, edx로 넘기고 나머지는 스택을 통해 전달합니다. 반면 <a target='tab' href='http://kkamagui.tistory.com/811'>x64에서는 처음 4개의 인자를 rcx, rdx, r8, r9로 넘기고 이후의 인자는 스택으로 전달합니다. (하지만, 처음 4개의 인자를 위한 스택도 할당합니다.)</a> 심지어 이름조차도 Wikipedia에서는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > x86 calling conventions ; <a target='tab' href='https://en.wikipedia.org/wiki/X86_calling_conventions'>https://en.wikipedia.org/wiki/X86_calling_conventions</a> </pre> <br /> __fastcall이 아닌 "Microsoft x64 calling convention"이라고 지칭합니다. 이런 상황에서 마이크로소프트는 공식 문서에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > x64 calling convention ; <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 /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> Given the expanded register set, <span style='color: blue; font-weight: bold'>x64 uses the __fastcall calling convention</span> and a RISC-based exception-handling model. The __fastcall convention uses registers for the first four arguments and the stack frame to pass additional arguments. </div><br /> <br /> __fastcall이라고 명시합니다. 이렇게 혼란스러운 면이 있지만, 여기서는 마이크로소프트의 문서에 따라 __fastcall 호출 규약이라고 하겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이름이야 뭐든지 간에, 덕분에 C++에서 해당 함수를 __cdecl이든 __stdcall이든 예약어를 지정해도 Visual C++ 컴파일러는 이를 무시하고 무조건 __fastcall 호출 규약으로 만들어 버립니다. 이로 인해 Name mangling도 간편해지는데, C++ 이름 형식이어야 하는 export 함수들을 제외하고는 전부 - .def에 명시했든, extern "C"로 하든 - 함수 이름 자체를 그대로 노출시킵니다. 가령, export 함수들을 다음과 같이 선언했을 때,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__cdecl</span> CDECL_Func(int value); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__cdecl</span> CDECL_Func_By_DEF(int value); // .def 파일에 명시 extern "C" { __declspec(dllexport) <span style='color: blue; font-weight: bold'>__cdecl</span> int ExternC_CDECL_Func(int value); __declspec(dllexport) <span style='color: blue; font-weight: bold'>__cdecl</span> int ExternC_CDECL_Func_Arg2(int value, int *pValue); __declspec(dllexport) <span style='color: blue; font-weight: bold'>__cdecl</span> int ExternC_CDECL_Func_Arg5(int value1, int value2, int value3, int value4, int value5); __declspec(dllexport) <span style='color: blue; font-weight: bold'>__cdecl</span> int ExternC_CDECL_Func_By_DEF(int value); } __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> STD_Func(int value); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> STD_Func_By_DEF(int value); // .def 파일에 명시 extern "C" { __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> ExternC_STD_Func0(); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> ExternC_STD_Func(int value); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> ExternC_STD_Func2(int value); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> ExternC_STD_Func_Arg2(int value, int *pValue); __declspec(dllexport) int <span style='color: blue; font-weight: bold'>__stdcall</span> ExternC_STD_Func_By_DEF(int value); } </pre> <br /> 빌드하면 DLL Export 함수 이름들이 이렇게 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ?CDECL_Func@@YAHH@Z CDECL_Func_By_DEF ?STD_Func@@YAHH@Z STD_Func_By_DEF ExternC_CDECL_Func ExternC_CDECL_Func_Arg2 ExternC_CDECL_Func_Arg5 ExternC_CDECL_Func_By_DEF ExternC_STD_Func ExternC_STD_Func0 ExternC_STD_Func2 ExternC_STD_Func_Arg2 ExternC_STD_Func_By_DEF </pre> <br /> 당연히, 닷넷 측에서도 DllImport를 지정하는 방법이 간편해졌습니다. 운영체제 차원의 CallingConvention이 통합되었으므로 닷넷 역시 DllImport에 개발자가 어떻게 명시하든 그것을 무시해버리고 x64의 __fastcall 호출 규약을 따른다고 가정합니다.<br /> <br /> 따라서, x86에서는 __cdecl 호출 규약을 다음과 같이 정의해야 했지만,<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>)] internal static extern int ExternC_CDECL_Func(int value); </pre> <br /> x64 빌드 용이라면 다음과 같이 해도 무방합니다.<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")] internal static extern int ExternC_CDECL_Func(int value); </pre> <br /> 심지어 __stdcall로 지정해도 됩니다. (어차피 무시하므로!)<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.StdCall</span>)] internal static extern int ExternC_CDECL_Func(int value); </pre> <br /> 당연히 Visual Studio 디버거에서도 (무시할 호출 규약을 지정했다고 해서) PInvokeStackImbalance MDA 예외를 발생시키지 않습니다. 적어도 x64 세계에서는, 호출 규약의 복잡함이 없어졌기 때문에 개발자 입장에서는 반가운 변화입니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1098&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6322
(왼쪽의 숫자를 입력해야 합니다.)