从ComImport类调用函数并不会按预期失败

时间:2015-12-16 03:15:08

标签: c# com com-interop

我试图验证我尝试通过COM使用的课程是否按预期工作。不幸的是,它似乎在一个应该失败的电话上取得成功:

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转换到界面工作,这表明guid正常。 (并且它无法投射到其他guid,所以它并非随机成功)

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

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

那么为什么我没有看到例外?

1 个答案:

答案 0 :(得分:3)

问题是您正在重新声明界面,并且新定义与原始定义不同。

Guids是正常的,但是在下面,QueryInterface实现检查GUID,并返回指向实现的指针 - 这是接口vtable,并且相对于该地址计算方法地址(当编译方法的调用时) ,将该方法的偏移量添加到该地址以获取实际地址。)

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

但是,在原始界面中,InitializeFromTemplate之前还有56个其他方法,因为有一个继承链:

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

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

作为一项实验,如果您在InitializeFromTemplate IX509CertificateRequestCmc2之前添加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程序集时,我不明白为什么不简单地使用这些生成的互操作类。这是完整的示例,其行为符合预期:

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);
        }
    }
}

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