W jaki sposób kompilator C # wykrywa typy COM?

168

EDYCJA: Zapisałem wyniki jako wpis na blogu .


Kompilator C # traktuje typy COM nieco magicznie. Na przykład to stwierdzenie wygląda normalnie ...

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

... dopóki nie zdasz sobie sprawy, że Applicationto interfejs. Wywołanie konstruktora w interfejsie? Yoiks! To faktycznie jest tłumaczone na wywołanie do, Type.GetTypeFromCLSID()a drugie na Activator.CreateInstance.

Dodatkowo w C # 4 można użyć argumentów innych niż ref dla refparametrów, a kompilator po prostu dodaje zmienną lokalną do przekazania przez odwołanie, odrzucając wyniki:

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

(Tak, brakuje wielu argumentów. Czy parametry opcjonalne nie są ładne? :)

Próbuję zbadać zachowanie kompilatora i nie udaje mi się sfałszować pierwszej części. Drugą część mogę zrobić bez problemu:

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

Chciałbym móc napisać:

Dummy dummy = new Dummy();

chociaż. Oczywiście wybuchnie w czasie egzekucji, ale to w porządku. Po prostu eksperymentuję.

Inne atrybuty dodane przez kompilator do połączonych PIA COM ( CompilerGeneratedi TypeIdentifier) nie wydają się działać ... co to za magiczny sos?

Jon Skeet
źródło
11
Czy parametry opcjonalne nie są ładne? IMO, nie, nie są mili. Firma Microsoft próbuje naprawić lukę w interfejsach COM pakietu Office, dodając wzdęcie do języka C #.
Mehrdad Afshari
18
@Mehrdad: Parametry opcjonalne są oczywiście przydatne poza COM. Musisz uważać na wartości domyślne, ale między nimi a nazwanymi argumentami o wiele łatwiej jest zbudować użyteczny niezmienny typ.
Jon Skeet,
1
Prawdziwe. W szczególności nazwane parametry mogą być praktycznie wymagane do współdziałania z niektórymi dynamicznymi środowiskami. Jasne, bez wątpienia jest to przydatna funkcja, ale to nie znaczy, że jest dostępna za darmo. Kosztuje prostotę (wyraźnie określony cel projektowy). Osobiście uważam, że C # jest niesamowity ze względu na funkcje, które zespół przerwał (w przeciwnym razie mógłby to być klon C ++). Zespół C # jest świetny, ale środowisko korporacyjne nie może być wolne od polityki. Myślę, że sam Anders nie był z tego powodu zbyt zadowolony, jak stwierdził w swoim przemówieniu PDC'08: „zajęło nam dziesięć lat, aby wrócić do miejsca, w którym byliśmy”.
Mehrdad Afshari
7
Zgadzam się, że zespół będzie musiał uważnie obserwować złożoność. Dynamiczne elementy dodają dużo złożoności za niewielką wartość dla większości programistów, ale mają wysoką wartość dla niektórych programistów.
Jon Skeet,
1
Widziałem, jak deweloperzy frameworka zaczęli omawiać jego zastosowania w wielu miejscach. IMO to tylko czas, aby znaleźć dobre zastosowanie dynamic... jesteśmy zbyt przyzwyczajeni do statycznego / silnego pisania, aby zobaczyć, dlaczego miałoby to znaczenie poza COM.
chakrit

Odpowiedzi:

145

W żadnym wypadku nie jestem w tym ekspertem, ale ostatnio natknąłem się na to, o czym myślę, że chcesz: na klasę atrybutów CoClass .

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

Coclass dostarcza konkretną implementację (y) jednego lub więcej interfejsów. W COM takie konkretne implementacje można napisać w dowolnym języku programowania obsługującym tworzenie komponentów COM, np. Delphi, C ++, Visual Basic itp.

