¿Cómo detecta el compilador de C # los tipos COM?

168

EDITAR: He escrito los resultados como una publicación de blog .


El compilador de C # trata los tipos COM de forma algo mágica. Por ejemplo, esta declaración parece normal ...

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

... hasta que te das cuenta de que Applicationes una interfaz. ¿Llamando a un constructor en una interfaz? Yoiks! Esto en realidad se traduce en una llamada a Type.GetTypeFromCLSID()y otra a Activator.CreateInstance.

Además, en C # 4, puede usar argumentos que no sean de referencia para los refparámetros, y el compilador simplemente agrega una variable local para pasar por referencia, descartando los resultados:

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

(Sí, faltan muchos argumentos. ¿No son buenos los parámetros opcionales? :)

Estoy tratando de investigar el comportamiento del compilador, y no puedo falsificar la primera parte. Puedo hacer la segunda parte sin 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);
    }
}

Me gustaría poder escribir:

Dummy dummy = new Dummy();

aunque. Obviamente estallará en el momento de la ejecución, pero está bien. Solo estoy experimentando.

Los otros atributos agregados por el compilador para los PIA COM vinculados ( CompilerGeneratedy TypeIdentifier) no parecen hacer el truco ... ¿cuál es la salsa mágica?

Jon Skeet
fuente
11
¿No son agradables los parámetros opcionales? OMI, no, no son agradables. Microsoft está tratando de corregir la falla en las interfaces COM de Office agregando hinchazón a C #.
Mehrdad Afshari
18
@Mehrdad: los parámetros opcionales son útiles más allá de COM, por supuesto. Debe tener cuidado con los valores predeterminados, pero entre ellos y los argumentos con nombre, es mucho más fácil construir un tipo inmutable utilizable.
Jon Skeet el
1
Cierto. Específicamente, los parámetros con nombre pueden ser prácticamente necesarios para interoperabilidad con algunos entornos dinámicos. Claro, sin duda, es una función útil, pero eso no significa que sea gratis. Cuesta simplicidad (un objetivo de diseño explícitamente establecido). Personalmente, creo que C # es increíble por las características que el equipo dejó (de lo contrario, podría haber sido un clon de C ++). El equipo de C # es excelente, pero un entorno corporativo difícilmente puede estar libre de políticas. Yo supongo Anders mismo no estaba muy feliz por esto como declaró en su discurso PDC'08: "nos llevó diez años para volver a donde estábamos."
Mehrdad Afshari
77
Estoy de acuerdo en que el equipo deberá vigilar de cerca la complejidad. El material dinámico agrega mucha complejidad por poco valor para la mayoría de los desarrolladores, pero alto valor para algunos desarrolladores.
Jon Skeet el
1
He visto a desarrolladores de marcos comenzar a discutir sus usos en muchos lugares. En mi opinión, es solo el tiempo hasta que encontremos un buen uso para dynamic... estamos demasiado acostumbrados al tipeo estático / fuerte para ver por qué sería importante fuera de COM.
chakrit

Respuestas:

145

De ninguna manera soy un experto en esto, pero recientemente me topé con lo que creo que quieres: la clase de atributo CoClass .

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

Una coclass proporciona implementaciones concretas de una o más interfaces. En COM, tales implementaciones concretas se pueden escribir en cualquier lenguaje de programación que admita el desarrollo de componentes COM, por ejemplo, Delphi, C ++, Visual Basic, etc.

Vea mi respuesta a una pregunta similar sobre la API de Microsoft Speech , donde puede "instanciar" la interfaz SpVoice(pero realmente, está creando instancias SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
fuente
2
Muy interesante, lo intentaré más tarde. Sin embargo, los tipos de PIA vinculados no tienen CoClass. Tal vez sea algo que tenga que ver con el proceso de vinculación. Veré el PIA original ...
Jon Skeet el
64
+1 por ser increíble al escribir la respuesta aceptada cuando Eric Lippert y Jon Skeet también respondieron;) No, realmente, +1 por mencionar CoClass.
OregonGhost
61

Entre tú y Michael casi tienes las piezas juntas. Creo que así es como funciona. (No escribí el código, por lo que podría estar un poco equivocado, pero estoy bastante seguro de que así es como funciona).

Si:

  • usted es "nuevo" en un tipo de interfaz y
  • el tipo de interfaz tiene una coclase conocida y
  • USTED ESTÁ usando la función "no pia" para esta interfaz

entonces el código se genera como (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Si:

  • usted es "nuevo" en un tipo de interfaz y
  • el tipo de interfaz tiene una coclase conocida y
  • NO está utilizando la función "no pia" para esta interfaz

entonces el código se genera como si dijeras "nuevo COCLASSTYPE ()".

Jon, siéntete libre de molestarme a mí o a Sam directamente si tienes preguntas sobre estas cosas. FYI, Sam es el experto en esta característica.

Eric Lippert
fuente
36

De acuerdo, esto es solo para poner un poco más de carne en la respuesta de Michael (puede agregarla si lo desea, en cuyo caso eliminaré esta).

Mirando el PIA original para Word. Aplicación, hay tres tipos involucrados (ignorando los 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
{
}

Hay dos interfaces por razones de las que Eric Lippert habla en otra respuesta . Y allí, como dijiste, está el CoClass- tanto en términos de la clase misma como del atributo en la Applicationinterfaz.

Ahora, si usamos enlaces PIA en C # 4, algo de esto está incrustado en el binario resultante ... pero no todo. Una aplicación que solo crea una instancia de Applicationtermina con estos tipos:

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

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

No ApplicationClass, probablemente porque se cargará dinámicamente desde el tipo COM real en el momento de la ejecución.

Otra cosa interesante es la diferencia en el código entre la versión vinculada y la versión no vinculada. Si descompilas la línea

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

en la versión referenciada termina como:

Application application = new ApplicationClass();

mientras que en la versión vinculada termina como

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

Así que parece que el "verdadero" PIA necesita el CoClassatributo, pero la versión vinculada no lo hace porque no es un CoClasscompilador puede en realidad referencia. Tiene que hacerlo dinámicamente.

Podría intentar falsificar una interfaz COM utilizando esta información y ver si puedo conseguir que el compilador lo vincule ...

Jon Skeet
fuente
27

Solo para agregar un poco de confirmación a la respuesta de Michael:

El siguiente código compila y ejecuta:

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

Necesita tanto el ComImportAttributecomo el GuidAttributepara que funcione.

También tenga en cuenta la información cuando pase el mouse sobre new IFoo(): Intellisense recoge correctamente la información: ¡Bien!

Rasmus Faber
fuente
gracias, estaba intentando pero me faltaba el atributo ComImport , pero cuando voy al código fuente que estaba trabajando usando F12 solo muestra CoClass y Guid , ¿por qué es eso?
Ehsan Sajjad