성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법</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# - .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> </pre> <a name='com_create'><a/> <br /> COM 개체는 1) CoCreateInstance (<a target='tab' href='https://www.sysnet.pe.kr/2/0/12446'>CoCreateInstanceEx</a>)+ Marshal.GetObjectForIUnknown <a target='tab' href='조합으로 구하는 방법'>조합으로 구하는 방법</a>과 2) <a target='tab' href='https://www.sysnet.pe.kr/2/0/1180'>Activator.CreateInstance를 이용한 방법</a>으로 생성할 수 있습니다.<br /> <br /> 그리고, 또 한 가지 방법이 있는데요, 예전에 Cookbook 번역 글을 쓰면서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > (번역글) .NET Internals Cookbook Part 3 - Initialization tricks ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11871#17'>https://www.sysnet.pe.kr/2/0/11871#17</a> </pre> <br /> 내용에 언급한 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.coclassattribute'>CoClass 특성</a>을 이용하는 것입니다.<br /> <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;' > // Visual C/C++ IDL 형식으로 정의한 인터페이스 명세 [ object, <span style='color: blue; font-weight: bold'>uuid(047b2642-74d5-4fb9-8e89-023dfe4aed75),</span> <span style='color: blue; font-weight: bold'>dual,</span> nonextensible, pointer_default(unique) ] <span style='color: blue; font-weight: bold'>interface IATLSimpleObject</span> : IDispatch { [id(1)] HRESULT ShowInfo(); }; [ uuid(c2c30609-4731-486c-9cd2-04ec1dd9cafb), version(1.0), ] library ATLProject1Lib { importlib("stdole2.tlb"); [ <span style='color: blue; font-weight: bold'>uuid(f69406d6-912b-4092-a847-2707abfc4dac)</span> ] <span style='color: blue; font-weight: bold'>coclass ATLSimpleObject { [default] interface IATLSimpleObject; };</span> }; </pre> <br /> 대개의 경우, 저 IDL로 빌드한 COM 개체는 DLL 내부에 Type Library를 갖고 있을 것이고, 비주얼 스튜디오에서 "Add Reference..." 기능으로 추가해 "Interop.ATLProject1.dll"과 같은 Interop DLL을 (자동으로) 생성해 참조할 것입니다.<br /> <br /> 실제로, 해당 Interop DLL에는 COM 개체가 갖는 인터페이스 정의에 따라 CoClass 특성을 이용한 타입들이 생성됩니다. 그리고, 당연히 이것을 우리도 만들 수 있기 때문에 부가적인 Interop DLL에 의존하는 것을 원치 않는 경우 써먹을 수 있는 좋은 방법이 됩니다.<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")] // Interface의 Guid public interface IATLSimpleObject { void ShowInfo(); } [ComImport] [Guid("047b2642-74d5-4fb9-8e89-023dfe4aed75")] // Interface의 Guid <span style='color: blue; font-weight: bold'>[CoClass(typeof(ATLSimpleObjectClass))]</span> public interface ATLSimpleObject : IATLSimpleObject { } [ComImport] [Guid("f69406d6-912b-4092-a847-2707abfc4dac")] // coclass의 Guid [ClassInterface(ClassInterfaceType.None)] public class ATLSimpleObjectClass : ATLSimpleObject { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] public virtual extern void ShowInfo(); } </pre> <br /> 간단하죠? ^^ "<a target='tab' href='https://www.sysnet.pe.kr/2/0/11871#17'>(번역글) .NET Internals Cookbook Part 3 - Initialization tricks</a>" 글의 내용과 일치하기 때문에 사실 더 설명할 것이 없습니다.<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;' > IATLSimpleObject myObj = <span style='color: blue; font-weight: bold'>new ATLSimpleObject()</span>; Console.WriteLine($"myObj == {myObj}"); // myObj == ATLSimpleObjectClass myObj.ShowInfo(); </pre> <br /> 인터페이스로 정의된 ATLSimpleObject에 대해 new를 하고 있는데요, 사실 원래는 불가능한 문법이지만 이를 가능하게 만드는 것이 바로 "<a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.comimportattribute'>ComImport 특성</a>"입니다. 별도의 COM 개체에 구현 코드를 포함하고 있기 때문에 new 구문을 통과시키라는 표시 역할을 하는데요, 만약 이 특성을 빼고 컴파일하려고 하면 경고 및 오류를 만나게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // CoClass는 있는데, ComImport가 없는 경우 // 경고 발생: warning CS0684: 'ATLSimpleObject' interface marked with 'CoClassAttribute' not marked with 'ComImportAttribute' [Guid("047b2642-74d5-4fb9-8e89-023dfe4aed75")] [CoClass(typeof(ATLSimpleObjectClass))] public interface ATLSimpleObject : IATLSimpleObject { } // 이와 함께, ATLSimpleObject에 ComImport가 없으므로 오류 발생 // error CS0144: Cannot create an instance of the abstract type or interface 'ATLSimpleObject' IATLSimpleObject myObj = new ATLSimpleObject(); </pre> <br /> 또 한 가지 더 재미있는 곳이 CoClass로 연결시킨 ATLSimpleObjectClass 타입인데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [ComImport] [Guid("f69406d6-912b-4092-a847-2707abfc4dac")] [ClassInterface(ClassInterfaceType.None)] public <span style='color: blue; font-weight: bold'>class</span> ATLSimpleObjectClass : ATLSimpleObject { [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] public virtual <span style='color: blue; font-weight: bold'>extern</span> void ShowInfo(); } </pre> <br /> 분명히 class에 속하기 때문에 반드시 상속한 인터페이스에 대해 구현 코드가 있어야만 합니다. 하지만, 구현은 외부 COM에서 제공하기 때문에 메서드 정의에 extern을 추가했습니다.<br /> <br /> 은근히, C# 자체라는 언어적인 문법과 함께 여러 환경적인 코드들 간의 상호작용이 복잡하게 얽혀 있는 것이 바로 "닷넷"입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 주의할 점이 있는데요, 자칫 실수라도 해서 위의 규칙을 어기는 경우 난감한 오류를 만날 수 있습니다.<br /> <br /> 일례로, 위와 같은 정의에서 ComImport 특성을 제거하면 컴파일 시에는 오류가 발생하지 않지만 실행 시 ShowInfo 멤버를 호출하는 코드에서 다음과 같은 예외가 발생합니다.<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.Security.SecurityException: ECall methods must be packaged into a system module. at ATLSimpleObjectClass.ShowInfo() at Program.Main(String[] args) </pre> <br /> 또한, ShowInfo 메서드에 적용했던 MethodImpl 특성이 없다면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] public virtual extern void ShowInfo(); </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;' > [.NET Core/5+의 경우] Unhandled exception. System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (0x8007000B) at ATLSimpleObjectClass.ShowInfo() at Program.Main(String[] args) [.NET Framework의 경우] Unhandled Exception: System.TypeLoadException: Could not load type 'ATLSimpleObjectClass' from assembly 'ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because the method 'ShowInfo' has no implementation (no RVA). at Program.Main(String[] args) </pre> <br /> 이런 오류는 정말 잡기도 힘듭니다. ^^; 또 한 가지 더 짚고 넘어가야 할 것이 있다면, interface에 InterfaceType을 정확히 일치시켜야 한다는 점입니다. C/C++ IDL에는 해당 인터페이스를 "dual"로 명시했기 때문에, 우리가 정의한 인터페이스도 그에 따라야 합니다.<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'>[InterfaceType(ComInterfaceType.InterfaceIsDual)]</span> // 기본값이 InterfaceIsDual이므로 현재 예제에서는 정의하지 않아도 잘 동작 public interface IATLSimpleObject { void ShowInfo(); } </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;' > [dual에 대해 InterfaceIsIUnknown으로 설정한 경우] Fatal error. Internal CLR error. (0x80131506) at Program.Main(System.String[]) [또는,] Fatal error. Internal CLR error. (0x80131506) at System.Reflection.AssemblyName.ParseAsAssemblySpec(Char*, Void*) at Program.Main(System.String[]) </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [dual에 대해 InterfaceIsIDispatch로 설정한 경우] Unhandled exception. System.Runtime.InteropServices.COMException (0x8002801D): Library not registered. (0x8002801D (TYPE_E_LIBNOTREGISTERED)) at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters) at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Object[] aArgs, Boolean[] aArgsIsByRef, Int32[] aArgsWrapperTypes, Type[] aArgsTypes, Type retType) at ATLSimpleObjectClass.ShowInfo() at Program.Main(String[] args) </pre> <br /> 혹은, 오류가 안 나더라도 마치 문제가 없는 것처럼 ShowInfo 메서드 호출이 지나갈 수도 있는데, 그에 따른 발생 상황은 확률적일 수 있지만, 어쨌든 정상 동작은 하지 않습니다. (결국 vtable의 함수 포인터 수가 변화되기 때문인 것으로, 그 offset 위치에 있는 함수 포인터 위치에 따라 오동작의 현상이 달라집니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 위의 COM 개체 생성 방식은 C#으로 만든 COM Server에 대해서도 잘 동작합니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2117&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1970
(왼쪽의 숫자를 입력해야 합니다.)