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

Posted

技术标签:

【中文标题】C# 编译器如何检测 COM 类型?【英文标题】:How does the C# compiler detect COM types? 【发布时间】:2010-11-08 18:37:21 【问题描述】:

编辑:我已将结果写成blog post。


C# 编译器对 COM 类型的处理有点神奇。比如这个语句看起来很正常……

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

...直到您意识到Application 是一个接口。在接口上调用构造函数?呸!这实际上被转化为对Type.GetTypeFromCLSID() 的调用和对Activator.CreateInstance 的调用。

另外,在 C# 4 中,您可以对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)添加的其他属性似乎不起作用...什么是神奇的酱汁?

【问题讨论】:

可选参数不是很好吗? IMO,不,他们不好。微软正试图通过向 C# 添加膨胀来修复 Office COM 接口中的缺陷。 @Mehrdad:当然,可选参数在 COM 之外很有用。您需要注意默认值,但在它们和命名参数之间,构建可用的不可变类型要容易得多。 是的。具体来说,命名参数可能是实际上需要与某些动态环境进行互操作的。当然,毫无疑问,这是一个有用的功能,但这并不意味着它是免费的。它以简单为代价(明确规定的设计目标)。就个人而言,我认为 C# 对于团队遗漏的功能来说是惊人的(否则,它可能是 C++ 的克隆)。 C# 团队很棒,但企业环境很难没有政治。我安德斯本人对此并不十分高兴,正如他在 PDC'08 演讲中所说:“我们花了十年时间才回到原来的位置。” 我同意团队需要密切关注复杂性。动态的东西增加了很多复杂性,对于大多数开发者来说价值不大,但对于一些开发者来说价值很高。 我已经看到框架开发人员开始在很多地方讨论它的用途。 IMO 是时候找到 dynamic 的好用处了……我们太习惯于静态/强类型,不知道为什么它在 COM 之外很重要。 【参考方案1】:

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

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

一个 coclass 供应混凝土 一个或多个的实现 接口。在 COM 中,这样的具体 实现可以写成任何 支持 COM 的编程语言 组件开发,例如德尔福, C++、Visual Basic 等。

参见my answer to a similar question about the Microsoft Speech API,您可以在其中“实例化”接口SpVoice(但实际上,您正在实例化SPVoiceClass)。

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event  

【讨论】:

非常有趣 - 稍后会尝试。链接的 PIA 类型虽然没有 CoClass。也许这与链接过程有关 - 我将查看原始 PIA... +1,因为在 Eric Lippert 和 Jon Skeet 也回答时写出了公认的答案;)不,真的,+1 提到 CoClass。【参考方案2】:

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

以下代码编译运行:

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 正确地拾取信息:很好!

【讨论】:

谢谢,我正在尝试,但我缺少 ComImport 属性,但是当我转到源代码时,我正在使用 F12 只显示 CoClass还有 Guid,这是为什么呢?【参考方案3】:

好的,这只是为了让迈克尔的回答更加充实(如果他愿意,欢迎他添加它,在这种情况下我会删除它)。

查看 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 - 大概是因为它会在执行时从real COM 类型动态加载。

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

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

引用 版本中,它最终为:

Application application = new ApplicationClass();

而在 linked 版本中,它最终是

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

所以看起来“真正的”PIA 需要CoClass 属性,但链接版本不需要,因为编译器实际上可以引用CoClass没有。它必须动态执行。

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

【讨论】:

【参考方案4】:

在你和迈克尔之间,你几乎已经把所有的碎片拼凑在一起了。我认为这就是它的工作原理。 (我没有编写代码,所以我可能会稍微错误地陈述它,但我很确定它是这样的。)

如果:

您正在“新建”一个接口类型,并且 接口类型有一个已知的 coclass,并且 您正在为此界面使用“no pia”功能

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

如果:

您正在“新建”一个接口类型,并且 接口类型有一个已知的 coclass,并且 您没有为此界面使用“no pia”功能

然后生成代码,就像您说“new COCLASSTYPE()”一样。

Jon,如果您对这些内容有任何疑问,请随时直接向我或 Sam 提出问题。仅供参考,Sam 是此功能的专家。

【讨论】:

以上是关于C# 编译器如何检测 COM 类型?的主要内容,如果未能解决你的问题,请参考以下文章

.net中反编译过后代码怎么组合?(c#)

一段反编译后的C#代码段,不知道是啥意思,但是出错。

C#枚举类型的常用操作总结

C#提示不只定义了一个入口点,请使用/main进行编译以指定包含入口点的类型

如何在 C# 9 中复制/克隆记录?

求c# 通过Debug 文件反编译出其中的代码,能看见编译的代码就可以了,求这样的软件工具???