Como o compilador C # detecta tipos COM?

168

Edição: Eu escrevi os resultados como um post no blog .


O compilador C # trata os tipos COM de maneira um pouco mágica. Por exemplo, esta declaração parece normal ...

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

... até você perceber que Applicationé uma interface. Chamando um construtor em uma interface? Yoiks! Na verdade, isso é traduzido em uma chamada para Type.GetTypeFromCLSID()e outra para Activator.CreateInstance.

Além disso, no C # 4, você pode usar argumentos não-ref para refparâmetros, e o compilador apenas adiciona uma variável local para passar por referência, descartando os resultados:

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

(Sim, faltam vários argumentos. Os parâmetros opcionais não são bons? :)

Estou tentando investigar o comportamento do compilador e não estou conseguindo falsificar a primeira parte. Eu posso fazer a segunda parte sem nenhum problema:

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

Eu gostaria de poder escrever:

Dummy dummy = new Dummy();

Apesar. Obviamente, vai estragar no momento da execução, mas tudo bem. Estou apenas experimentando.

Os outros atributos adicionados pelo compilador para PIAs COM vinculados ( CompilerGeneratede TypeIdentifier) não parecem fazer o truque ... qual é o molho mágico?

Jon Skeet
fonte
11
Os parâmetros opcionais não são bons? OMI, não, eles não são bons. A Microsoft está tentando corrigir a falha nas interfaces COM do Office adicionando inchaço ao C #.
Mehrdad Afshari 07/07/2009
18
@ Mehrdad: Parâmetros opcionais são úteis além do COM, é claro. Você precisa ter cuidado com os valores padrão, mas entre eles e os argumentos nomeados, é muito mais fácil criar um tipo imutável utilizável.
9139 Jon Skeet
1
Verdade. Especificamente, parâmetros nomeados podem ser praticamente necessários para interoperabilidade com alguns ambientes dinâmicos. Claro, sem dúvida, é um recurso útil, mas isso não significa que ele seja gratuito. Custa simplicidade (uma meta de design explicitamente declarada). Pessoalmente, acho que o C # é incrível para os recursos que a equipe parou (caso contrário, poderia ter sido um clone de C ++). A equipe de C # é ótima, mas um ambiente corporativo dificilmente pode ser livre de políticas. Eu acho que Anders próprio não estava muito feliz sobre isso como ele afirmou em seu discurso PDC'08: "nos levou dez anos para voltar para onde estávamos."
Mehrdad Afshari 07/07/2009
7
Concordo que a equipe precisará acompanhar de perto a complexidade. O material dinâmico adiciona muita complexidade a pouco valor para a maioria dos desenvolvedores, mas alto valor para alguns desenvolvedores.
9139 Jon Skeet
1
Eu já vi desenvolvedores de framework começando a discutir seus usos em muitos lugares. Na IMO, é hora de encontrarmos um bom uso para dynamic... estamos acostumados a digitar estático / forte para ver por que isso importa fora do COM.
usar o seguinte comando

Respostas:

145

De maneira alguma sou um especialista nisso, mas recentemente me deparei com o que acho que você deseja: a classe de atributo CoClass .

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

Uma coclasse fornece implementações concretas de uma ou mais interfaces. No COM, essas implementações concretas podem ser escritas em qualquer linguagem de programação que suporte o desenvolvimento de componentes COM, por exemplo, Delphi, C ++, Visual Basic, etc.

Veja minha resposta a uma pergunta semelhante sobre a API do Microsoft Speech , onde você pode "instanciar" a interface SpVoice(mas, na verdade, você está instanciando SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
fonte
2
Muito interessante - tentarei mais tarde. Os tipos de PIA vinculados ainda não possuem CoClass. Talvez seja algo a ver com o processo de vinculação - Eu vou dar uma olhada na PIA originais ...
Jon Skeet
64
+1 por ser incrível ao escrever a resposta aceita quando Eric Lippert e Jon Skeet também responderam;) Não, realmente, +1 por mencionar o CoClass.
OregonGhost 10/07/2009
61

Entre você e Michael, você quase reuniu as peças. Eu acho que é assim que funciona. (Eu não escrevi o código, por isso posso estar indeciso, mas tenho certeza de que é assim que funciona.)

E se:

  • você é "novo" em um tipo de interface e
  • o tipo de interface possui uma coclasse conhecida e
  • você está usando o recurso "no pia" para esta interface

o código é gerado como (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

E se:

  • você é "novo" em um tipo de interface e
  • o tipo de interface possui uma coclasse conhecida e
  • você NÃO está usando o recurso "sem pia" para esta interface

o código é gerado como se você tivesse dito "new COCLASSTYPE ()".

Jon, sinta-se à vontade para me incomodar ou Sam diretamente, se você tiver perguntas sobre essas coisas. Para sua informação, Sam é o especialista nesse recurso.

Eric Lippert
fonte
36

Ok, isso é apenas para dar um pouco mais de detalhe à resposta de Michael (ele pode adicioná-lo se quiser, caso em que removerei esse).

Observando o PIA original para Word.Application, existem três tipos envolvidos (ignorando os eventos):

[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
{
}

Existem duas interfaces por razões que Eric Lippert fala em outra resposta . E, como você disse, existe o CoClass- tanto em termos da própria classe quanto do atributo na Applicationinterface.

Agora, se usarmos a ligação PIA em C # 4, parte disso será incorporada no binário resultante ... mas não em tudo. Um aplicativo que apenas cria uma instância de Applicationacaba com estes tipos:

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

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

Não ApplicationClass- presumivelmente porque isso será carregado dinamicamente do tipo COM real no momento da execução.

Outra coisa interessante é a diferença no código entre a versão vinculada e a versão não vinculada. Se você descompilar a linha

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

na versão referenciada , termina como:

Application application = new ApplicationClass();

enquanto que na versão vinculada termina como

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

Portanto, parece que o PIA "real" precisa do CoClassatributo, mas a versão vinculada não precisa, porque não existe um CoClasscompilador que possa realmente fazer referência. Tem que fazer isso dinamicamente.

Eu posso tentar falsificar uma interface COM usando essas informações e ver se consigo o compilador vinculá-lo ...

Jon Skeet
fonte
27

Apenas para adicionar um pouco de confirmação à resposta de Michael:

O código a seguir compila e executa:

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

Você precisa do ComImportAttributee do GuidAttributepara que ele funcione.

Observe também as informações ao passar o mouse sobre o new IFoo(): Intellisense capta corretamente as informações: Nice!

Rasmus Faber
fonte
obrigado, eu estava tentando, mas estava faltando o atributo ComImport , mas quando vou para o código-fonte eu estava trabalhando usando F12 apenas mostra CoClass e Guid , por que isso?
Ehsan Sajjad