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


168

编辑:我已经将结果写为博客文章


C#编译器在某种程度上神奇地对待COM类型。例如,此语句看起来很正常...

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

...直到您意识到这Application是一个界面。在接口上调用构造函数?ik!实际上,这会转换为的调用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)添加的其他属性似乎并没有解决问题的方法……魔咒是什么?


11
可选参数不是很好吗?IMO,不,他们不好。微软正试图通过向C#中添加膨胀功能来修复Office COM界面中的漏洞。
Mehrdad Afshari

18
@Mehrdad:可选参数当然比COM有用。您需要注意默认值,但是在默认值和命名参数之间,构建可用的不可变类型要容易得多。
乔恩·斯基特

1
真正。具体来说,与一些动态环境互操作实际上需要命名参数。当然,毫无疑问,这是一个有用的功能,但这并不意味着它是免费提供的。它花费了简单性(明确声明的设计目标)。就个人而言,我认为C#对于团队遗留下来的功能非常了不起(否则,它可能是C ++的克隆)。C#团队很棒,但是公司环境很难摆脱政治束缚。我安德斯本人对此并不十分满意,正如他在PDC'08演讲中所说的那样:“花了我们十年时间回到原来的状态。”
Mehrdad Afshari

7
我同意团队需要密切关注复杂性。动态的东西增加了很多复杂性,对于大多数开发者来说价值不大,但是对于某些开发者却价值很高。
乔恩·斯基特

1
我已经看到框架开发人员开始在许多地方讨论其用途。海事组织(IMO)只是时候,我们才找到了一个很好的用途dynamic...我们太习惯于进行静态/强类型键入,以了解在COM之外为何如此重要。
chakrit 2011年

Answers:


145

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

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

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

请参阅我对有关Microsoft Speech API的类似问题的回答,您可以在其中“实例化”接口SpVoice(但实际上,您正在实例化SPVoiceClass)。

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

2
非常有趣-稍后再试。链接的PIA类型没有CoClass。也许与链接过程有关-我将看一下原始的PIA ...
Jon Skeet 2009年

64
+1是因为在Eric Lippert和Jon Skeet也回答时写出了已接受的答案,因此很棒;)不,真的,+1是提到CoClass。
OregonGhost

61

在您和Michael之间,您几乎可以拼凑出所有内容。我认为这是这样的。(我没有编写代码,因此我可能会误称它,但是我很确定这是正确的。)

如果:

  • 您是“新”接口类型,并且
  • 接口类型具有已知的协类,并且
  • 您正在为此界面使用“ no pia”功能

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

如果:

  • 您是“新”接口类型,并且
  • 接口类型具有已知的协类,并且
  • 您没有为此界面使用“ no pia”功能

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

乔恩(Jon),如果您对此内容有疑问,请直接与我或萨姆(Sam)纠缠。仅供参考,Sam是此功能的专家。


36

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

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

[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在另一个答案中谈到了这些原因。正如您所说,就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("...")));

所以它看起来像“真实” PIA需要的CoClass属性,但链接的版本,不会因为有没有一个CoClass编译器实际上可以参考。它必须动态地做到这一点。

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


27

只是为了对迈克尔的答案添加一点确认:

以下代码编译并运行:

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

您需要ComImportAttribute和都GuidAttribute可以使用它。

当您将鼠标悬停在上时,还请注意以下信息new IFoo():Intellisense会正确获取该信息:很好!


谢谢,我正在尝试,但是我没有ComImport属性,但是当我转到源代码时,使用F12的工作仅显示CoClassGuid,为什么呢?
Ehsan Sajjad 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.