让我们一起学MetaData API(2)
    上次简单地介绍了在win32下使用MetaData API的方法,本章在深入应用它的各种功能前,先介绍在C# 下运用的方法。因为C#用于.net编程实在是太方便了!本章的示例代码来自开源软件DILE(dotnet il editor),下载地址http://dile.sourceforge.net。
1、  在C#中引入COM
    C#中使用COM要用到.net与COM的Interop,所有的内容全部都是公开的,所以直接看代码。一般,需要引用什么接口,就建立一个相应的文件,比如要引用IMetaDataDispenserEx接口,我们就在工程中新建一个IMetaDataDispenserEx.cs,代码如下(代码引用自我写的injectReflector):

namespace injectReflector.com
{
    [ComImport, GuidAttribute("31BCFCE2-DAFB-11D2-9F81-00C04F79A0A3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IMetaDataDispenserEx
    {
        uint DefineScope(ref Guid rclsid, uint dwCreateFlags, ref Guid riid, [MarshalAs(UnmanagedType.Interface)]out object ppIUnk);
        uint OpenScope([MarshalAs(UnmanagedType.LPWStr)]string szScope, uint dwOpenFlags, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppIUnk);

        uint OpenScopeOnMemory(IntPtr pData, uint cbData, uint dwOpenFlags, ref Guid riid, [MarshalAs(UnmanagedType.Interface)]out object ppIUnk);

        uint SetOption(ref Guid optionid, [MarshalAs(UnmanagedType.Struct)]object value);

        uint GetOption(ref Guid optionid, [MarshalAs(UnmanagedType.Struct)]out object pvalue);

        uint OpenScopeOnITypeInfo([MarshalAs(UnmanagedType.Interface)]ITypeInfo pITI, uint dwOpenFlags, ref Guid riid, [MarshalAs(UnmanagedType.Interface)]out object ppIUnk);

        uint GetCORSystemDirectory([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]char[] szBuffer, uint cchBuffer, out uint pchBuffer);

        uint FindAssembly([MarshalAs(UnmanagedType.LPWStr)]string szAppBase, [MarshalAs(UnmanagedType.LPWStr)]string szPrivateBin, [MarshalAs(UnmanagedType.LPWStr)]string szGlobalBin, [MarshalAs(UnmanagedType.LPWStr)]string szAssemblyName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)]char[] szName, uint cchName, out uint pcName);

        uint FindAssemblyModule([MarshalAs(UnmanagedType.LPWStr)]string szAppBase, [MarshalAs(UnmanagedType.LPWStr)]string szPrivateBin, [MarshalAs(UnmanagedType.LPWStr)]string szGlobalBin, [MarshalAs(UnmanagedType.LPWStr)]string szAssemblyName, [MarshalAs(UnmanagedType.LPWStr)]string szModuleName, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5)]char[] szName, uint cchName, out uint pcName);
    }
}

    关键的几处,一是ComImport属性中的定义,包话GUID值和接口类型(IUnknown),这样我们的接口就定义OK了。随后就是在接口中定义COM中的所有函数,由于是Interface,所以这里的定义中不包含任何代码。

2、  C#中使用本地数据类型
    接口函数的定义中用到了许多MarshalAs,这是C#与本地数据类型的转换。《COM本质论》中给出了一张表,列举了所有的需要转换和C#自动转换的数据类型。看下代码中用到的:
无需转换:uint、Guid
需要转换:
object  [MarshalAs(UnmanagedType.Interface)]
object  [MarshalAs(UnmanagedType.Struct)]
string  [MarshalAs(UnmanagedType.LPWStr)]
char[]  [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 5或4)]

    其中string的转换也许是最常用的,特别是在调用本地dll时。指针倒反而简单了,IntPtr直接使用。

3、  使用Interface
    定义完接口后,在代码中如何使用呢?首先要定义这些接口的变量:
    public static IMetaDataDispenserEx dispenser;
    public static IMetaDataImport import;
    随后就可以直接使用接口的方法。看下示例,同样来自injectReflector中:

        public static string GetMethodDefName(uint token)
        {
            string szRet;
            uint bclass=0;
            char[] szName=new char[MAX_NAME_LENGTH];
            uint ccount;
            uint pAttr;
            IntPtr pBlob;
            uint bcount;
            uint pRVA;
            uint flags;

            import.GetMethodProps(token, out bclass, szName, (uint)MAX_NAME_LENGTH, out ccount, out pAttr, out pBlob, out bcount, out pRVA, out flags);
            if(ccount==0)
            {
                szRet = token.ToString();
            }
            else
            {
                szRet = new string(szName, 0, Convert.ToInt32(ccount) - 1);
            }

            if(bclass!=0)
            {
                szRet = GetTypeDefRefName(bclass) +"."+ szRet;
            }
            return szRet;
        }

     这段代码是根据一个token取得该token所对应的方法的名称。值得注意的仍然是类型的转换,看下IMetaDataImport::GetMethodProps的原型定义:
   HRESULT GetMethodProps (
        mdMethodDef         mb, 
        mdTypeDef           *pClass, 
        LPWSTR              szMethod, 
        ULONG               cchMethod, 
        ULONG               *pchMethod,
        DWORD               *pdwAttr, 
        PCCOR_SIGNATURE     *ppvSigBlob, 
        ULONG               *pcbSigBlob, 
        ULONG               *pulCodeRVA, 
        DWORD               *pdwImplFlags
    );
    对照代码可以看到uint代替了大多数定义,包括mdMethodDef、mdTypeDef、ulong、dword,还有两个转换是LPWSTR就直接传递char[],指针PCCOR_SIGNATURE直接用IntPtr代替。所有的MetaData接口用到的类型基本上就这么多了。
    最后,需要使用哪个接口的某个方法,就直接用C#的语法进行调用:
import.GetMethodProps
import.GetFieldProps
import.GetTypeSpecFromToken

4、  小结
    完整的代码示例请参照开始给出的DILE。本文没有什么难度,只是介绍下C#中使用MetaData COM接口的方法,但是MetaData API的威力确是无穷的。以后有时间再介绍吧。