Pembungkus Callable COM

Ketika klien COM memanggil objek .NET, runtime bahasa umum membuat objek terkelola dan pembungkus yang dapat dipanggil COM (CCW) untuk objek. Tidak dapat mereferensikan objek .NET secara langsung, klien COM menggunakan CCW sebagai proksi untuk objek terkelola.

Runtime membuat tepat satu CCW untuk objek terkelola, terlepas dari jumlah klien COM yang meminta layanannya. Seperti yang ditunjukkan oleh ilustrasi berikut, beberapa klien COM dapat menyimpan referensi ke CCW yang mengekspos antarmuka INew. CCW, pada gilirannya, menyimpan satu referensi ke objek terkelola yang mengimplementasikan antarmuka dan merupakan sampah yang dikumpulkan. Klien COM dan .NET dapat membuat permintaan pada objek terkelola yang sama secara bersamaan.

Multiple COM clients holding a reference to the CCW that exposes INew.

Pembungkus yang dapat dipanggil COM tidak terlihat oleh kelas lain yang berjalan dalam runtime .NET. Tujuan utama mereka adalah untuk mengatur panggilan antara kode terkelola dan tidak terkelola; namun, CCW juga mengelola identitas objek dan masa pakai objek dari objek terkelola yang mereka bungkus.

Identitas Objek

Runtime mengalokasikan memori untuk objek .NET dari tumpukan yang dikumpulkan sampahnya, yang memungkinkan runtime untuk memindahkan objek di sekitar memori seperlunya. Sebaliknya, runtime mengalokasikan memori untuk CCW dari tumpukan yang tidak dikumpulkan, sehingga memungkinkan klien COM untuk mereferensikan pembungkus secara langsung.

Masa Pakai objek

Tidak seperti klien .NET yang dibungkusnya, CCW dihitung referensi dengan cara COM tradisional. Ketika jumlah referensi pada CCW mencapai nol, pembungkus melepaskan referensinya pada objek terkelola. Objek terkelola tanpa referensi yang tersisa dikumpulkan selama siklus pengumpulan sampah berikutnya.

Menyimulasikan antarmuka COM

CCW mengekspos semua antarmuka, tipe data, dan nilai pengembalian publik yang terlihat COM ke klien COM dengan cara yang konsisten dengan penegakan interaksi berbasis antarmuka COM. Untuk klien COM, memanggil metode pada objek .NET identik dengan memanggil metode pada objek COM.

Untuk membuat pendekatan yang mulus ini, CCW memproduksi antarmuka COM tradisional, seperti IUnknown dan IDispatch. Seperti yang ditunjukkan oleh ilustrasi berikut, CCW mempertahankan satu referensi pada objek .NET yang dibungkusnya. Klien COM dan objek .NET berinteraksi satu sama lain melalui proksi dan konstruksi rintisan CCW.

Diagram that shows how CCW manufactures COM interfaces.

Selain mengekspos antarmuka yang secara eksplisit diimplementasikan oleh kelas di lingkungan terkelola, runtime .NET memasok implementasi antarmuka COM yang tercantum dalam tabel berikut atas nama objek. Kelas .NET dapat mengambil alih perilaku default dengan menyediakan implementasi antarmuka ini sendiri. Namun, runtime selalu menyediakan implementasi untuk antarmuka IUnknown dan IDispatch.

Antarmuka Deskripsi
IDispatch Menyediakan mekanisme untuk pengikatan lambat ke tipe.
IErrorInfo Menyediakan deskripsi tekstual tentang kesalahan, sumbernya, file Bantuan, konteks Bantuan, dan GUID antarmuka yang menentukan kesalahan (selalu GUID_NULL untuk kelas .NET).
IProvideClassInfo Memungkinkan klien COM untuk mendapatkan akses ke antarmuka ITypeInfo yang diimplementasikan oleh kelas terkelola. Mengembalikan COR_E_NOTSUPPORTED pada .NET Core untuk jenis yang tidak diimpor dari COM.
ISupportErrorInfo Memungkinkan klien COM untuk menentukan apakah objek yang dikelola mendukung antarmuka IErrorInfo. Jika demikian, memungkinkan klien untuk mendapatkan penunjuk ke objek pengecualian terbaru. Semua jenis terkelola mendukung antarmuka IErrorInfo.
ITypeInfo (hanya .NET Framework) Menyediakan informasi jenis untuk kelas yang sama persis dengan informasi jenis yang dihasilkan oleh Tlbexp.exe.
IUnknown Menyediakan implementasi standar antarmuka IUnknown tempat klien COM mengelola masa pakai CCW dan menyediakan konversi eksplisit jenis.

