성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <br /> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우</div><br /> <br /> 이 글은 다음의 질문에 대한 답변입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > dll import하기 위해 struct 구성시에 struct가 struct를 가지고 있고 포함된 struct가 ByValArray형태일때 해결 ; <a target='_tab' href='/3/0/808'>http://www.sysnet.pe.kr/3/0/808</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/11556'>크기가 정해진 경우</a>에는 구조체만 잘 맞춰주면 COM 메서드나 export된 Win32 API와 연동하는 것은 그다지 어렵지 않습니다.<br /> <br /> 예전에 설명해드린 아래의 글은 union 구조체인 경우까지도 정상적으로 연동하는 것을 보여주는 예인데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > C#에서 Union 구조체 다루기 ; <a target='_tab' href='/2/0/728'>http://www.sysnet.pe.kr/2/0/728</a> </pre> <br /> 그런데, 크기가 정해지지 않은 배열의 경우에는 상황이 다릅니다. COM의 IDL조차도 이런 경우 length_is / size_is로 명시적인 마샬링 방법을 지정해야 하고, 이러한 메서드를 호출할 때 managed 환경에서는 MarshalAs의 SizeParamIndex를 지정해서 연동해야 합니다. 그나마 이렇게 대상이 COM 개체의 메서드라면 IDL 상에서 적당히 attribute만 지정되어 있으면 C#과의 연동이 허용된다는 것이지요.<br /> <br /> 문제는, 그렇게 정해지지 않은 타입이 구조체의 멤버로 포함되어 있고 이런 구조체를 C/C++에서 C#쪽으로 넘기는 경우에는 매끄럽게 연동하는 방법이 없습니다. 다음과 같은 경우가 그 예일 텐데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > public struct fng_curve { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)] public double[] tenor; }; public struct fng_curve_list { public <b style='color: Blue;'>int size</b>; public <b style='color: Blue;'>fng_curve* data</b>; }; </pre> <br /> 사실, COM을 제대로 아는 사람이라면 위의 구조체를 서비스하는 COM 개체는 잘못 작성되어진 것임을 알 수 있습니다. 무슨 이야기냐하면, COM조차도 fng_curve_list의 구조체 멤버인 fng_curve* data 내용을 마샬링할 수 없습니다. 이것이 가능한 경우는, 위의 COM 개체를 In-Process로만 사용하는 C/C++ 클라이언트 뿐입니다. 만약 Out-of-Process로 위의 COM 개체를 사용하면 Marshaller는 fng_curve* data 내용을 정상적으로 마샬링하지 못합니다. (사용자 정의 마샬러를 작성한다면 예외겠지만.) 간단하게는 Apartment만 바꾸어도 AV( Access Violation) 예외가 발생하지요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > C/C++ 프로젝트에 /clr 옵션 적용으로 인한 COM 개체 사용 오류 ; <a target='_tab' href='/2/0/650'>http://www.sysnet.pe.kr/2/0/650</a> </pre> <br /> 그러니, 애당초 이런 COM 개체를 만들어서는 안됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 비록 그렇다고는 하지만, C# 클라이언트 역시 같은 EXE 프로세스 공간이라면 이런 경우에 대한 해결책이 있긴 합니다. 바로 이런 경우, fng_curve 포인터 변수 대신에 IntPtr을 써주면 되는 것입니다.<br /> <br /> 이렇게 다루려면 다음과 같이 정의해 줘야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > [StructLayout(LayoutKind.Sequential)] public <b style='color: Blue;'>class</b> fng_curve { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)] public double[] tenor; } [StructLayout(LayoutKind.Sequential)] public struct fng_curve_list { public int size; public <b style='color: Blue;'>IntPtr data</b>; } </pre> <br /> 구조체 포인터 대신에 IntPtr로 바뀌고, fng_curve 구조체가 클래스로 바뀐 것 빼고는 별다르게 특별한 점은 없습니다.<br /> <br /> 호출되는 C/C++ 측의 코드와 호출하는 C# 측은 각각 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > ===== export되는 Win32 API ===== INTEROPWIN32_API void fnInterop1(fng_curve_list *item) { item->size = 5; // 배열 수를 지정하고, item->data = new fng_curve[item->size]; // 배열 할당 후 값을 설정 for (int i = 0; i < item->size; i ++) { for (int j = 0; j < 50; j ++ ) { item->data[i].tenor[j] = j * i; } } } ===== Win32 API를 호출하는 C#코드 ===== fng_curve_list item = new fng_curve_list(); fnInterop1(ref item); // 구조체를 ref로 전달하고, // 반환받은 구조체에서 IntPtr 데이터를 역직렬화 for (int i = 0; i < item.size; i++) { fng_curve curve = new fng_curve(); <b style='color: Blue;'>IntPtr pAddr = new IntPtr(item.data.ToInt64() + (i * Marshal.SizeOf(curve))); Marshal.PtrToStructure(pAddr, curve);</b> } </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12445'>Marshal.PtrToStructure 메서드는 이름과는 달리 두 번째 인자에 값 형식의 struct를 전달해주면 안되고 참조 형식의 class를 전달</a>해줘야 합니다. 이 때문에 fng_curve를 기존에 struct로 정의된 것을 class로 재정의한 것입니다.<br /> <br /> 일단, 이렇게 하면 급한데로 연동은 시켰습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 물론, 문제는 여기서 끝나지 않습니다. 제가 말씀드린 데로 위의 COM 개체는 COM에 대한 이해가 부족한 상태에서 제작된 것이기 때문에 치명적인 문제가 있습니다. 너무도 유명한 "Memory Leak"이 발생한다는 것!<br /> <br /> C/C++에서 메모리를 "new" 연산자를 통해서 할당한 것은 C/C++의 고유한 메모리 할당방식일 뿐 언어간에 표준으로 자리잡은 것이 아니기 때문에 managed 환경에서의 C#에서 IntPtr로 받은 메모리를 해제할 수 있는 방법이 없습니다. Marshal 타입에서 제공되는 해제 함수들은 AllocHGlobal / CoTaskMem으로 할당되었거나 COM 개체의 참조카운트를 감소시키는 정도일 뿐 C/C++의 new/delete에 상응되는 것은 없습니다. 따라서 이런 경우에는 메모리 해제를 하도록 C/C++ 측에서 메서드를 만들어주고 C#에서는 그것을 반드시 호출해 주어야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > ===== export되는 Win32 API ===== INTEROPWIN32_API void fnInterop2(fng_curve_list *item) { <b style='color: Blue;'>delete [] item->data;</b> } ===== Win32 API를 호출하는 C#코드 ===== fng_curve_list item = new fng_curve_list(); fnInterop1(ref item); // 사용 후, <b style='color: Blue;'>fnInterop2(ref item);</b> // 반드시 메모리 해제 </pre> <br /> <hr style='width: 50%' /><br /> <br /> 일단, 위에서는 접근방식을 가능한 managed 환경으로 처리를 해보았는데 unsafe 키워드를 적절하게 사용하면 좀 더 직관적인 구문으로 처리하는 것도 됩니다. 2가지 방식으로 처리한 예제 코드를 다음에 첨부해 놓았습니다.<br /> <br /> - <a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=468&boardid=331301885'>managed 버전으로 처리: InteropTest(managed).zip</a><br /> - <a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=469&boardid=331301885'>unsafe 버전으로 처리: InteropTest(unsafe).zip</a><br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6378
(왼쪽의 숫자를 입력해야 합니다.)