Bagaimana kompiler C # mendeteksi tipe COM?

168

EDIT: Saya sudah menulis hasilnya sebagai posting blog .


Compiler C # memperlakukan tipe COM agak ajaib. Misalnya, pernyataan ini terlihat normal ...

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

... sampai Anda menyadari bahwa itu Applicationadalah antarmuka. Memanggil konstruktor pada antarmuka? Yoiks! Ini sebenarnya diterjemahkan ke dalam panggilan ke Type.GetTypeFromCLSID()dan ke lainnya Activator.CreateInstance.

Selain itu, dalam C # 4, Anda dapat menggunakan argumen non-ref untuk refparameter, dan kompilator hanya menambahkan variabel lokal untuk lulus dengan referensi, membuang hasil:

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

(Ya, ada banyak argumen yang hilang. Tidakkah parameter opsional bagus? :)

Saya mencoba untuk menyelidiki perilaku kompiler, dan saya gagal memalsukan bagian pertama. Saya dapat melakukan bagian kedua tanpa masalah:

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

Saya ingin dapat menulis:

Dummy dummy = new Dummy();

meskipun. Jelas itu akan meledak pada waktu eksekusi, tapi tidak apa-apa. Saya hanya bereksperimen.

Atribut lain yang ditambahkan oleh kompiler untuk COM PIA ( CompilerGenerateddan TypeIdentifier) tampaknya tidak melakukan trik ... apa saus ajaibnya?

Jon Skeet
sumber
11
Bukankah parameter opsional bagus? IMO, Tidak, mereka tidak baik. Microsoft sedang mencoba untuk memperbaiki kesalahan pada antarmuka Office COM dengan menambahkan mengasapi ke C #.
Mehrdad Afshari
18
@Mehrdad: Parameter opsional berguna di luar COM, tentu saja. Anda harus berhati-hati dengan nilai default, tetapi di antara mereka dan argumen yang dinamai, jauh lebih mudah untuk membangun tipe yang tidak dapat digunakan yang dapat digunakan.
Jon Skeet
1
Benar. Secara khusus, parameter bernama dapat secara praktis diperlukan untuk interop dengan beberapa lingkungan dinamis. Tentu, tanpa diragukan lagi, ini adalah fitur yang berguna tetapi itu tidak berarti itu gratis. Harganya kesederhanaan (tujuan desain yang dinyatakan secara eksplisit). Secara pribadi, saya pikir C # luar biasa untuk fitur yang tim tinggalkan (jika tidak, itu bisa menjadi klon C ++). Tim C # hebat tetapi lingkungan perusahaan tidak bisa bebas dari politik. Saya kira Anders sendiri tidak begitu senang tentang hal ini ketika ia menyatakan dalam ceramah PDC'08: "kami butuh sepuluh tahun untuk kembali ke tempat kami berada."
Mehrdad Afshari
7
Saya setuju bahwa tim perlu mengawasi kompleksitas. Hal-hal dinamis menambahkan banyak kompleksitas untuk nilai kecil bagi sebagian besar pengembang, tetapi bernilai tinggi untuk beberapa pengembang.
Jon Skeet
1
Saya telah melihat kerangka pengembang mulai membahas penggunaannya di banyak tempat. IMO hanya saatnya sampai kita menemukan penggunaan yang baik untuk dynamic... kita terlalu terbiasa mengetik statis / kuat untuk melihat mengapa itu penting di luar COM.
chakrit

Jawaban:

145

Tidak berarti saya ahli dalam hal ini, tetapi saya tersandung baru-baru ini pada apa yang saya pikir Anda inginkan: kelas atribut CoClass .

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

Coclass memasok implementasi konkret dari satu atau lebih antarmuka. Dalam COM, implementasi konkret tersebut dapat ditulis dalam bahasa pemrograman apa pun yang mendukung pengembangan komponen COM, misalnya Delphi, C ++, Visual Basic, dll.

Lihat jawaban saya untuk pertanyaan serupa tentang Microsoft Speech API , di mana Anda dapat "instantiate" antarmuka SpVoice(tapi sungguh, Anda instantiating SPVoiceClass).