Kelas terkelola juga dapat menyediakan antarmuka COM yang dijelaskan dalam tabel berikut.

Antarmuka Deskripsi
Antarmuka kelas (_classname) Antarmuka, diekspos oleh runtime dan tidak didefinisikan secara eksplisit, yang mengekspos semua antarmuka publik, metode, properti, dan bidang yang secara eksplisit terekspos pada objek terkelola.
IConnectionPoint dan IConnectionPointContainer Antarmuka untuk objek yang sumber peristiwa berbasis delegasi (antarmuka untuk mendaftarkan pelanggan peristiwa).
IDispatchEx (hanya .NET Framework) Antarmuka yang disediakan oleh runtime jika kelas mengimplementasikan IExpando. Antarmuka IDispatchEx adalah ekstensi antarmuka IDispatch yang, tidak seperti IDispatch, memungkinkan enumerasi, penambahan, penghapusan, dan panggilan anggota yang peka huruf besar/kecil.
IEnumVARIANT Antarmuka untuk kelas jenis kumpulan, yang menghitung objek dalam kumpulan jika kelas mengimplementasikan IEnumerable.

Memperkenalkan antarmuka kelas

Antarmuka kelas, yang tidak secara eksplisit didefinisikan dalam kode terkelola, adalah antarmuka yang mengekspos semua metode publik, properti, bidang, dan peristiwa yang secara eksplisit terekspos pada objek .NET. Antarmuka ini bisa menjadi antarmuka ganda atau khusus pengiriman. Antarmuka kelas menerima nama kelas .NET itu sendiri, didahului oleh garis bawah. Misalnya, untuk kelas Mamalia, antarmuka kelasnya adalah _Mammal.

Untuk kelas turunan, antarmuka kelas juga mengekspos semua metode publik, properti, dan bidang kelas dasar. Kelas turunan juga mengekspos antarmuka kelas untuk setiap kelas dasar. Misalnya, jika kelas Mamalia memperluas kelas MammalSuperclass, yang sendiri memperluas System.Object, objek .NET mengekspos ke klien COM tiga antarmuka kelas bernama _Mammal, _MammalSuperclass, dan _Object.

Sebagai contoh, perhatikan kelas berikut:

' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
    Sub Eat()
    Sub Breathe()
    Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
    public void Eat() {}
    public void Breathe() {}
    public void Sleep() {}
}

Klien COM dapat memperoleh pointer ke antarmuka kelas bernama _Mammal. Pada .NET Framework, Anda dapat menggunakan alat Pengekspor Pustaka Tipe (Tlbexp.exe) untuk menghasilkan pustaka tipe yang berisi _Mammal definisi antarmuka. Pengekspor Pustaka Tipe tidak didukung pada .NET Core. Jika kelas Mammal mengimplementasikan satu atau lebih antarmuka, antarmuka akan muncul di bawah kelas bersama.

[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
    [id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
        pRetVal);
    [id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
        VARIANT_BOOL* pRetVal);
    [id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
    [id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x6002000d)] HRESULT Eat();
    [id(0x6002000e)] HRESULT Breathe();
    [id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
    [default] interface _Mammal;
}

Menghasilkan antarmuka kelas bersifat opsional. Secara default, interop COM menghasilkan antarmuka khusus pengiriman untuk setiap kelas yang Anda ekspor ke pustaka tipe. Anda dapat mencegah atau memodifikasi pembuatan antarmuka ini secara otomatis dengan menerapkan ClassInterfaceAttribute ke kelas Anda. Meskipun antarmuka kelas dapat memudahkan tugas mengekspos kelas terkelola ke COM, penggunaannya terbatas.

Perhatian

Menggunakan antarmuka kelas, dan bukan secara eksplisit mendefinisikan antarmuka Anda sendiri, dapat mempersulit penerapan versi kelas terkelola Anda di masa mendatang. Harap baca panduan berikut sebelum menggunakan antarmuka kelas.

Tentukan antarmuka eksplisit untuk digunakan klien COM daripada menghasilkan antarmuka kelas.

Karena interop COM menghasilkan antarmuka kelas secara otomatis, perubahan pascaversi pada kelas Anda dapat mengubah tata letak antarmuka kelas yang diekspos oleh runtime bahasa umum. Karena klien COM biasanya tidak siap untuk menangani perubahan dalam tata letak antarmuka, mereka akan rusak jika Anda mengubah tata letak anggota kelas.

Pedoman ini memperkuat gagasan bahwa antarmuka yang diekspos ke klien COM harus tetap tidak berubah. Untuk mengurangi risiko melanggar klien COM dengan secara tidak sengaja menyusun ulang tata letak antarmuka, isolasi semua perubahan ke kelas dari tata letak antarmuka dengan mendefinisikan antarmuka secara eksplisit.

Gunakan ClassInterfaceAttribute untuk melepaskan pembuatan otomatis antarmuka kelas dan menerapkan antarmuka eksplisit untuk kelas , seperti yang ditunjukkan oleh fragmen kode berikut:

<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
    Implements IExplicit
    Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
    int IExplicit.M() { return 0; }
}

