성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import</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++ 간 배열 전달. SafeArray ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5726'>https://www.sysnet.pe.kr/3/0/5726</a> </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;' > using System; using System.Runtime.InteropServices; namespace CsClassLibrary { [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] [Guid("EF9B11F3-AAF7-47B3-95BA-5D842F376E8E")] public interface IDevice { <span style='color: blue; font-weight: bold'>UInt32[] GetFoo();</span> } [ComVisible(true)] [ClassInterface(ClassInterfaceType.None)] [Guid("E0F14B0E-7749-4BA5-8304-FA61F8056D24")] public class CDevice : IDevice { <span style='color: blue; font-weight: bold'>public UInt32[] GetFoo()</span> { UInt32[] retArray = new UInt32[] { 0, 1, 2, 3, 4 }; return retArray; } } } </pre> <br /> "<a target='tab' href='https://www.sysnet.pe.kr/2/0/12791'>Register for COM interop</a>" 옵션을 통해 COM 개체로 노출시킨 상태입니다. 그리고 이것을 Visual C++에서 다음과 같이 사용하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include <atlstr.h> #include <atlconv.h> #include <atlbase.h> #include <atlsafe.h> #import "..\\CsClassLibrary\\bin\\Debug\\CsClassLibrary.tlb" no_namespace named_guids int main() { CoInitialize(NULL); IDevice* mp_device; HRESULT hr = CoCreateInstance(CLSID_CDevice, NULL, CLSCTX_INPROC_SERVER, DIID_IDevice, reinterpret_cast<void**>(&mp_device)); if (hr == S_OK) { SAFEARRAY* presult = <span style='color: blue; font-weight: bold'>mp_device->GetFoo();</span> mp_device->Release(); } CoUninitialize(); } </pre> <br /> GetFoo 함수 호출에서 런타임 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled exception at 0x00007FFE9AB6474C in CppClient.exe: Microsoft C++ exception: _com_error at memory location 0x000000CABF78F360. > vcruntime140d.dll!<a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20231110-00/?p=108991'>_CxxThrowException</a>(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 75 C++ Non-user code. Symbols loaded. CppClient.exe!_com_raise_error(HRESULT hr, IErrorInfo * perrinfo) Line 19 C++ Symbols loaded. CppClient.exe!_com_dispatch_method(IDispatch * pDispatch, long dwDispID, unsigned short wFlags, unsigned short vtRet, void * pvRet, const wchar_t * pwParamInfo, ...) Line 96 C++ Symbols loaded. CppClient.exe!IDevice::GetFoo() Line 17 C++ Symbols loaded. CppClient.exe!main() Line 18 C++ Symbols loaded. CppClient.exe!invoke_main() Line 79 C++ Non-user code. Symbols loaded. CppClient.exe!__scrt_common_main_seh() Line 288 C++ Non-user code. Symbols loaded. CppClient.exe!__scrt_common_main() Line 331 C++ Non-user code. Symbols loaded. CppClient.exe!mainCRTStartup(void * __formal) Line 17 C++ Non-user code. Symbols loaded. kernel32.dll!BaseThreadInitThunk() Unknown Non-user code. Symbols loaded. ntdll.dll!RtlUserThreadStart() Unknown Non-user code. Symbols loaded. </pre> <br /> 도대체 무슨 오류가 있는 것일까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> Visual C++가 "#import ..." 구문을 이용해 tlb 파일로부터 생성한 GetFoo 함수의 호출 코드를 보면 이렇습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > inline SAFEARRAY * IDevice::GetFoo ( ) { SAFEARRAY * _result = 0; _com_dispatch_method(this, 0x60020000, DISPATCH_METHOD, <span style='color: blue; font-weight: bold'>VT_ARRAY|VT_I4</span>, (void*)&_result, NULL); return _result; } </pre> <br /> 뭔가.. 좀 이상하지 않나요? ^^ 우리가 만든 C# 메서드는 uint[]를 반환하는데 위에서 명시한 "VT_ARRAY|VT_I4" 플래그는 int[]에 해당합니다. 이 가정이 맞는지, 테스트를 위해 위의 함수를 우리가 직접 다음과 같이 만들어,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > inline SAFEARRAY* <span style='color: blue; font-weight: bold'>GetFoo(IDevice *pDevice)</span> { SAFEARRAY* _result = 0; _com_dispatch_method(pDevice, 0x60020000, DISPATCH_METHOD, VT_ARRAY | <span style='color: blue; font-weight: bold'>VT_UI4</span>, (void*)&_result, NULL); return _result; } </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;' > SAFEARRAY* presult = GetFoo(mp_device); </pre> <br /> 호출 결과,<br /> <br /> <img alt='cs_cpp_interop_ui4_1.png' src='/SysWebRes/bbs/cs_cpp_interop_ui4_1.png' /><br /> <br /> presult에도 값이 정상적으로 들어 있으니 이것으로 문제가 파악되었습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면, 혹시 어느 쪽에서 잘못하고 있는 것인지 알 수 있을까요?<br /> <br /> 우선, C# 프로젝트의 "Register for COM interop" 옵션으로 생성된 TLB 파일을 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/com/ole-com-object-viewer'>oleview</a>를 통해 확인해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [ uuid(63722C41-D463-41DB-BCE4-8A497077448B), version(1.0), custom(90883F05-3D28-11D2-8F17-00A0C9A6186D, "CsClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null") ] library CsClassLibrary { // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D} importlib("mscorlib.tlb"); // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046} importlib("stdole2.tlb"); // Forward declare all types defined in this typelib dispinterface IDevice; [ uuid(EF9B11F3-AAF7-47B3-95BA-5D842F376E8E), version(1.0), custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "CsClassLibrary.IDevice") ] dispinterface IDevice { properties: methods: [id(0x60020000)] <span style='color: blue; font-weight: bold'>SAFEARRAY(unsigned long) GetFoo();</span> }; [ uuid(E0F14B0E-7749-4BA5-8304-FA61F8056D24), version(1.0), custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "CsClassLibrary.CDevice") ] coclass CDevice { interface _Object; [default] dispinterface IDevice; }; }; </pre> <br /> 정상적으로 "SAFEARRAY(unsigned long)"라고 출력을 하고 있습니다. 이것으로 C# 측에서는 정상적으로 COM 규약에 따라 메타데이터를 생성하고 있는 것입니다. 그럼, 남은 범인은 Visual C++ 측이 되는군요. ^^<br /> <br /> 그러니까, #import 구문(혹은 아마도 이 처리를 수행하는 하위 프로세스)은 IDL의 SAFEARRAY(unsigned long) 규약을 "VT_ARRAY|VT_ UI4"가 아닌 "VT_ARRAY|VT_I4"로 출력하는 버그(?)가 있는 것입니다.<br /> <br /> 일단, 어쩔 수 없습니다. 현상이 저러하니, 마이크로소프트 측에 이슈 제기를 하거나, 이 글에서처럼 별도의 함수를 만들어 직접 마샬링 타입을 지정하는 수밖에는 없습니다. (물론, 이렇게 되면 C#에서 메서드 시그니처를 변경했을 때 주의 깊게 해당 메서드의 마샬링 플래그도 직접 수정해야 하는 단점이 있습니다.)<br /> <br /> 이렇게 해결 방법은 알게 되었지만, 그나저나 런타임 오류가 발생하는 것이 좀 신기하긴 합니다. UI4나 I4나 어차피 4바이트인데... 너무 빡빡하게 ^^; 타입 체크를 해서 문제가 되는 듯합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 이번에는 반환값이 아닌 out 인자로 들어온 경우를 테스트해볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C# 코드 public void GetFoo2(out uint[] result) { result = new UInt32[] { 0, 1, 2, 3, 4 }; } </pre> <br /> 위의 메서드를 C++에서 #import하면 이런 함수가 자동생성됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // .tli 파일에 자동생성된 코드 inline HRESULT IDevice::GetFoo2 ( SAFEARRAY * * result ) { return _com_dispatch_method(this, 0x60020001, DISPATCH_METHOD, VT_EMPTY, NULL, L"\x6003", result); } </pre> <br /> 그리고 이번엔 해당 메서드를 호출하면 아예 Access Violation 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Exception thrown at 0x00007FFE851509A2 (clr.dll) in CppClient.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF. clr.dll!ClrSafeArrayGetVartype(struct tagSAFEARRAY *,unsigned short *) Unknown Symbols loaded. clr.dll!OleVariant::CreateArrayRefForSafeArray(struct tagSAFEARRAY *,unsigned short,class MethodTable *) Unknown Symbols loaded. clr.dll!DispParamArrayMarshaler::MarshalNativeToManaged(struct tagVARIANT *,class Object * *) Unknown Symbols loaded. clr.dll!DispatchMemberInfo::MarshalParamNativeToManaged(int,struct tagVARIANT *,class Object * *) Unknown Symbols loaded. clr.dll!DispatchInfo::InvokeMemberWorker(struct DispatchMemberInfo *,struct InvokeObjects *,int,int,int,int &,int &,long,struct tagDISPPARAMS *,struct tagVARIANT *,unsigned short,unsigned long,long *,struct tagVARIANT *,struct OBJECTHANDLE__ * *,int *,struct tagVARIANT * *) Unknown Symbols loaded. clr.dll!DispatchInfo::InvokeMemberDebuggerWrapper(struct DispatchMemberInfo *,struct InvokeObjects *,int,int,int,int &,int &,long,struct tagDISPPARAMS *,struct tagVARIANT *,unsigned short,unsigned long,long *,struct tagVARIANT *,struct OBJECTHANDLE__ * *,int *,struct tagVARIANT * *,class Frame *) Unknown Symbols loaded. clr.dll!DispatchInfo::InvokeMember(struct SimpleComCallWrapper *,long,unsigned long,unsigned short,struct tagDISPPARAMS *,struct tagVARIANT *,struct tagEXCEPINFO *,struct IServiceProvider *,unsigned int *) Unknown Symbols loaded. clr.dll!InternalDispatchImpl_Invoke(struct IDispatch *,long,struct _GUID const &,unsigned long,unsigned short,struct tagDISPPARAMS *,struct tagVARIANT *,struct tagEXCEPINFO *,unsigned int *) Unknown Symbols loaded. clr.dll!InternalDispatchImpl_Invoke_CallBack(void *) Unknown Symbols loaded. clr.dll!InternalDispatchImpl_Invoke_Wrapper(struct IDispatch *,long,struct _GUID const &,unsigned long,unsigned short,struct tagDISPPARAMS *,struct tagVARIANT *,struct tagEXCEPINFO *,unsigned int *) Unknown Symbols loaded. CppClient.exe!_com_invoke_helper(IDispatch * pDispatch, long dwDispID, unsigned short wFlags, unsigned short vtRet, void * pvRet, const wchar_t * pwParamInfo, char * argList, IErrorInfo * * pperrinfo) Line 324 C++ Symbols loaded. CppClient.exe!_com_dispatch_method(IDispatch * pDispatch, long dwDispID, unsigned short wFlags, unsigned short vtRet, void * pvRet, const wchar_t * pwParamInfo, ...) Line 84 C++ Symbols loaded. > CppClient.exe!GetFoo2(IDevice * pDevice, tagSAFEARRAY * * result) Line 18 C++ Symbols loaded. CppClient.exe!main() Line 35 C++ Symbols loaded. CppClient.exe!invoke_main() Line 79 C++ Non-user code. Symbols loaded. CppClient.exe!__scrt_common_main_seh() Line 288 C++ Non-user code. Symbols loaded. CppClient.exe!__scrt_common_main() Line 331 C++ Non-user code. Symbols loaded. CppClient.exe!mainCRTStartup(void * __formal) Line 17 C++ Non-user code. Symbols loaded. kernel32.dll!BaseThreadInitThunk() Unknown Non-user code. Symbols loaded. ntdll.dll!RtlUserThreadStart() Unknown Non-user code. Symbols loaded. </pre> <br /> 사실, 이번에도 원인은 같을 것입니다. 반환 값에 대해서 VT_ARRAY|VT_I4 조합을 했던 것처럼 인자에 대해서도 역시 VT_I4를 했을 테니까요.<br /> <br /> 실제로 인자 형식을 지정한 L"\x6003" 값은 VARENUM의 정의 중,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // wtypes.h enum VARENUM { //...[생략]... VT_I4 = 3, //...[생략]... VT_UI4 = 19, //...[생략]... VT_ARRAY = 0x2000, VT_BYREF = 0x4000, //...[생략]... } ; </pre> <br /> 아래의 3개 값이 조합한 것임을 유추할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > VT_ARRAY = 0x2000, VT_BYREF = 0x4000, VT_I4 = 3, </pre> <br /> 따라서, VT_I4만 VT_UI4로 바꿔서 다음과 같이 맞춰주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > inline HRESULT GetFoo2(<span style='color: blue; font-weight: bold'>IDevice* pDevice</span>, SAFEARRAY** result) { return _com_dispatch_method(pDevice, 0x60020001, DISPATCH_METHOD, VT_EMPTY, NULL, <span style='color: blue; font-weight: bold'>L"\x6013"</span>, result); } </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;' > SAFEARRAY *pArrayResult = nullptr; GetFoo2(mp_device, &pArrayResult); </pre> <br /> <img onclick='toggle_img(this)' class='imgView' alt='cs_cpp_interop_ui4_2.png' src='/SysWebRes/bbs/cs_cpp_interop_ui4_2.png' /><br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 이런 식의 문제가 발생했을 때 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-idispatch'>IDispatch</a>를 이용한 원시적인 호출을 하면서 접근하는 것도 원인 파악에 도움이 됩니다. 가령, 위에서 구현한 GetFoo, GetFoo2에 대해서는 다음과 같이 <a target='tab' href='GetIDsOfNames'>GetIDsOfNames</a>, <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke'>Invoke</a>를 직접 호출하는 식으로 접근할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ...[생략]... { DISPID dispId; LPOLESTR lpDesc = (LPOLESTR)L"GetFoo"; HRESULT hr = mp_device->GetIDsOfNames(IID_NULL, &lpDesc, 1, 0, &dispId); CComVariant varResult; DISPPARAMS params = { nullptr, nullptr, 0, 0 }; hr = mp_device->Invoke(dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, &varResult, nullptr, nullptr); if (hr != S_OK) { throw 1; } } { DISPID dispId; LPOLESTR lpDesc = (LPOLESTR)L"GetFoo2"; HRESULT hr = mp_device->GetIDsOfNames(IID_NULL, &lpDesc, 1, 0, &dispId); CComVariant* varParams = new CComVariant[1]; SAFEARRAY* pArray = nullptr; varParams[0].pparray = &pArray; varParams[0].vt = VT_ARRAY | VT_BYREF | VT_UI4; DISPPARAMS params = { varParams, nullptr, 1, 0 }; hr = mp_device->Invoke(dispId, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, nullptr, nullptr, nullptr); if (hr != S_OK) { throw 1; } } </pre> <br /> 이 정도면.. 대충 모두 설명한 것 같군요. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1967&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1487
(왼쪽의 숫자를 입력해야 합니다.)