Wie erkennt der C # -Compiler COM-Typen?

168

EDIT: Ich habe die Ergebnisse als Blog-Beitrag geschrieben .


Der C # -Compiler behandelt COM-Typen etwas magisch. Zum Beispiel sieht diese Aussage normal aus ...

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

... bis Sie erkennen, dass dies Applicationeine Schnittstelle ist. Einen Konstruktor auf einer Schnittstelle aufrufen? Yoiks! Dies wird tatsächlich in einen Anruf an Type.GetTypeFromCLSID()und einen anderen an übersetzt Activator.CreateInstance.

Darüber hinaus können Sie in C # 4 Nicht-Ref-Argumente für refParameter verwenden, und der Compiler fügt lediglich eine lokale Variable hinzu, die als Referenz übergeben werden soll, und verwirft die Ergebnisse:

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

(Ja, es fehlen eine Reihe von Argumenten. Sind optionale Parameter nicht nett? :)

Ich versuche, das Verhalten des Compilers zu untersuchen, und ich kann den ersten Teil nicht vortäuschen. Ich kann den zweiten Teil ohne Probleme machen:

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

Ich möchte schreiben können:

Dummy dummy = new Dummy();

obwohl. Natürlich wird es zur Ausführungszeit knallen, aber das ist okay. Ich experimentiere nur.

Die anderen Attribute, die der Compiler für verknüpfte COM PIAs ( CompilerGeneratedund TypeIdentifier) hinzugefügt hat, scheinen nicht den Trick zu tun ... Was ist die magische Sauce?

Jon Skeet
quelle
11
Sind optionale Parameter nicht nett? IMO, nein sie sind nicht nett. Microsoft versucht, den Fehler in den Office COM-Schnittstellen zu beheben, indem C # aufgebläht wird.
Mehrdad Afshari
18
@Mehrdad: Optionale Parameter sind natürlich über COM hinaus nützlich. Sie müssen mit den Standardwerten vorsichtig sein, aber zwischen ihnen und benannten Argumenten ist es viel einfacher, einen verwendbaren unveränderlichen Typ zu erstellen.
Jon Skeet
1
Wahr. Insbesondere können benannte Parameter für die Interaktion mit einigen dynamischen Umgebungen praktisch erforderlich sein . Sicher, ohne Zweifel ist es eine nützliche Funktion, aber das bedeutet nicht, dass es kostenlos ist. Es kostet Einfachheit (ein explizit angegebenes Entwurfsziel). Persönlich finde ich C # erstaunlich für Funktionen, die das Team ausgelassen hat (andernfalls hätte es ein C ++ - Klon sein können). Das C # -Team ist großartig, aber ein Unternehmensumfeld kann kaum politikfrei sein. Ich denke, Anders selbst war darüber nicht sehr glücklich, wie er in seinem PDC'08-Vortrag feststellte: "Wir haben zehn Jahre gebraucht, um dorthin zurückzukehren, wo wir waren."
Mehrdad Afshari
7
Ich bin damit einverstanden, dass das Team die Komplexität im Auge behalten muss. Das dynamische Material erhöht die Komplexität bei geringem Wert für die meisten Entwickler, bei einigen Entwicklern jedoch um einen hohen Wert .
Jon Skeet
1
Ich habe gesehen, wie Framework-Entwickler an vielen Stellen anfingen, über seine Verwendung zu diskutieren. IMO, es ist nur Zeit, bis wir eine gute Verwendung für dynamic... finden. Wir sind einfach zu sehr an statisches / starkes Tippen gewöhnt, um zu sehen, warum es außerhalb von COM wichtig ist.
Chakrit

Antworten:

145

Ich bin kein Experte in diesem Bereich, aber ich bin kürzlich über das gestolpert, was Sie meiner Meinung nach wollen: die CoClass- Attributklasse.

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

Eine Coclass liefert konkrete Implementierung (en) einer oder mehrerer Schnittstellen. In COM können solche konkreten Implementierungen in jeder Programmiersprache geschrieben werden, die die Entwicklung von COM-Komponenten unterstützt, z. B. Delphi, C ++, Visual Basic usw.

