首页 文章

C#编译器如何检测COM类型?

提问于
浏览
165

EDIT: 我把结果写成blog post .


C#编译器有点神奇地处理COM类型 . 例如,这个陈述看起来很正常......

Word.Application app = new Word.Application();

......直到你意识到 Application 是一个界面 . 在接口上调用构造函数? Yoiks!这实际上被转换为对Type.GetTypeFromCLSID()的调用以及对Activator.CreateInstance的另一次调用 .

此外,在C#4中,您可以为 ref 参数使用非ref参数,并且编译器只是添加一个局部变量以通过引用传递,丢弃结果:

// FileName parameter is *really* a ref parameter
app.ActiveDocument.SaveAs(FileName: "test.doc");

(是的,有一堆参数丢失了 . 不是可选参数好吗?:)

我正在尝试调查编译器的行为,我没有假装第一部分 . 我可以毫无问题地完成第二部分:

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

[ComImport, GuidAttribute("00012345-0000-0000-0000-000000000011")]
public interface Dummy
{
    void Foo(ref int x);
}

class Test
{
    static void Main()
    {
        Dummy dummy = null;
        dummy.Foo(10);
    }
}

我想能够写:

Dummy dummy = new Dummy();

虽然 . 显然它会在执行时爆炸,但那没关系 . 我只是在试验 .

编译器为链接的COM PIA( CompilerGeneratedTypeIdentifier )添加的其他属性是什么?

4 回答

  • 141

    在你和迈克尔之间,你几乎把这些东西放在一起 . 我认为这是它的工作原理 . (我没有编写代码,所以我可能会稍微误解它,但我很确定这是怎么回事 . )

    如果:

    • 你是一个接口类型_315174

    • 接口类型有一个已知的coclass,和

    • 您正在使用此界面的"no pia"功能

    然后代码生成为(IPIAINTERFACE)Activator.CreateInstance(Type.GetTypeFromClsid(GUID OF COCLASSTYPE))

    如果:

    • 你是_315176的接口类型,和

    • 接口类型有一个已知的coclass,和

    • 您没有在此界面中使用"no pia"功能

    然后生成代码就像你说“new COCLASSTYPE()”一样 .

    乔恩,如果你对这些东西有疑问,请随时给我或Sam直接打扰 . 仅供参考,Sam是此功能的专家 .

  • 35

    只是为迈克尔的回答添加一点确认:

    以下代码编译并运行:

    public class Program
    {
        public class Foo : IFoo
        {
        }
    
        [Guid("00000000-0000-0000-0000-000000000000")]
        [CoClass(typeof(Foo))]
        [ComImport]
        public interface IFoo
        {
        }
    
        static void Main(string[] args)
        {
            IFoo foo = new IFoo();
        }
    }
    

    你需要 ComImportAttributeGuidAttribute 来使它工作 .

    当你将鼠标悬停在 new IFoo() 上时,请注意信息:Intellisense正确地获取信息:很好!

  • 27

    我绝不是这方面的专家,但最近我偶然发现了我想要的东西:CoClass属性类 .

    [System.Runtime.InteropServices.CoClass(typeof(Test))]
    public interface Dummy { }
    

    coclass提供一个或多个接口的具体实现 . 在COM中,这种具体实现可以用支持COM组件开发的任何编程语言编写,例如, Delphi,C,Visual Basic等

    请参阅my answer to a similar question about the Microsoft Speech API,您可以在"instantiate"接口 SpVoice (但实际上,您正在实例化 SPVoiceClass ) .

    [CoClass(typeof(SpVoiceClass))]
    public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
    
  • 60

    好吧,这只是为迈克尔的回答增加了一点点(如果他愿意的话,欢迎加入它,在这种情况下我会删除它) .

    查看Word.Application的原始PIA,涉及三种类型(忽略事件):

    [ComImport, TypeLibType(...), Guid("..."), DefaultMember("Name")]
    public interface _Application
    {
         ...
    }
    
    [ComImport, Guid("..."), CoClass(typeof(ApplicationClass))]
    public interface Application : _Application
    {
    }
    
    [ComImport, ClassInterface(...), ComSourceInterfaces("..."), Guid("..."), 
     TypeLibType((short) 2), DefaultMember("Name")]
    public class ApplicationClass : _Application, Application
    {
    }
    

    由于Eric Lippert在another answer中谈到的原因,有两个接口 . 正如你所说,那里是 CoClass - 无论是类本身还是 Application 界面上的属性 .

    现在,如果我们在C#4中使用PIA链接,其中一些嵌入在生成的二进制文件中......但不是全部 . 只创建 Application 实例的应用程序最终会使用以下类型:

    [ComImport, TypeIdentifier, Guid("..."), CompilerGenerated]
    public interface _Application
    
    [ComImport, Guid("..."), CompilerGenerated, TypeIdentifier]
    public interface Application : _Application
    

    ApplicationClass - 可能是因为它将在执行时从真实的COM类型动态加载 .

    另一个有趣的事情是链接版本和非链接版本之间的代码差异 . 如果您反编译该行

    Word.Application application = new Word.Application();
    

    在引用的版本中,它最终为:

    Application application = new ApplicationClass();
    

    而在链接版本中它最终为

    Application application = (Application) 
        Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("...")));
    

    所以看起来"real" PIA需要 CoClass 属性,但链接版本不需要,因为编译器实际上没有引用 CoClass . 它必须动态地完成它 .

    我可能会尝试使用此信息伪造一个COM接口,看看我是否可以让编译器链接它...

相关问题