从 ComImport class 调用函数没有像预期的那样失败

Calling function from ComImport class doesn't fail as expected

我正在尝试验证我尝试通过 COM 使用的 class 是否按预期工作。不幸的是,它似乎在一个应该失败的调用上成功了:

enum X509CertificateEnrollmentContext
{
    ContextUser = 0x1,
    ContextMachine = 0x2,
    ContextAdministratorForceMachine = 0x3
}

[ComImport(), Guid("884e2045-217d-11da-b2a4-000e7bbb2b09")]
class Cenroll { }

[Guid("728ab35d-217d-11da-b2a4-000e7bbb2b09")]
interface IX509CertificateRequestCmc2
{
    void InitializeFromTemplate(
        [In] X509CertificateEnrollmentContext Context,
        [In] IX509EnrollmentPolicyServer pPolicyServer,
        [In] IX509CertificateTemplate pTemplate);
}

static void Main(string[] args)
{
    var cr = new Cenroll();
    var cmc2 = (IX509CertificateRequestCmc2)cr;
    cmc2.InitializeFromTemplate(X509CertificateEnrollmentContext.ContextUser, null, null);
}

从 Cenroll 到界面的转换工作正常,这表明 guids 是正确的。 (并且它无法投射到其他 guid,所以它不是随机成功)

但是当我调用 InitializeFromTemplate 时,两个参数都设置为 null,它会成功。 documentation 表示结果应该是 E_POINTER 错误:

Return code - Description
E_POINTER - The pPolicyServer and pTemplate parameters cannot be NULL.

为什么我没有看到异常?

问题是你重新声明了接口,新的定义和原来的不一样

Guids 没问题,但在下面,QueryInterface 实现检查 GUID,returns 指向实现的指针——这是接口 vtable,方法地址是相对于这个地址计算的(当一个对方法的调用被编译,方法的偏移量被添加到这个地址以获得实际地址)。

在您的实现中,InitializeFromTemplate 是第一个方法,生成的客户端代码在 vtable 的开头调用该方法。

但是在原来的接口中,InitializeFromTemplate之前还有56个方法,因为有继承链:

IX509CertificateRequest (25 methods)
|
+-> IX509CertificateRequestPkcs7 (8 methods)
    |
    +-> IX509CertificateRequestCmc (23 methods)
        |
        +-> IX509CertificateRequestCmc2

certenroll.dll 中的函数地址遵循此布局,因此当您调用接口中声明的 InitializeFromTemplate 时,您调用的是链中的第一个方法,实际上是 IX509CertificateRequest::Initialize .

作为实验,如果您在 IX509CertificateRequestCmc2 中的 InitializeFromTemplate 之前添加 56 个虚拟方法,您将正确接收到异常:

[Guid("728ab35d-217d-11da-b2a4-000e7bbb2b09")]
interface IX509CertificateRequestCmc
{
    void fn1();
    void fn2();
    ...
    void fn56();
    void InitializeFromTemplate(...);
}

调用将抛出:CertEnroll::CX509CertificateRequestCmc::InitializeFromTemplate: Invalid pointer 0x80004003 (-2147467261)

当然,解决方案不是添加虚拟方法 :) 您应该使用生成的互操作类型,而不是提供您自己的类型。当您引用 certenroll 程序集时,我不明白您为什么不简单地使用那些生成的互操作 classes。这是按预期运行的完整示例:

using CERTENROLLLib;

namespace comcerttest
{
    class Program
    {
        static void Main(string[] args)
        {
            // If you are embedding the interop types, note that you must
            // remove the `Class` suffix from generated type name in order
            // to instantiate it. See link at the bottom for explanation:
            var cr = new CX509CertificateRequestCmc();
            var cmc2 = (IX509CertificateRequestCmc2)cr;
            cmc2.InitializeFromTemplate(X509CertificateEnrollmentContext.ContextUser, null, null);
        }
    }
}

这里解释了使用 class 与接口类型的问题: Using embedded interop types