C# COM 개체를 C++에서 사용하는 예제
다음의 질문이 나온 김에,
C++ C# API 연동 관련
; https://www.sysnet.pe.kr/3/0/5546
정리를 해보겠습니다. ^^ 또한, 네이티브가 엮이므로 반드시 x86, x64를 선택해야 하는데 이 글은 x64 기준으로 설명합니다. x86에서 필요한 경우라면, 글에 나온 x64 옵션 등을 모두 x86 기준으로 바꾸면 됩니다.
우선, C# DLL(.NET 4.0)을 다음과 같이 만들겠습니다.
using System.Runtime.InteropServices;
using System;
using System.Windows.Forms;
namespace ClassLibrary1
{
[ComVisible(true)]
[Guid("23172f2f-a3d3-4180-97ae-7805f74a5a46")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyNetCode
{
void ShowDialog();
}
[ComVisible(true)]
[Guid("96A2754F-0F5A-446B-A974-3EC47C04B27F")]
public class MyNetCode : IMyNetCode
{
public void ShowDialog()
{
string platform = (IntPtr.Size == 4) ? "x86" : "x64";
MessageBox.Show($".NET Framework: {Environment.Version}, {platform}");
}
}
}
또한 프로젝트 설정에서 "Signing"을 선택해 "Sign the assembly"를 이용해 서명합니다. 이후, 빌드하고, 명령행에서 ./bin/Debug/ 디렉터리로 이동해 다음과 같은 명령어를 실행,
C:\temp\ClassLibrary1\ClassLibrary1\bin\Debug> tlbexp ClassLibrary1.dll /win64
Microsoft (R) .NET Framework Assembly to Type Library Converter 4.8.4084.0
Copyright (C) Microsoft Corporation. All rights reserved.
TlbExp : warning TX00131175 : When cross-compiling, all type library references should be included on the command line to ensure the correct bit-specific type libraries are loaded.
Assembly exported to 'C:\temp\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.tlb'
tlb 파일을 구해 놓습니다. 물론 위의 과정은 프로젝트 속성 창에서 "Register for COM interop" 옵션을 켜면 생략할 수 있지만 그런 경우 Visual Studio IDE를 관리자 권한으로 실행시켜야 하기 때문에 개인적으로 선호하지 않습니다.
어차피, TLB 파일은 인터페이스가 바뀌지 않는 한 재생성할 일이 없으므로 저런 식으로 그때그때 구하시는 것을 추천합니다.
C# DLL과 TLB 파일이 준비되었다면 이제 C++ Console 프로젝트를 생성하고 x64 타깃으로 바꾼 다음, 다음과 같이 코드를 작성합니다.
#include <combaseapi.h>
#include <Windows.h>
#import "../ClassLibrary1/bin/Debug/ClassLibrary1.tlb" no_namespace named_guids
int main()
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
{
IMyNetCode* pMyObject = NULL;
hr = ::CoCreateInstance(CLSID_MyNetCode, NULL, CLSCTX_INPROC_SERVER, IID_IMyNetCode,
(PVOID*)&pMyObject);
if (hr != S_OK)
{
printf("Failed - CoCreateInstance: %x(%d)\n", hr, hr);
return 1;
}
pMyObject->ShowDialog();
}
CoUninitialize();
return 0;
}
코드 작성은 이것으로 끝입니다.
자, 이제 Release 빌드를 하고, 다음의 바이너리 파일 2개를 대상 PC에 복사합니다.
- 닷넷 DLL 복사(c:\temp\ClassLibrary1.dll)
- C++ EXE 복사(c:\temp\ConsoleApplication1.exe)
이 글에서는 "C:\temp" 디렉터리에 복사했다고 가정하고 진행하겠습니다. 일단, 현재 상태로는 내 PC든 다른 PC든 정상적으로 실행되지 않습니다.
C:\temp>ConsoleApplication1.exe
Failed - CoCreateInstance: 80040154(-2147221164)
/* 0x80040154 == Class not registered */
왜냐하면, C# DLL의 위치를 찾을 방법이 없기 때문인데, 이것을 위해서는 regasm.exe를 실행해 레지스트리에 COM 관련 설정을 등록해야 합니다. 이를 위해서는 2가지 방법이 있는데요,
gacutil을 사용하는 경우, 일반 사용자 PC에 설치되어 있지 않으므로 regasm만 실행하는 방법이 더 나은데, 이 경우 옵션에 "/codebase"를 더해주면 됩니다. 따라서 관리자 권한의 창을 띄워 다음의 명령을 수행합니다.
C:\temp> C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm /codebase ClassLibrary1.dll
Microsoft .NET Framework 버전 4.7.3190.0용
Microsoft .NET Framework Assembly Registration Utility 버전 4.7.3190.0
Copyright (C) Microsoft Corporation. All rights reserved.
형식이 등록되었습니다.
끝입니다. 이제 ConsoleApplication1.exe를 실행하면 정상적으로 실행되는 것을 확인할 수 있습니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
시간 되시면, 다음의 글도 읽어 보시고. ^^
배열을 반환하는 C# COM 개체의 메서드를 C++에서 사용 시 메모리 누수 현상
; https://www.sysnet.pe.kr/2/0/12491
regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU
; https://www.sysnet.pe.kr/2/0/1286
regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0
; https://www.sysnet.pe.kr/2/0/1287
regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library
; https://www.sysnet.pe.kr/2/0/1288
regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항
; https://www.sysnet.pe.kr/2/0/1289
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]