Nilai ClassInterfaceType.None mencegah antarmuka kelas dihasilkan saat metadata kelas diekspor ke pustaka tipe. Dalam contoh sebelumnya, klien COM hanya dapat mengakses kelas LoanApp melalui antarmuka IExplicit.

Hindari pengidentifikasi pengiriman penembolokan (DispIds)

Menggunakan antarmuka kelas adalah opsi yang dapat diterima untuk klien skrip, klien Microsoft Visual Basic 6.0, atau klien yang terlambat terikat yang tidak menyimpan dispId anggota antarmuka. DispIds mengidentifikasi anggota antarmuka untuk mengaktifkan pengikatan terlambat.

Untuk antarmuka kelas, pembuatan DispIds didasarkan pada posisi anggota di antarmuka. Jika Anda mengubah urutan anggota dan mengekspor kelas ke pustaka jenis, Anda akan mengubah DispId yang dihasilkan di antarmuka kelas.

Untuk menghindari melanggar klien COM yang terlambat saat menggunakan antarmuka kelas, terapkan ClassInterfaceAttribute dengan nilai ClassInterfaceType.AutoDispatch. Nilai ini mengimplementasikan antarmuka kelas khusus pengiriman, tetapi menghilangkan deskripsi antarmuka dari pustaka jenis. Tanpa deskripsi antarmuka, klien tidak dapat melakukan penembolokan DispIds pada waktu kompilasi. Meskipun ini adalah jenis antarmuka default untuk antarmuka kelas, Anda dapat menerapkan nilai atribut secara eksplisit.

<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
    Implements IAnother
    Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
    public int M() { return 0; }
}

Untuk mendapatkan DispId anggota antarmuka pada runtime, klien COM dapat memanggil IDispatch.GetIdsOfNames. Untuk memanggil metode pada antarmuka, teruskan DispId yang dikembalikan sebagai argumen ke IDispatch.Invoke.

Batasi menggunakan opsi antarmuka ganda untuk antarmuka kelas.

Antarmuka ganda memungkinkan pengikatan awal dan terlambat ke anggota antarmuka oleh klien COM. Pada waktu desain dan selama pengujian, Anda mungkin merasa berguna untuk mengatur antarmuka kelas ke ganda. Untuk kelas terkelola (dan kelas dasarnya) yang tidak akan pernah dimodifikasi, opsi ini juga dapat diterima. Dalam semua kasus lain, hindari mengatur antarmuka kelas ke ganda.

Antarmuka ganda yang dihasilkan secara otomatis mungkin sesuai dalam kasus yang jarang terjadi; namun, lebih sering menciptakan kompleksitas terkait versi. Misalnya, klien COM yang menggunakan antarmuka kelas dari kelas turunan dapat dengan mudah putus dengan perubahan pada kelas dasar. Ketika pihak ketiga menyediakan kelas dasar, tata letak antarmuka kelas berada di luar kendali Anda. Selanjutnya, tidak seperti antarmuka khusus pengiriman, antarmuka ganda (ClassInterfaceType.AutoDual) memberikan deskripsi antarmuka kelas di pustaka tipe yang diekspor. Deskripsi seperti itu mendorong klien yang terlambat terikat untuk melakukan cache DispId pada waktu kompilasi.

Pastikan bahwa semua pemberitahuan peristiwa COM terlambat terikat.

Secara default, informasi jenis COM disematkan langsung ke dalam rakitan terkelola, yang menghilangkan kebutuhan akan rakitan interop primer (PIA). Namun, salah satu batasan informasi jenis yang disematkan adalah tidak mendukung pengiriman pemberitahuan peristiwa COM melalui panggilan vtable terikat awal, tetapi hanya mendukung panggilan IDispatch::Invoke akhir.

Jika aplikasi Anda memerlukan panggilan terikat awal ke metode antarmuka peristiwa COM, Anda dapat menyetel properti Embed Interop Types di Visual Studio ke true, atau sertakan elemen berikut dalam file proyek Anda:

<EmbedInteropTypes>True</EmbedInteropTypes>

Lihat juga