성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입</h1> <p> 이번 글은 .NET Conf 2023에 있었던 아래의 영상에 대해 베낀 것입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > COM Source Generation: An evolution of COM interop in .NET | .NET Conf 2023 ; <a target='tab' href='https://youtu.be/DZd1SGd7dSU'>https://youtu.be/DZd1SGd7dSU</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 마이크로소프트가 Native AOT를 위해 전방위로 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12223'>Source Generator</a>를 활용하고 있습니다. 일례로, <a target='tab' href='https://www.sysnet.pe.kr/2/0/13159#17082'>ASP.NET Core의 경우 MapGet과 같은 메서드 호출을 Request Delegate Generator(Souce Generator + Interceptor) 기능을 활용해 해결</a>하는가 하면 Json Serializer/Deserializer도 System.Text.Json에 컴파일 시 소스 코드 생성을 통해 (성능뿐만 아니라) AOT 문제까지 해결하고 있습니다. 게다가 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13466'>지난 글의 LibraryImport 옵션</a>도 AOT 상황에서 문제가 될 수 있는 DllImport를 소스 코드 생성기를 활용해 해결(?)하고 있습니다.<br /> <br /> 그리고, 마침내 COM Interop 관련한 것에도 ^^ 소스 코드 생성을 제공하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 8: COM Source Generator * Generate implementations of ComWrappers * Marshal in C# * Compatible with trimming and Native AOT * [ComImport] => [GeneratedComInterface] * [ComVisible(true)] => [GeneratedComClass] * [MarshalAs(UnmanagedType.Interface)] in [LibraryImport] * "Lightbulb' Code Fixers </pre> <br /> 어떻게 되는지 간단하게 실습을 해볼까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, PublishAot를 통한 AOT 컴파일 시에는 (내부적으로 Reflection을 사용하는) <a target='tab' href='https://www.sysnet.pe.kr/2/0/13469#com_create'>Activator를 통한 방법</a>은 지원하지 않습니다. 다른 방법인 Win32 Interop을 활용해야 하는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("Ole32.dll")] internal static extern int CoCreateInstance(Guid rclsid, IntPtr pUnkOuter, CLSCTX dwClsCtx, Guid riid, out nint ppv); nint pValue; int hr = CoCreateInstance(CLSID_ATLSimpleObject, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IATLSimpleObject, out pValue); object simpleObj = Marshal.GetObjectForIUnknown((IntPtr)pValue); </pre> <br /> 물론, 순수 포인터로 다루기에 별다르게 마샬링 작업이 필요 없으므로 DllImport를 통한 코드 수행이 AOT 환경에서도 잘됩니다. 하지만, 문제는 그 포인터로부터 닷넷에서 활용할 수 있는 개체를 호출하는 방법이 마땅치 않습니다. 이를 자동화하는 코드가 (위의 예처럼) Marshal.GetObjectForIUnknown으로 제공하긴 하지만, 이것 역시 내부적으로 Reflection을 사용하기 때문에 AOT 환경에서 사용할 수 없습니다. 실제로 위의 코드는 PublishAot 옵션과 함께 "dotnet publish" 배포까지는 잘 통과하지만, 실행 시 다음과 같은 오류가 발생합니다.<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: System.NotSupportedException: COM Interop requires ComWrapper instance registered for marshalling. at System.Runtime.InteropServices.ComWrappers.ComObjectForInterface(IntPtr) + 0x76 at Program.Main(String[] args) + 0x94 at ConsoleApp1!<BaseAddress>+0xdaf70 </pre> <br /> 혹은 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("Ole32.dll")] internal static extern int CoCreateInstance( Guid rclsid, IntPtr pUnkOuter, CLSCTX dwClsCtx, Guid riid, <span style='color: blue; font-weight: bold'>[MarshalAs(UnmanagedType.Interface)] out object instance</span>); object objValue; CoCreateInstance(CLSID_ATLSimpleObject, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IUnknown, out objValue); </pre> <br /> 일반적인 빌드라면 정상 동작했겠지만, PublishAot 옵션을 추가한 경우에는 컴파일에는 경고를, 실행 시에는 런타임 오류를 발생시킵니다.<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'> [컴파일 경고]<br /> warning IL2050: P/invoke method 'Program.CoCreateInstance(Guid, nint, CLSCTX, Guid, out Object)' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.<br /> </div><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'> [실행 시 예외]<br /> Unhandled exception. System.NotSupportedException: Built-in COM has been disabled via a feature switch. See <a target='tab' href='https://aka.ms/dotnet-illink/com'>https://aka.ms/dotnet-illink/com</a> for more information.<br /> at System.Runtime.InteropServices.Marshal.GetObjectForIUnknownNative(IntPtr pUnk)<br /> at Program.Main(String[] args) <br /> </div><br /> <br /> 결국 MarshalAs 특성을 지정했다면 (사용자가 직접 Marshal.GetObjectForIUnknown을 호출했던 작업을) 런타임 내부에서 자동으로 하는 것에 불과하기 때문에 오류가 나는 것은 당연합니다.<br /> <br /> 자, 이제 남은 방법은, 직접 마샬링 작업을 처리하면 됩니다. 어차피 CoCreateInstance가 반환한 pValue는 COM 개체의 this 값, 즉 <a target='tab' href='https://www.sysnet.pe.kr/2/0/11169'>vtable을 가리키는 주소</a>로 시작하므로 이를 해석해 적절하게 함수 호출을 하면 되는데요, 예를 들어 아래의 코드는 AddRef와 Release 함수를 직접 호출하는 것을 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>nint pValue;</span> int hr = CoCreateInstance(CLSID_ATLSimpleObject, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IUnknown, <span style='color: blue; font-weight: bold'>out pValue</span>); <span style='color: blue; font-weight: bold'>nint vtable = *((nint *)pValue); nint *vtablePtr = (nint*)vtable;</span> // IUnknown 인터페이스 함수 3개 + IDispatch 인터페이스 함수 4개 + 사용자 COM 개체가 구현한 함수는 1개로 가정 // vtable의 함수에 대한 주소를 나열 for (int i = 0; i < (3 + 4 + 1); i ++) { nint pFunc = vtablePtr[i]; Console.WriteLine($"vtable[{i}] = {pFunc:X}"); } // 그중에서 IUnknown 인터페이스의 2개 함수(AddRef, Release)에 대한 포인터를 가져와서, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12374'>delegate* unmanaged</a>[Stdcall]<nint, int> addRefFunc = (delegate* unmanaged[Stdcall]<nint, int>)vtablePtr[1]; delegate* unmanaged[Stdcall]<nint, int> releaseFunc = (delegate* unmanaged[Stdcall]<nint, int>)vtablePtr[2]; int refCount = addRefFunc(pValue); // AddRef 직접 호출 Console.WriteLine(refCount); for (int i = 0; i < refCount; i ++) { Console.WriteLine(releaseFunc(pValue)); // Release 직접 호출 } /* 출력 결과 vtable[0] = 7FFBC6861D1B vtable[1] = 7FFBC6861681 vtable[2] = 7FFBC6861415 vtable[3] = 7FFBC68617DA vtable[4] = 7FFBC6861CE9 vtable[5] = 7FFBC6861947 vtable[6] = 7FFBC6861DBB vtable[7] = 7FFBC68618A2 2 1 0 */ </pre> <br /> <hr style='width: 50%' /><br /> <br /> 기존에는 런타임에 저 작업을 구성해 RCW/CCW로 연결했지만, AOT를 위해서는 저 작업을 컴파일 시점에 모두 마쳐야 합니다. 바로 그 목표를 해결하기 위해 .NET 8부터 추가된 것이 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshalling.generatedcominterfaceattribute'>GeneratedComInterface</a> 특성(<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/standard/native-interop/qualify-net-types-for-interoperation#com-interface-inheritance-and-net'>문서</a>)입니다.<br /> <br /> 방법은 간단한데요, 원하는 COM 인터페이스의 정의를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [Guid("047b2642-74d5-4fb9-8e89-023dfe4aed75")] public interface IATLSimpleObject { void ShowInfo(); } </pre> <br /> GeneratedComInterface 특성을 추가 후, partial을 추가하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [Guid("047b2642-74d5-4fb9-8e89-023dfe4aed75")] <span style='color: blue; font-weight: bold'>[GeneratedComInterface()]</span> public <span style='color: blue; font-weight: bold'>partial</span> interface IATLSimpleObject { void ShowInfo(); } </pre> <br /> (partial에서 눈치채셨겠지만) 그럼 C# 컴파일러는 IATLSimpleObject의 마샬링 작업을 처리하는 소스 코드를 자동으로 생성해 줍니다. 그래서, 이제는 다음과 같이 Native 포인터로부터 .NET COM 개체를 얻어와 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > nint pValue; int hr = CoCreateInstance(CLSID_ATLSimpleObject, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IATLSimpleObject, <span style='color: blue; font-weight: bold'>out pValue</span>); <span style='color: blue; font-weight: bold'>IATLSimpleObject? simpleObj</span> = global::System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller<global::IATLSimpleObject>.ConvertToManaged(<span style='color: blue; font-weight: bold'>(void*)pValue</span>); global::System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller<global::IATLSimpleObject>.Free((void*)pValue); Console.WriteLine(simpleObj); // System.Runtime.InteropServices.Marshalling.ComObject <span style='color: blue; font-weight: bold'>simpleObj?.ShowInfo();</span> </pre> <br /> 그래도 ConvertToManaged와 Free에 대한 약간 귀찮은 작업이 추가되었는데요, 이것조차도 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13466'>.NET 7부터 추가된 LibraryImport를 사용</a>하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [LibraryImport("Ole32")] private static partial int CoCreateInstance( Guid rclsid, IntPtr pUnkOuter, CLSCTX dwClsContext, Guid riid, <span style='color: blue; font-weight: bold'>out IATLSimpleObject ppObj</span>); </pre> <br /> LibraryImport 특성에 대한 소스 코드 생성기는 저렇게 인자로 들어온 COM Interface에 GeneratedComInterface 특성이 부여되어 있다면 자동으로 ConvertToManaged, Free 작업을 처리하는 코드를 생성해 줍니다. 따라서 LibraryImport + GeneratedComInterface가 합작하면 간단하게 COM 개체를 사용할 수 있는 코드가 탄생하게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IATLSimpleObject pObj; int hr = CoCreateInstance(CLSID_ATLSimpleObject, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IATLSimpleObject, out pObj); Console.WriteLine(pObj); // System.Runtime.InteropServices.Marshalling.ComObject pObj.ShowInfo(); </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데 실제로 저 코드대로 따라 해 보면, COM 개체의 메서드를 호출하는 (대부분의) 경우 정작 예외가 발생하는 것을 보게 될 것입니다. ^^;<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Runtime.InteropServices.Marshalling.ComObject Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. at <IATLSimpleObject>F31946FAB9252FCD84CDCF1432DA985EC6ABBECBDC40F381FF3A34F83531C558D__InterfaceImplementation.global::IATLSimpleObject.ShowInfo() at Program.Main(System.String[]) </pre> <br /> 그 이유를, GeneratedComInterface 특성을 부여해 생성된 소스 코드에서 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > file unsafe partial interface InterfaceImplementation { internal static void** CreateManagedVirtualFunctionTable() { void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::IATLSimpleObject), sizeof(void*) * 4); { nint v0, v1, v2; <span style='color: blue; font-weight: bold'>global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out v0, out v1, out v2); vtable[0] = (void*)v0; vtable[1] = (void*)v1; vtable[2] = (void*)v2;</span> } { <span style='color: blue; font-weight: bold'>vtable[3] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int> )&ABI_ShowInfo;</span> } return vtable; } } </pre> <br /> 보는 바와 같이, vtable에 IUnknown의 함수 3개와, 사용자 구현한 함수를 offset 3번부터 이어서 초기화하고 있습니다. 다시 말해, IDispatch를 포함한 COM 개체, 예를 들어 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/midl/dual'>dual</a> 설정이 된 COM 개체들은 지원하지 않고 있는 것입니다.<br /> <br /> 실제로 마이크로소프트의 <a target='tab' href='https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6/#source-generated-com-interop'>블로그 문서</a>에서 다음과 같은 제약을 설명하고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > * No support for IDispatch-based interfaces. Support for these interfaces may be manually implemented using a local definition of the IDispatch interface. * No support for IInspectable-based interfaces. Use the CsWinRT tool to generate the interop code for these interfaces. * No support for apartment affinity. All COM objects are assumed to be free-threaded. Support for apartment affinity may be manually implemented using the StrategyBasedComWrappers type and custom strategy implementations. * No support for COM properties. These may be manually implemented as methods on the interface. * No support for COM events. These may be manually implemented using the underlying COM APIs. * No support for using the <a target='tab' href='https://www.sysnet.pe.kr/2/0/13469#body'>new keyword to activate a COM CoClass</a>. Use LibraryImportAttribute to P/Invoke to the CoCreateInstance API to activate the CoClass. </pre> <br /> 사실 이게 아주 심각한 제약인데요, 현존하는 거의 모든 COM 개체들은 IDispatch까지를 포함한 경우가 대부분이기 때문입니다. 따라서 만약 다른 사람이 만든 COM 개체라면 IUnknown만 기반으로 하는 COM 개체를 제공해 줄 때까지 기다려야 하고, 심지어 자신이 만든 COM 개체라고 해도 습관적으로 IDispatch를 지원했을 것이므로 일부러 그것을 제외한 COM 개체를 하나 더 제공해야만 합니다.<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;' > Tutorial: Use the ComWrappers API ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/standard/native-interop/tutorial-comwrappers'>https://learn.microsoft.com/en-us/dotnet/standard/native-interop/tutorial-comwrappers</a> </pre> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.comwrappers'>ComWrappers</a>를 상속해 적절한 수준으로, 가령 IDispatch의 4개 함수에 대한 기능은 아니더라도 vtable만 넘어가도록 초기화하는 등의 작업만 해주는 것으로 적절하게 소스 코드 생성기와 연계할 수 있을 듯합니다.<br /> <br /> 그런 의미에서, 어찌 보면 이런 상황이 이해는 갑니다. IDispatch에는 DISPARAMS나 VARIANT와 같은 복잡한 마샬링 작업을 수반하는 요소들이 있기 때문에 아마도 이번에 한꺼번에 구현해 릴리스하는 것은 부담이 있었을 것입니다. 이와 관련해 다음의 이슈를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Provide a COM source generator #66674 ; <a target='tab' href='https://github.com/dotnet/runtime/issues/66674'>https://github.com/dotnet/runtime/issues/66674</a> </pre> <a name='cp4'></a> <br /> "Checkpoint 4: IDispatch support" 항목에서, 요구가 있다면 IDispatch 지원을 고려해 볼 거라고 언급은 하고 있으니 아마도 점진적으로 이 부분은 개선이 될 것으로 예상합니다.<br /> <br /> 위의 문서에서 한 가지 더 재미있는 것은 "Checkpoint 2: WinForms compatibility"의 내용입니다. 현재 WinForms의 근간에 IUnknown 기반의 COM 개체가 주를 이루고, 소수의 IDispatch COM 개체가 <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/winauto/microsoft-active-accessibility'>접근성(accessibility)</a> 영역으로 사용되고 있다고 합니다. 따라서, 마이크로소프트 입장에서 향후 IDispatch AOT를 해결한다면 Windows Forms 응용 프로그램을 AOT 빌드할 수 있다는 것을 의미하기 때문에 충분히 논의가 될 가치는 있어 보입니다. (어쩌면, IDispatch를 Full Support로 구현하지는 않더라도 최소한 WinForms에 사용한 IDispatch interop이 가능한 범위로는 하지 않을까요? ^^)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 부가적인 이야기를 몇 개 덧붙이자면, 해당 <a target='tab' href='https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-6/#source-generated-com-interop'>블로그 문서</a>에 의하면, 이번 "Source Generated COM Interop"을 System.Transactions 패키지에 도입했다고 합니다. 즉, 기존에는 해당 dll을 사용하면 AOT가 불가능했지만 이제는 가능해졌다는 이야기일 것입니다.<br /> <br /> 아울러, AOT 실행에 대한 호환성 여부를 도와주는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers'>IsAotCompatible</a> 옵션이 추가되었다고 하는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- 생략 --> <span style='color: blue; font-weight: bold'><IsAotCompatible>true</IsAotCompatible></span> <!-- IsTrimmable, EnableTrimAnalyzer, EnableSingleFileAnalyzer, EnableAotAnalyzer 4개의 값을 true로 설정한 효과 --> </PropertyGroup> </Project> </pre> <br /> 자신의 프로젝트를 AOT로 마이그레이션하고 싶다면 저 옵션의 도움을 받아 경고가 발생하는 것마다 하나씩 처리해 나가면 되겠습니다. (아울러 "<a target='tab' href='https://devblogs.microsoft.com/dotnet/creating-aot-compatible-libraries/'>How to make libraries compatible with native AOT</a>" 글도 참고하시면 좋습니다.)<br /> <br /> 마지막으로, COM 인터페이스에 대한 소스 생성을 GeneratedComInterface로 하는 것처럼, 닷넷 managed 개체를 Native에 COM 개체로써 넘길 때에도 (AOT를 위한) 마샬링을 해결하는 용도로 GeneratedComClass 특성이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [GeneratedComClass] [Guid("...")] class GetStringImpl: IGetString { string? _string; public string? GetString() => _string; } </pre> <br /> 별다르게 어려울 것이 없으니 이에 대한 예제는 생략합니다. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2118&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <a name='yak_shaving'></a> <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# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13469'>https://www.sysnet.pe.kr/2/0/13469</a> C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13468'>https://www.sysnet.pe.kr/2/0/13468</a> C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13467'>https://www.sysnet.pe.kr/2/0/13467</a> C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13466'>https://www.sysnet.pe.kr/2/0/13466</a> MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13465'>https://www.sysnet.pe.kr/2/0/13465</a> C# - .NET 7부터 UnmanagedCallersOnly 함수 export 기능을 AOT 빌드에 통합 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13464'>https://www.sysnet.pe.kr/2/0/13464</a> .NET Core 3/5+ 기반의 COM Server를 registry 등록 없이 사용하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13461'>https://www.sysnet.pe.kr/2/0/13461</a> .NET 6+ 기반의 COM Server 내에 Type Library를 내장하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13460'>https://www.sysnet.pe.kr/2/0/13460</a> .NET Core 3/5+ 기반의 COM Server를 기존의 regasm처럼 등록하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13459'>https://www.sysnet.pe.kr/2/0/13459</a> .NET Core/5+ 기반의 COM Server를 tlb 파일을 생성하는 방법(tlbexp) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13458'>https://www.sysnet.pe.kr/2/0/13458</a> </pre> <br /> 이 정도면... 그야말로 <a target='tab' href='https://brunch.co.kr/@lesstif/4'>야크 털 깎기</a>의 전형적인 사례라고 볼 수 있겠죠? ^^;<br /> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 오류 상황에 대해 하나 더 정리하면, 위의 본문에서 예를 든 것은 C/C++ COM 개체의 경우였고, 만약 이것을 C# COM 개체로 테스트하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > nint pValue; int hr = CoCreateInstance(CLSID_MyNetCodeUnk, IntPtr.Zero, CLSCTX.INPROC_SERVER, IID_IMyNetCodeUnk, out pValue); </pre> <br /> CoCreateInstance 단계에서, PublishAot 옵션을 true로 설정하고 빌드(dotnet build)만 한 경우에는 hr 값이 0x800080a7(-2147450713)를 반환하며 오류가 발생합니다. 검색해 보면,<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://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md'>https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md</a> Error returned by hostfxr_get_runtime_delegate when managed feature support for native host is disabled. </pre> <br /> 여전히 "dotnet build" 상태는 관리 코드로 실행하는 중이라 "native host is disabled." 설명이 맞습니다. 다른 문구는 잘 이해는 안 가지만, ^^; 어쨌든, 정작 배포(dotnet publish)하고 나면 다시 정상적으로 동작을 합니다.<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1578
(왼쪽의 숫자를 입력해야 합니다.)