Siehe meine Antwort auf eine ähnliche Frage zur Microsoft Speech API , bei der Sie die Benutzeroberfläche "instanziieren" können SpVoice(aber wirklich, Sie instanziieren SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
quelle
2
Sehr interessant - werde es später versuchen. Die verknüpften PIA-Typen haben jedoch keine CoClass. Vielleicht hat es etwas mit dem Verknüpfungsprozess zu tun - ich werde einen Blick in die ursprüngliche PIA werfen ...
Jon Skeet
64
+1 für das Schreiben der akzeptierten Antwort, als Eric Lippert und Jon Skeet ebenfalls antworteten;) Nein, wirklich, +1 für das Erwähnen von CoClass.
OregonGhost
61

Zwischen dir und Michael hast du fast die Teile zusammengesetzt. Ich denke, so funktioniert es. (Ich habe den Code nicht geschrieben, daher könnte ich ihn leicht falsch ausdrücken, aber ich bin mir ziemlich sicher, dass dies so ist.)

Wenn:

  • Sie "neu" einen Schnittstellentyp und
  • Der Schnittstellentyp hat eine bekannte Coclass und
  • Sie verwenden die Funktion "no pia" für diese Schnittstelle

dann wird der Code als (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE)) generiert.

Wenn:

  • Sie "neu" einen Schnittstellentyp und
  • Der Schnittstellentyp hat eine bekannte Coclass und
  • Sie verwenden NICHT die "no pia" -Funktion für diese Schnittstelle

dann wird der Code so generiert, als hätten Sie "new COCLASSTYPE ()" gesagt.

Jon, zögern Sie nicht, mich oder Sam direkt zu nerven, wenn Sie Fragen zu diesem Zeug haben. Zu Ihrer Information, Sam ist der Experte für diese Funktion.

Eric Lippert
quelle
36

Okay, dies ist nur, um Michaels Antwort ein bisschen mehr Fleisch zu verleihen (er kann es gerne hinzufügen, wenn er möchte, in diesem Fall werde ich diese entfernen).

Bei der Betrachtung der ursprünglichen PIA für Word.Application handelt es sich um drei Typen (Ignorieren der Ereignisse):

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

Es gibt zwei Schnittstellen aus Gründen, über die Eric Lippert in einer anderen Antwort spricht . Und da ist, wie Sie sagten, das CoClass- sowohl in Bezug auf die Klasse selbst als auch auf das Attribut auf der ApplicationSchnittstelle.

Wenn wir nun die PIA-Verknüpfung in C # 4 verwenden, ist ein Teil davon in die resultierende Binärdatei eingebettet ... aber nicht alles. Eine Anwendung, die nur eine Instanz von erstellt, hat folgende ApplicationTypen:

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

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

Nein ApplicationClass- vermutlich, weil dies zur Ausführungszeit dynamisch vom realen COM-Typ geladen wird.

Eine weitere interessante Sache ist der Unterschied im Code zwischen der verknüpften Version und der nicht verknüpften Version. Wenn Sie die Zeile dekompilieren

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

in der referenzierten Version endet es als:

Application application = new ApplicationClass();

in der verlinkten Version endet es als

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

So sieht es aus wie die „echte“ PIA das braucht CoClassAttribut, aber die verknüpfte Version nicht , weil es nicht ist eine CoClassder Compiler kann tatsächlich Referenz. Es muss es dynamisch machen.

Ich könnte versuchen, eine COM-Schnittstelle mit diesen Informationen zu fälschen und zu sehen, ob ich den Compiler dazu bringen kann, sie zu verknüpfen ...

Jon Skeet
quelle
27

Nur um Michaels Antwort ein wenig zu bestätigen:

Der folgende Code wird kompiliert und ausgeführt:

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

Sie brauchen sowohl das ComImportAttributeals auch das, GuidAttributedamit es funktioniert.

Beachten Sie auch die Informationen, wenn Sie mit der Maus über new IFoo(): Intellisense nimmt die Informationen richtig auf: Schön!

Rasmus Faber
quelle
danke, ich habe es versucht, aber mir fehlte das ComImport- Attribut, aber wenn ich zum Quellcode gehe, habe ich mit F12 gearbeitet und nur CoClass und Guid angezeigt . Warum ist das so?
Ehsan Sajjad