¿Cómo detecta el comstackdor C # los tipos COM?

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


El comstackdor de C # trata los tipos COM de forma mágica. Por ejemplo, esta afirmación parece normal …

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

… hasta que te das cuenta de que la Application es una interfaz. ¿Llamar 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 ref para los parámetros ref , y el comstackdor solo 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 agradables los parámetros opcionales? 🙂

Intento investigar el comportamiento del comstackdor, y no puedo fingir la primera parte. Puedo hacer la segunda parte sin ningún 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 va a explotar en el momento de la ejecución, pero está bien. Solo estoy experimentando.

Los otros atributos agregados por el comstackdor para PIA COM combinados ( CompilerGenerated y TypeIdentifier ) no parecen hacer el truco … ¿cuál es la salsa mágica?

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

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

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

Consulte mi respuesta a una pregunta similar sobre Microsoft Speech API , donde puede “crear instancias” de la interfaz SpVoice (pero en realidad está instanciando SPVoiceClass ).

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

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 equivocándolo, pero estoy bastante seguro de que así es como funciona).

Si:

  • eres “nuevo” en un tipo de interfaz, y
  • el tipo de interfaz tiene un coclass conocido, y
  • ESTÁS utilizando la función “no pia” para esta interfaz

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

Si:

  • eres “nuevo” en un tipo de interfaz, y
  • el tipo de interfaz tiene un coclass conocido, y
  • NO estás usando la función “no pia” para esta interfaz

entonces el código se genera como si dijera “new COCLASSTYPE ()”.

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

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

Mirando el PIA original para Word.Application, 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 las razones que Eric Lippert menciona en otra respuesta . Y allí, como dijiste, está CoClass , tanto en términos de la clase en sí como del atributo en la interfaz de la Application .

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

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

No ApplicationClass : presumiblemente porque se cargará dinámicamente desde el tipo de 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 descomstacks 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 enlazada termina como

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

Entonces parece que la PIA “real” necesita el atributo CoClass , pero la versión vinculada no lo hace porque no hay una CoClass la que el comstackdor pueda realmente hacer referencia. Tiene que hacerlo dinámicamente.

Podría intentar falsificar una interfaz COM utilizando esta información y ver si puedo hacer que el comstackdor la vincule …

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

El siguiente código se comstack 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 el ComImportAttribute y el GuidAttribute para que funcione.

También tenga en cuenta la información al pasar el mouse sobre el new IFoo() : Intellisense recoge correctamente la información: ¡Agradable!