C# - CoCreateInstanceEx 사용 예제 코드
CoCreateInstanceEx Win32 API는,
CoCreateInstanceEx function (combaseapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstanceex
C#에서 대체 방법을 제공하므로 직접 사용해야 할 필요는 거의 없습니다.
Guid clsid = new Guid("{469afbdf-084f-4dc9-904f-9e824c48bc37}");
{
Type targetType = Type.GetTypeFromCLSID(clsid);
object comObj = Activator.CreateInstance(targetType);
Console.WriteLine($"{comObj}");
}
단지, 지금까지 다뤘던 Interop 예제와는 다른 면이 있기 때문에,
Win32 Interop - 크기가 정해지지 않은 배열을 C++에서 C#으로 전달하는 경우
; https://www.sysnet.pe.kr/2/0/737
C#에서 Union 구조체 다루기
; https://www.sysnet.pe.kr/2/0/728
How to Interop DISPPARAMS
; https://www.sysnet.pe.kr/2/0/617
[in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/811
[in,out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810
살펴보겠습니다. ^^
가장 중요한 코드는 C++의 MULTI_QI 타입인데,
typedef struct tagMULTI_QI
{
const IID *pIID; // [입력] IID의 값이 담겨 있는 주소에 대한 포인터
IUnknown *pItf; // [출력] COM 개체의 IID에 해당하는 인터페이스 포인터
HRESULT hr;
} MULTI_QI;
보는 바와 같이 지난 글에서 다룬 GUID의 포인터가,
C# - GUID 타입 전용의 UnmanagedType.LPStruct
; https://www.sysnet.pe.kr/2/0/12444
들어 있어 unsafe 구문의 마샬링을 해야 합니다. 그런데,
PInvoke 사이트에는 다음과 같이 C# 구조체를 정의하고 있습니다.
// https://www.pinvoke.net/default.aspx/Structures/MULTI_QI.html
[StructLayout(LayoutKind.Sequential)]
struct MULTI_QI
{
[MarshalAs(UnmanagedType.LPStruct)] public Guid pIID;
[MarshalAs(UnmanagedType.Interface)] public object pItf;
public int hr;
}
이상하죠?
분명히 LPStruct는 구조체 내에서 정의한 GUID를 마샬링하지 못합니다. 실제로 PInovke에 나온 MULTI_QI와 CoCreateInstanceEx 코드로 예제를 구성하면,
[DllImport("ole32.dll")]
static extern int CoCreateInstance([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
IntPtr pUnkOuter, CLSCTX dwClsCtx, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[In, Out] ref IntPtr ppv);
[DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
static extern void CoCreateInstanceEx(
[In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
CLSCTX dwClsCtx,
IntPtr pServerInfo,
uint cmq,
[In, Out] MULTI_QI[] pResults);
public static void CoCreateInstance(Guid clsid, out object comObject)
{
comObject = null;
Guid iUnk = new Guid("{00000000-0000-0000-C000-000000000046}");
MULTI_QI[] mqs = new MULTI_QI[1];
mqs[0].pIID = iUnk;
CoCreateInstanceEx(clsid, null, CLSCTX.CLSCTX_INPROC_SERVER, IntPtr.Zero, 1, mqs);
}
예의 그 예외가 발생합니다.
Unhandled Exception: System.TypeLoadException: Cannot marshal field 'pIID' of type 'CreateTest.MULTI_QI': Invalid managed/unmanaged type combination (this value type must be paired with Struct).
at System.StubHelpers.MngdNativeArrayMarshaler.ConvertContentsToNative(IntPtr pMarshalState, Object& pManagedHome, IntPtr pNativeHome)
at CreateTest.PInvokeSample.CoCreateInstanceEx(Guid rclsid, Object pUnkOuter, CLSCTX dwClsCtx, IntPtr pServerInfo, UInt32 cmq, MULTI_QI[] pResults)
at CreateTest.PInvokeSample.CoCreateInstance(Guid clsid, Object& comObject) in C:\CreateTest\PInvokeSample.cs:line 27
at CreateTest.Program.Main(String[] args) in C:\CreateTest\Program.cs:line 20
어쨌든, 정상적인 PInvoke 호출을 하려면 MULTI_QI 구조체를 이런 식으로 구성해야 합니다.
/*
typedef struct tagMULTI_QI
{
const IID *pIID;
IUnknown *pItf;
HRESULT hr;
} MULTI_QI;
*/
internal struct MULTI_QI_PTR
{
public IntPtr pIID;
public IntPtr pItf;
public int hr;
}
그리고 호출 시 pIID 멤버를 포인터 처리하는 작업을 직접 코딩해야 합니다. 이와 관련해
지난 예제에서 구조체 처리를 (LPStruct는 불가능하니) 2가지 방식으로 다뤘는데요, 다음은 첫 번째 방식으로 처리한 것이고,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace ComUtil
{
public static class NativeMethods
{
[DllImport("ole32.dll", CharSet = CharSet.Unicode, EntryPoint = "CoCreateInstanceEx")]
static internal extern uint CoCreateInstanceEx([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
CLSCTX dwClsCtx,
IntPtr pServerInfo,
uint cmq,
IntPtr pResults);
public static uint CoCreateInstanceEx(Guid clsid, out object comObject)
{
Guid iUnk = new Guid("{00000000-0000-0000-C000-000000000046}");
return CoCreateInstanceEx(clsid, iUnk, out comObject);
}
private static uint CoCreateInstanceEx(Guid clsid, Guid qIID, out object comObject)
{
MULTI_QI_PTR mq = default;
IntPtr pGuid = IntPtr.Zero;
IntPtr pBuf = IntPtr.Zero;
comObject = null;
try
{
pGuid = Marshal.AllocHGlobal(16);
pBuf = Marshal.AllocHGlobal(Marshal.SizeOf(mq));
Marshal.StructureToPtr(qIID, pGuid, true);
mq.pIID = pGuid;
Marshal.StructureToPtr(mq, pBuf, true);
uint result = CoCreateInstanceEx(clsid, null, CLSCTX.INPROC_SERVER, IntPtr.Zero, 1, pBuf);
if (result == 0)
{
mq = (MULTI_QI_PTR)Marshal.PtrToStructure(pBuf, typeof(MULTI_QI_PTR));
comObject = Marshal.GetObjectForIUnknown(mq.pItf);
}
return result;
}
finally
{
if (pGuid != IntPtr.Zero)
{
Marshal.FreeHGlobal(pGuid);
}
if (pBuf != IntPtr.Zero)
{
Marshal.FreeHGlobal(pBuf);
}
}
}
}
}
using ComUtil;
using System;
namespace CreateTest
{
class Program
{
public const string comGuid = "{469afbdf-084f-4dc9-904f-9e824c48bc37}";
[STAThread]
static void Main(string[] args)
{
Guid clsid = new Guid(comGuid);
{
uint result = NativeMethods.CoCreateInstanceEx(clsid, out object comObj);
Console.WriteLine($"{result.ToString("x")} {comObj}");
}
}
}
}
반면, 두 번째 방식으로 포인터의 힘을 빌리면 좀 더 간단하게 처리할 수 있습니다.
unsafe internal struct MULTI_QI
{
public Guid* pIID;
public IntPtr pItf;
public int hr;
}
[DllImport("ole32.dll")]
static unsafe internal extern uint CoCreateInstanceEx([In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
CLSCTX dwClsCtx,
IntPtr pServerInfo,
uint cmq,
MULTI_QI* pResults);
private static uint CoCreateInstanceEx(Guid clsid, Guid qIID, out object comObject)
{
MULTI_QI mq = default;
comObject = null;
unsafe
{
mq.pIID = &qIID;
uint result = CoCreateInstanceEx(clsid, null, CLSCTX.INPROC_SERVER, IntPtr.Zero, 1, &mq);
if (result == 0)
{
comObject = Marshal.GetObjectForIUnknown(mq.pItf);
}
return result;
}
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]