从 importlib 编辑的未注册类型库访问引用 ITypeInfo 的 ITypeInfo 会导致 TYPE_E_CANTLOADLIBRARY 错误

Accessing an ITypeInfo that references an ITypeInfo from an importlib-ed unregistered type library causes TYPE_E_CANTLOADLIBRARY error

我正在使用 .NET (System.Runtime.InteropServices.ComTypes) 中的自动化 API 检查我自己生成的类型库 Bar.tlb(见下文)。此类型库声明了一个接口 IBar,它继承自在导入的类型库 Foo.tlb 中定义的接口 IFoo。检查表示 IBarITypeInfo 会导致异常。 (代码如下。)

在开始我的代码之前,下面是我如何生成 Bar.tlb 类型库。

Bar.idl:

[uuid(32E81FDD-BCB0-481B-AD3C-3ED04BFA7D1F)]
library Bar
{
    importlib("Foo.tlb");

    [uuid(CF062BE8-86D2-4D9B-8D1D-D889A77DA876)]
    interface IBar : IFoo { };
}

Foo.idl:

[uuid(22E81FDD-BCB0-481B-AD3C-3ED04BFA7D1E)]
library Foo
{
    importlib("stdole32.tlb");

    [uuid(BF062BE8-86D2-4D9B-8D1D-D889A77DA875)]
    interface IFoo : IUnknown { };
}

我使用以下命令编译了两个 IDL 文件,这些命令成功且没有任何错误或警告:

midl.exe /mktyplib203 /env win32 /i … /tlb Foo.tlb Foo.idl
midl.exe /mktyplib203 /env win32 /i … /tlb Bar.tlb Bar.idl

现在我要做的是:

using System.Runtime.InteropServices;
using ITypeLib = System.Runtime.InteropServices.ComTypes.ITypeLib;
using ITypeInfo = System.Runtime.InteropServices.ComTypes.ITypeInfo;

static class Program
{
    [DllImport("oleaut32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
    static extern ITypeLib LoadTypeLibEx(string path, REGKIND regkind);

    enum REGKIND { REGKIND_NONE = 2 }

    public static void Main()
    {
        ITypeLib typeLib = LoadTypeLibEx(@"C:\Path\To\Bar.tlb", REGKIND.REGKIND_NONE);
        ITypeInfo typeInfo;
        typeLib.GetTypeInfo(0, out typeInfo);
        IntPtr typeAttrPtr;
        typeInfo.GetTypeAttr(out typeAttrPtr); //! COMException: TYPE_E_CANTLOADLIBRARY
        …                                      //                (HRESULT 0x80029c4a)
    }
}

在标有//!的行抛出异常。检查的 ITypeInfoIBar 接口的

我知道自动化 API 一定无法找到继承的接口 IFoo,它包含在另一个未注册的类型库中。

但显然应该可以检查 Bar.tlbOleView.exe 管理得很好:

(是的,提示无法重建外部类型库的文件名,这是因为我没有注册Foo.tlb。我担心的不是这个。)

如果 OleView.exe 可以检查 IBar 而不会崩溃,为什么我的代码会因为像 typeInfo.GetTypeAttr() 这样简单的事情而崩溃?我该如何解决这个问题?

TL;DR: 自动化 API 似乎在幕后访问当前工作目录中的类型库以解析 ITypeInfo 引用。这个问题可以通过这样做来解决:

// using static System.IO.Directory;
SetCurrentDirectory(@"C:\Path\To");
ITypeLib typeLib = LoadTypeLibEx("Bar.tlb", REGKIND.REGKIND_NONE);

而不是:

ITypeLib typeLib = LoadTypeLibEx(@"C:\Path\To\Bar.tlb", REGKIND.REGKIND_NONE);

我进行了更多实验,发现如果 Foo.tlb 存在于同一目录中,OleView.exe 只能成功检查 Bar.tlb 中的 IBar 类型详细信息。一旦我将 Foo.tlb 移到其他地方,OleView.exe 就会像我自己的代码一样失败:

我想查看哪些 API 调用 OleView.exe 用于访问类型库,所以我检查了:

dumpbin.exe /imports OleView.exe
dumpbin.exe /exports C:\WINDOWS\…\oleaut32.dll

并发现它引用了 LoadTypeLib — 而不是 LoadTypeLibEx。这两个函数的不同之处在于它们如何进行类型库注册。所以我查找了 MSDN documentation for LoadTypeLib 并发现了这个:

"LoadTypeLib will not register the type library if the path of the type library is specified."

这让我产生了重写代码的想法,如本答案开头所示。

然而,重要的是要指出,与引用的文本所暗示的相反,它不是传递给 LoadTypeLibEx 的路径中是否存在目录的问题 — 这是对Directory.SetCurrentDirectory 解决了错误。 以下方法也有效:

// using static System.IO.Directory;
SetCurrentDirectory(@"C:\Path\To");
ITypeLib typeLib = LoadTypeLibEx("C:\Path\To\Bar.tlb", REGKIND.REGKIND_NONE);