[CoClass(typeof(SpVoiceClass))]
public interface SpVoice : ISpeechVoice, _ISpeechVoiceEvents_Event { }
Michael Petrotta
sumber
2
Sangat menarik - akan mencobanya nanti. Jenis PIA yang ditautkan tidak memiliki CoClass. Mungkin sesuatu itu harus dilakukan dengan proses yang menghubungkan - Saya akan lihat dalam PIA asli ...
Jon Skeet
64
+1 untuk menjadi luar biasa dengan menulis jawaban yang diterima ketika Eric Lippert dan Jon Skeet juga menjawab;) Tidak, sungguh, +1 untuk menyebutkan CoClass.
OregonGhost
61

Antara kamu dan Michael, kamu hampir bisa menyatukan potongan-potongan itu. Saya pikir ini cara kerjanya. (Saya tidak menulis kode, jadi saya mungkin sedikit salah menyebutkannya, tapi saya cukup yakin begini caranya.)

Jika:

  • Anda "baru" sedang jenis antarmuka, dan
  • jenis antarmuka memiliki coclass yang dikenal, dan
  • Anda ADALAH menggunakan fitur "no pia" untuk antarmuka ini

kemudian kode tersebut dihasilkan sebagai (IPIAINTERFACE) Activator.CreateInstance (Type.GetTypeFromClsid (GUID OF COCLASSTYPE))

Jika:

  • Anda "baru" sedang jenis antarmuka, dan
  • jenis antarmuka memiliki coclass yang dikenal, dan
  • Anda TIDAK MENGGUNAKAN fitur "no pia" untuk antarmuka ini

maka kode tersebut dibuat seolah-olah Anda akan mengatakan "COCLASSTYPE ()" baru.

Jon, jangan ragu untuk menggangguku atau Sam secara langsung jika Anda memiliki pertanyaan tentang hal ini. FYI, Sam adalah pakar fitur ini.

Eric Lippert
sumber
36

Oke, ini hanya untuk memberi sedikit lebih banyak jawaban pada Michael (dia dipersilakan menambahkannya jika dia mau, dalam hal ini saya akan menghapus yang ini).

Melihat PIA asli untuk Word. Aplikasi, ada tiga jenis yang terlibat (mengabaikan acara):

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

Ada dua antarmuka untuk alasan yang dibicarakan oleh Eric Lippert dalam jawaban lain . Dan di sana, seperti yang Anda katakan, adalah CoClass- baik dari segi kelas itu sendiri dan atribut pada Applicationantarmuka.

Sekarang jika kita menggunakan tautan PIA dalam C # 4, beberapa di antaranya tertanam dalam biner yang dihasilkan ... tetapi tidak semuanya. Aplikasi yang baru saja membuat instance dari Applicationberakhir dengan jenis ini:

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

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

Tidak ApplicationClass- mungkin karena itu akan dimuat secara dinamis dari tipe COM yang sebenarnya pada waktu eksekusi.

Hal lain yang menarik adalah perbedaan dalam kode antara versi tertaut dan versi yang tidak tertaut. Jika Anda mendekompilasi garis

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

dalam versi referensi itu berakhir sebagai:

Application application = new ApplicationClass();

sedangkan dalam versi tertaut itu berakhir sebagai

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

Sehingga terlihat seperti "nyata" PIA membutuhkan CoClassatribut, tapi versi terkait tidak karena ada tidak satu CoClasscompiler benar-benar dapat referensi. Itu harus dilakukan secara dinamis.

Saya mungkin mencoba memalsukan antarmuka COM menggunakan informasi ini dan melihat apakah saya bisa mendapatkan kompiler untuk menautkannya ...

Jon Skeet
sumber
27

Hanya untuk menambahkan sedikit konfirmasi ke jawaban Michael:

Kode ini mengkompilasi dan menjalankan:

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

Anda membutuhkan keduanya ComImportAttributedan GuidAttributeagar bisa berfungsi.

Perhatikan juga informasi ketika Anda mengarahkan mouse ke atas new IFoo(): Intellisense menerima informasi dengan benar: Bagus!

Rasmus Faber
sumber
terima kasih, saya sudah mencoba tetapi saya kehilangan atribut ComImport , tetapi ketika saya pergi saya ke kode sumber saya bekerja menggunakan F12 hanya menunjukkan CoClass dan Guid , mengapa begitu?
Ehsan Sajjad