C# 관리 코드에서 IMetaDataDispenserEx, IMetaDataImport 관련 인터페이스를 얻는 방법
이에 대해서는 다음의 글에 잘 나와 있습니다.
Introduction to CLR metadata
; https://iobservable.net/blog/2013/05/12/introduction-to-clr-metadata/
전체 소스 코드를 실어보면 다음과 같습니다.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace ConsoleApplication1
{
[ComImport, GuidAttribute("E5CB7A31-7512-11D2-89CE-0080C792E5D8")]
class CorMetaDataDispenserExClass
{
}
[ComImport, GuidAttribute("31BCFCE2-DAFB-11D2-9F81-00C04F79A0A3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMetaDataDispenserEx
{
uint DefineScope(ref Guid rclsid, uint dwCreateFlags, ref Guid riid, out object ppIUnk);
uint OpenScope(string szScope, uint dwOpenFlags, ref Guid riid, out object ppIUnk);
uint OpenScopeOnMemory(IntPtr pData, uint cbData, uint dwOpenFlags, ref Guid riid, out object ppIUnk);
uint SetOption(ref Guid optionid, object value);
uint GetOption(ref Guid optionid, out object pvalue);
uint OpenScopeOnITypeInfo(ITypeInfo pITI, uint dwOpenFlags, ref Guid riid, out object ppIUnk);
uint GetCORSystemDirectory(char[] szBuffer, uint cchBuffer, out uint pchBuffer);
uint FindAssembly(string szAppBase, string szPrivateBin, string szGlobalBin, string szAssemblyName, char[] szName, uint cchName, out uint pcName);
uint FindAssemblyModule(string szAppBase, string szPrivateBin, string szGlobalBin, string szAssemblyName, string szModuleName, char[] szName, uint cchName, out uint pcName);
}
[ComImport, GuidAttribute("7DAC8207-D3AE-4C75-9B67-92801A497D44"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMetaDataImport
{
void CloseEnum(IntPtr hEnum);
uint GetScopeProps(char[] szName, uint cchName, out uint pchName, ref Guid pmvid);
uint GetTypeRefProps(uint tr, out uint ptkResolutionScope, char[] szName, uint cchName, out uint pchName);
uint ResolveTypeRef(uint tr, ref Guid riid, out object ppIScope, out uint ptd);
uint EnumMethods(ref IntPtr phEnum, uint cl, uint[] rMethods, uint cMax, out uint pcTokens);
uint GetMethodProps(uint mb, out uint pClass, char[] szMethod, uint cchMethod, out uint pchMethod, out uint pdwAttr, out IntPtr ppvSigBlob, out uint pcbSigBlob, out uint pulCodeRVA, out uint pdwImplFlags);
uint GetMemberRefProps(uint mr, out uint ptk, char[] szMember, uint cchMember, out uint pchMember, out IntPtr ppvSigBlob, out uint pbSigBlob);
// for brevity, the rest of the methods are omitted
}
class Program
{
static void Main(string[] args)
{
Guid clsid = new Guid("E5CB7A31-7512-11D2-89CE-0080C792E5D8");
Type dispenserType = Type.GetTypeFromCLSID(clsid);
object dispenser = Activator.CreateInstance(dispenserType);
var dispenserEx = dispenser as IMetaDataDispenserEx;
Guid metaDataImportGuid = new Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44");
object rawScope;
uint hr = dispenserEx.OpenScope("ClassLibrary1.dll", 0, ref metaDataImportGuid, out rawScope);
var import = rawScope as IMetaDataImport;
Console.WriteLine(import);
}
}
}
그런데, dispenserEx.OpenScope 코드에서 다음과 같은 오류가 발생합니다.
Managed Debugging Assistant 'InvalidVariant' has detected a problem in 'C:\...\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe'.
Additional information: An invalid VARIANT was detected during a conversion from an unmanaged VARIANT to a managed object. Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss.
아마 저 때 당시에는 Managed Debugging Assistant가 해당 변환을 오류로 인식하지 않은 듯 한데... 어쨌든 지금은 명백히 예외를 던지고 있습니다. 릴리스 버전으로 빌드해도 다음과 같이 예외가 발생합니다.
Unhandled Exception: System.Runtime.InteropServices.InvalidOleVariantTypeException: Specified OLE variant is invalid.
at System.StubHelpers.ObjectMarshaler.ConvertToManaged(IntPtr pSrcVariant)
at ConsoleApplication1.IMetaDataDispenserEx.OpenScope(String szScope, UInt32 dwOpenFlags, Guid& riid, Object& ppIUnk)
at ConsoleApplication1.Program.Main(String[] args) in C:\...\ConsoleApplication1\Program.cs:line 68
해결책은 간단합니다. ^^ 다음과 같이 OpenScope 메서드에 마샬링 관련 힌트를 주시면 됩니다.
uint OpenScope(string szScope, uint dwOpenFlags, ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out object ppIUnk);
IMetaDataDispenserEx를 이용해 IMetaDataImport를 구했으면 비관리 코드에서 했던 것처럼 동일한 방식으로 사용해 주시면 됩니다. ^^
string path = typeof(ClassLibrary1.Class1).Assembly.Location;
uint hr = dispenserEx.OpenScope(path, 0, ref metaDataImportGuid, out rawScope);
{
var import = rawScope as IMetaDataImport;
{
uint typeDef = 0; // (uint)typeof(ClassLibrary1.Class1).MetadataToken;
import.FindTypeDefByName("ClassLibrary1.Class1", 0, out typeDef);
var hEnum = IntPtr.Zero;
var methodTokens = new uint[1024];
var methodName = new char[1024];
uint len;
import.EnumMethods(ref hEnum, typeDef, methodTokens, (uint)methodTokens.Length, out len);
for (var i = 0; i < len; i++)
{
uint tmpTypeDef;
uint attrs;
var sig = IntPtr.Zero;
uint sigLen;
uint rva;
uint flags;
uint methodNameLen;
hr = import.GetMethodProps(methodTokens[i], out tmpTypeDef, methodName, (uint)methodName.Length, out methodNameLen, out attrs, out sig, out sigLen, out rva, out flags);
var curName = new string(methodName, 0, (int)methodNameLen - 1);
Console.WriteLine(curName);
}
import.CloseEnum(hEnum);
Marshal.ReleaseComObject(import);
}
Marshal.ReleaseComObject(rawScope);
}
Marshal.ReleaseComObject(dispenser);
Marshal.ReleaseComObject(dispenserEx);
(
첨부 파일은 위의 소스 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]