Zobacz moją odpowiedź na podobne pytanie dotyczące interfejsu Microsoft Speech API , w którym można „utworzyć instancję” interfejsu SpVoice(ale tak naprawdę jest to instancja SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
źródło
2
Bardzo ciekawe - spróbuję później. Połączone typy PIA nie mają jednak CoClass. Może ma to coś wspólnego z procesem łączenia - przyjrzę się oryginalnemu PIA ...
Jon Skeet,
64
+1 za bycie niesamowitym, pisząc zaakceptowaną odpowiedź, gdy Eric Lippert i Jon Skeet również odpowiedzieli;) Nie, naprawdę, +1 za wspomnienie o CoClass.
OregonGhost,
61

Między tobą a Michaelem prawie udało ci się poskładać wszystkie elementy. Myślę, że tak to działa. (Nie napisałem kodu, więc mogę go trochę błędnie określić, ale jestem prawie pewien, że tak to działa.)

Jeśli:

  • jesteś "nowym" typem interfejsu, a
  • typ interfejsu ma znaną klasę, a
  • UŻYWASZ funkcji „no pia” dla tego interfejsu

następnie kod jest generowany jako (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Jeśli:

  • jesteś "nowym" typem interfejsu, a
  • typ interfejsu ma znaną klasę, a
  • NIE UŻYWASZ funkcji „no pia” dla tego interfejsu

wtedy kod jest generowany tak, jakbyś powiedział „nowy COCLASSTYPE ()”.

Jon, nie krępuj się zgłaszać bezpośrednio do mnie lub Sama, jeśli masz pytania dotyczące tych rzeczy. FYI, Sam jest ekspertem w tej funkcji.

Eric Lippert
źródło
36

Okay, to tylko po to, aby nadać odpowiedzi Michaela trochę więcej treści (jest mile widziany, jeśli chce, jeśli chce, w takim przypadku usunę ten).

Patrząc na oryginalny PIA for Word, w aplikacji występują trzy typy (ignorując zdarzenia):

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

Istnieją dwa interfejsy z powodów, o których Eric Lippert mówi w innej odpowiedzi . I tam, jak powiedziałeś, jest CoClass- zarówno pod względem samej klasy, jak i atrybutu w Applicationinterfejsie.

Jeśli teraz używamy łączenia PIA w C # 4, część z tego jest osadzona w wynikowym pliku binarnym ... ale nie wszystko. Aplikacja, która właśnie tworzy instancję, Applicationkończy się następującymi typami:

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

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

Nie ApplicationClass- prawdopodobnie dlatego, że zostanie załadowany dynamicznie z rzeczywistego typu COM w czasie wykonywania.

Inną interesującą rzeczą jest różnica w kodzie między wersją połączoną a wersją niepowiązaną. Jeśli zdekompilujesz wiersz

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

w wersji, do której się odwołuje , kończy się jako:

Application application = new ApplicationClass();

podczas gdy w wersji połączonej kończy się jako

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

Tak to wygląda „prawdziwy” PIA potrzebuje CoClassatrybutu, ale wersja związana nie dlatego, że nie jestCoClass kompilator może faktycznie odniesienia. Musi to robić dynamicznie.

Mogę spróbować sfałszować interfejs COM, korzystając z tych informacji i zobaczyć, czy uda mi się zmusić kompilator do połączenia go ...

Jon Skeet
źródło
27

Aby dodać trochę potwierdzenia do odpowiedzi Michaela:

Następujący kod kompiluje się i uruchamia:

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

Aby to działało, potrzebujesz zarówno opcji, jak ComImportAttributei GuidAttribute.

Zwróć także uwagę na informacje, gdy najedziesz kursorem myszy na new IFoo(): Intellisense prawidłowo odbiera informacje: Fajnie!

Rasmus Faber
źródło
dzięki, starałem ale brakowało ComImport atrybut, ale kiedy idę do kodu źródłowego i pracowała przy użyciu F12 pokazuje tylko CoClass i Guid , dlaczego tak jest?
Ehsan Sajjad