Bagikan melalui


Tutorial: Menggunakan API ComWrappers

Dalam tutorial ini, Anda akan mempelajari cara membuat subkelas jenis ComWrappers dengan benar untuk memberikan solusi interop COM yang dioptimalkan dan ramah AOT. Sebelum memulai tutorial ini, Anda harus sudah terbiasa dengan COM, arsitekturnya, dan solusi interop COM yang ada.

Dalam tutorial ini, Anda akan menerapkan definisi antarmuka berikut. Antarmuka ini dan implementasinya akan menunjukkan:

  • Mengawal dan melepaskan pengawalan jenis melintas batas COM/.NET.
  • Dua pendekatan berbeda untuk mengonsumsi objek COM asli di .NET.
  • Pola yang direkomendasikan untuk mengaktifkan interop COM kustom di .NET 5 dan yang lebih baru.

Semua kode sumber yang digunakan dalam tutorial ini tersedia di repositori dotnet/sample.

Catatan

Dalam .NET 8 SDK dan versi yang lebih baru, generator sumber disediakan untuk secara otomatis menghasilkan ComWrappers implementasi API untuk Anda. Untuk informasi selengkapnya, lihat ComWrappers pembuatan sumber.

Definisi C#

interface IDemoGetType
{
    string? GetString();
}

interface IDemoStoreType
{
    void StoreString(int len, string? str);
}

Definisi Win32 C++

MIDL_INTERFACE("92BAA992-DB5A-4ADD-977B-B22838EE91FD")
IDemoGetType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE GetString(_Outptr_ wchar_t** str) = 0;
};

MIDL_INTERFACE("30619FEA-E995-41EA-8C8B-9A610D32ADCB")
IDemoStoreType : public IUnknown
{
    HRESULT STDMETHODCALLTYPE StoreString(int len, _In_z_ const wchar_t* str) = 0;
};

Gambaran umum desain ComWrappers

API ComWrappers dirancang untuk menyediakan interaksi minimal yang diperlukan untuk menyelesaikan interop COM dengan runtime .NET 5+. Ini berarti bahwa banyak perbedaan kecil yang ada dalam sistem interop COM bawaan tidak ada dan harus dibangun dari blok penyusun dasar. Dua tanggung jawab utama API adalah:

  • Identifikasi objek yang efisien (misalnya, pemetaan antara instans IUnknown* dan objek terkelola).
  • Interaksi Pengumpul Sampah (GC).

Efisiensi ini dicapai dengan meminta pembuatan dan akuisi pembungkus untuk melalui API ComWrappers.

Karena API ComWrappers memiliki begitu sedikit tanggung jawab, API ini menjadi alasan bahwa sebagian besar pekerjaan interop harus ditangani oleh konsumen - ini benar. Namun, pekerjaan tambahan sebagian besar mekanis dan dapat dilakukan oleh solusi pembuatan-sumber. Sebagai contoh, rantai alat C#/WinRT adalah solusi pembuatan-sumber yang dibangun di atas ComWrappers untuk memberikan dukungan interop WinRT.

Menerapkan subkelas ComWrappers

Menyediakan subkelas ComWrappers berarti memberikan informasi yang cukup kepada runtime .NET untuk membuat dan merekam pembungkus untuk objek terkelola yang diproyeksikan ke dalam COM dan objek COM yang diproyeksikan ke dalam .NET. Sebelum kita melihat kerangka subkelas, kita harus mendefinisikan beberapa istilah.

Pembungkus Objek Terkelola – Objek .NET terkelola memerlukan pembungkus untuk mengaktifkan penggunaan dari lingkungan non-.NET. Pembungkus ini secara historis disebut COM Callable Wrappers (CCW).

Pembungkus Objek Asli – Objek COM yang diimplementasikan dalam bahasa non-.NET memerlukan pembungkus untuk mengaktifkan penggunaan dari .NET. Pembungkus ini secara historis disebut Runtime Callable Wrappers (RCW).

Langkah 1 - Tentukan metode untuk mengimplementasikan dan memahami niat mereka

Untuk memperluas jenis ComWrappers, Anda harus menerapkan tiga metode berikut. Masing-masing metode ini mewakili partisipasi pengguna dalam pembuatan atau penghapusan jenis pembungkus. Metode ComputeVtables() dan CreateObject() masing-masing membuat Pembungkus Objek Terkelola dan Pembungkus Objek Asli. Metode ReleaseObjects() ini digunakan oleh runtime untuk membuat permintaan koleksi pembungkus yang disediakan untuk "dilepaskan" dari objek asli yang mendasarinya. Dalam kebanyakan kasus, isi metode ReleaseObjects() hanya dapat membuang NotImplementedException, karena hanya dipanggil dalam skenario lanjutan yang melibatkan kerangka kerja Pelacak Referensi.

// See referenced sample for implementation.
class DemoComWrappers : ComWrappers
{
    protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) =>
        throw new NotImplementedException();

    protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags) =>
        throw new NotImplementedException();

    protected override void ReleaseObjects(IEnumerable objects) =>
        throw new NotImplementedException();
}

Untuk menerapkan metode ComputeVtables(), putuskan jenis terkelola mana yang ingin Anda dukung. Untuk tutorial ini, kami akan mendukung dua antarmuka yang ditentukan sebelumnya (IDemoGetType dan IDemoStoreType) dan jenis terkelola yang mengimplementasikan dua antarmuka tersebut (DemoImpl).

class DemoImpl : IDemoGetType, IDemoStoreType
{
    string? _string;
    public string? GetString() => _string;
    public void StoreString(int _, string? str) => _string = str;
}

Untuk metode CreateObject(), Anda juga perlu menentukan apa yang ingin Anda dukung. Namun, dalam hal ini, kita hanya tahu antarmuka COM yang kita minati, bukan kelas COM. Antarmuka yang digunakan dari sisi COM sama dengan antarmuka yang kami proyeksikan dari sisi .NET (yaitu, IDemoGetType dan IDemoStoreType).

Kami tidak akan menerapkan ReleaseObjects() dalam tutorial ini.

Langkah 2 – Terapkan ComputeVtables()

Mari kita mulai dengan Pembungkus Objek Terkelola - pembungkus ini lebih mudah. Anda akan membuat Tabel Metode Virtual, atau vtable, untuk setiap antarmuka untuk memproyeksikannya ke lingkungan COM. Untuk tutorial ini, Anda akan mendefinisikan vtable sebagai serangakaian pointer, di mana setiap pointer mewakili implementasi fungsi pada antarmuka - urutan sangat penting di sini. Di COM, setiap antarmuka mewarisi dari IUnknown. Jenis ini IUnknown memiliki tiga metode yang ditentukan dalam urutan berikut: QueryInterface(), AddRef(), dan Release(). Setelah metode IUnknown, akan muncul metode antarmuka tertentu. Misalnya, pertimbangkan IDemoGetType dan IDemoStoreType. Secara konseptual, vtable untuk jenis tersebut akan terlihat seperti berikut ini:

IDemoGetType    | IDemoStoreType
==================================
QueryInterface  | QueryInterface
AddRef          | AddRef
Release         | Release
GetString       | StoreString

Melihat DemoImpl, kita sudah memiliki implementasi untuk GetString() dan StoreString(), tetapi bagaimana dengan fungsi IUnknown? Cara mengimplementasikan IUnknown instans berada di luar cakupan tutorial ini, tetapi dapat dilakukan secara manual di ComWrappers. Namun, dalam tutorial ini, Anda akan membiarkan runtime menangani bagian tersebut. Anda bisa mendapatkan implementasi IUnknown menggunakan metode ComWrappers.GetIUnknownImpl().

Mungkin tampak seperti Anda telah menerapkan semua metode, tetapi sayangnya, hanya fungsi IUnknown yang dapat dikonsumsi dalam vtable COM. Karena COM berada di luar runtime, Anda harus membuat penunjuk fungsi asli ke implementasi DemoImpl Anda. Ini dapat dilakukan menggunakan penunjuk fungsi C# dan UnmanagedCallersOnlyAttribute. Anda dapat membuat fungsi untuk disisipkan ke dalam vtable dengan membuat fungsi static yang menirukan tanda tangan fungsi COM. Berikut ini adalah contoh tanda tangan COM untuk IDemoGetType.GetString() - ingat dari COM ABI bahwa argumen pertama adalah instans itu sendiri.

[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);

Implementasi pembungkus IDemoGetType.GetString() harus terdiri dari pengawalan logika dan kemudian pengiriman ke objek terkelola yang dibungkus. Semua status untuk pengiriman termuat dalam argumen _this yang disediakan. Argumen _this sebenarnya berasal dari jenis ComInterfaceDispatch*. Jenis ini mewakili struktur tingkat-rendah dengan satu bidang, Vtable, yang akan dibahas nanti. Detail lebih lanjut dari jenis ini dan tata letaknya adalah detail implementasi runtime dan tidak boleh diandalkan. Untuk mengambil instans terkelola dari instans ComInterfaceDispatch*, gunakan kode berikut:

IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);

Sekarang setelah Anda memiliki metode C# yang dapat disisipkan ke dalam vtable, Anda dapat membangun vtable. Perhatikan penggunaan RuntimeHelpers.AllocateTypeAssociatedMemory() untuk mengalokasikan memori dengan suatu cara yang bisa berfungsi dengan rakitan yang dapat dibongkar.

GetIUnknownImpl(
    out IntPtr fpQueryInterface,
    out IntPtr fpAddRef,
    out IntPtr fpRelease);

// Local variables with increment act as a guard against incorrect construction of
// the native vtable. It also enables a quick validation of final size.
int tableCount = 4;
int idx = 0;
var vtable = (IntPtr*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    IntPtr.Size * tableCount);
vtable[idx++] = fpQueryInterface;
vtable[idx++] = fpAddRef;
vtable[idx++] = fpRelease;
vtable[idx++] = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr*, int>)&ABI.IDemoGetTypeManagedWrapper.GetString;
Debug.Assert(tableCount == idx);
s_IDemoGetTypeVTable = (IntPtr)vtable;

Alokasi vtable adalah bagian pertama dari penerapan ComputeVtables(). Anda juga harus membuat definisi COM komprehensif untuk jenis yang anda rencanakan untuk didukung - pikirkan DemoImpl dan bagian-bagiannya yang harus dapat digunakan dari COM. Menggunakan vtable yang dibuat, Anda sekarang dapat membuat serangkaian instans ComInterfaceEntry yang mewakili tampilan lengkap objek terkelola di COM.

s_DemoImplDefinitionLen = 2;
int idx = 0;
var entries = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(
    typeof(DemoComWrappers),
    sizeof(ComInterfaceEntry) * s_DemoImplDefinitionLen);
entries[idx].IID = IDemoGetType.IID_IDemoGetType;
entries[idx++].Vtable = s_IDemoGetTypeVTable;
entries[idx].IID = IDemoStoreType.IID_IDemoStoreType;
entries[idx++].Vtable = s_IDemoStoreVTable;
Debug.Assert(s_DemoImplDefinitionLen == idx);
s_DemoImplDefinition = entries;

Alokasi vtable dan entri untuk Pembungkus Objek Terkelola dapat dan harus dilakukan sebelumnya karena data dapat digunakan untuk semua instans jenis. Pekerjaan di sini dapat dilakukan di konstruktor static atau penginisialisasi modul, tetapi harus dilakukan sebelumnya sehingga metode ComputeVtables() dapat dilakukan sesederhana dan secepat mungkin.

protected override unsafe ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags,
out int count)
{
    if (obj is DemoImpl)
    {
        count = s_DemoImplDefinitionLen;
        return s_DemoImplDefinition;
    }

    // Unknown type
    count = 0;
    return null;
}

Setelah Anda menerapkan metode ComputeVtables(), subkelas ComWrappers akan dapat menghasilkan Pembungkus Objek Terkelola untuk instans DemoImpl. Ketahuilah bahwa Pembungkus Objek Terkelola yang dikembalikan dari panggilan ke GetOrCreateComInterfaceForObject() berjenis IUnknown*. Jika API asli yang diteruskan ke pembungkus memerlukan antarmuka yang berbeda, Marshal.QueryInterface() untuk antarmuka tersebut harus dilakukan.

var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

Langkah 3 – Terapkan CreateObject()

Membangun Pembungkus Objek Asli memiliki lebih banyak opsi implementasi dan banyak nuansa daripada membangun Pembungkus Objek Terkelola. Pertanyaan pertama yang harus dijawab adalah seberapa permisif subkelas ComWrappers dalam mendukung jenis COM. Untuk mendukung semua jenis COM, yang mungkin, Anda harus menulis sejumlah besar kode atau menggunakan beberapa fungsi pintar dari Reflection.Emit. Untuk tutorial ini, Anda hanya akan mendukung instans COM yang mengimplementasikan IDemoGetType dan IDemoStoreType. Karena Anda tahu ada set terbatas dan telah membatasi bahwa setiap instans COM yang disediakan harus mengimplementasikan kedua antarmuka, Anda dapat menyediakan satu pembungkus yang ditentukan secara statis; namun, kasus dinamis cukup umum dalam COM sehingga kita akan mengeksplorasi kedua opsi ini.

Pembungkus Objek Asli Statis

Mari kita lihat implementasi statisnya terlebih dahulu. Pembungkus Objek Asli statis melibatkan penentuan jenis terkelola yang menerapkan antarmuka .NET dan dapat meneruskan panggilan pada jenis terkelola ke instans COM. Maka akan muncul garis besar kasar dari pembungkus statis.

// See referenced sample for implementation.
class DemoNativeStaticWrapper
    : IDemoGetType
    , IDemoStoreType
{
    public string? GetString() =>
        throw new NotImplementedException();

    public void StoreString(int len, string? str) =>
        throw new NotImplementedException();
}

Untuk membuat instans dar kelas ini dan menyediakannya sebagai pembungkus, Anda harus menentukan kebijakan tertentu. Jika jenis ini digunakan sebagai pembungkus, tampaknya karena mengimplementasikan kedua antarmuka, instans COM yang mendasar juga harus mengimplementasikan kedua antarmuka tersebut. Mengingat Anda mengadopsi kebijakan ini, Anda harus mengonfirmasi ini melalui panggilan ke Marshal.QueryInterface() pada instans COM.

int hr = Marshal.QueryInterface(ptr, ref IDemoGetType.IID_IDemoGetType, out IntPtr IDemoGetTypeInst);
if (hr != 0)
{
    return null;
}

hr = Marshal.QueryInterface(ptr, ref IDemoStoreType.IID_IDemoStoreType, out IntPtr IDemoStoreTypeInst);
if (hr != 0)
{
    Marshal.Release(IDemoGetTypeInst);
    return null;
}

return new DemoNativeStaticWrapper()
{
    IDemoGetTypeInst = IDemoGetTypeInst,
    IDemoStoreTypeInst = IDemoStoreTypeInst
};

Pembungkus Objek Asli Statis

Pembungkus dinamis lebih fleksibel karena menyediakan cara untuk jenis yang akan dikueri pada durasi, bukan secara statis. Untuk memberikan dukungan ini, Anda akan menggunakan IDynamicInterfaceCastable - detail lebih lanjut dapat ditemukan di sini. Amati bahwa DemoNativeDynamicWrapper hanya mengimplementasikan antarmuka ini. Fungsionalitas yang disediakan antarmuka adalah kesempatan untuk menentukan jenis apa yang didukung pada durasi. Sumber untuk tutorial ini melakukan pemeriksaan statis selama pembuatan tetapi itu hanya untuk berbagi kode karena pemeriksaan dapat ditangguhkan sampai panggilan dilakukan ke DemoNativeDynamicWrapper.IsInterfaceImplemented().

// See referenced sample for implementation.
internal class DemoNativeDynamicWrapper
    : IDynamicInterfaceCastable
{
    public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) =>
        throw new NotImplementedException();

    public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented) =>
        throw new NotImplementedException();
}

Mari kita lihat salah satu antarmuka yang akan didukung DemoNativeDynamicWrapper secara dinamis. Kode berikut menyediakan implementasi penggunaan IDemoStoreType fitur metode antarmuka default.

[DynamicInterfaceCastableImplementation]
unsafe interface IDemoStoreTypeNativeWrapper : IDemoStoreType
{
    public static void StoreString(IntPtr inst, int len, string? str);

    void IDemoStoreType.StoreString(int len, string? str)
    {
        var inst = ((DemoNativeDynamicWrapper)this).IDemoStoreTypeInst;
        StoreString(inst, len, str);
    }
}

Ada dua hal penting yang perlu diperhatikan dalam contoh ini:

  1. Atribut DynamicInterfaceCastableImplementationAttribute. Atribut ini diperlukan pada jenis apa pun yang dikembalikan dari metode IDynamicInterfaceCastable. Atribut ini memiliki manfaat tambahan untuk membuat pemangkasan IL lebih mudah, yang berarti skenario AOT lebih dapat diandalkan.
  2. Transmisi ke DemoNativeDynamicWrapper. Ini adalah bagian dari sifat dinamis IDynamicInterfaceCastable. Jenis yang dikembalikan dari IDynamicInterfaceCastable.GetInterfaceImplementation() digunakan untuk "selimut" jenis yang mengimplementasikan IDynamicInterfaceCastable. Intinya di sini adalah pointer this tidak seperti tampaknya karena kita mengizinkan huruf dari DemoNativeDynamicWrapper ke IDemoStoreTypeNativeWrapper.

Meneruskan panggilan ke instans COM

Terlepas dari Pembungkus Objek Asli mana yang digunakan, Anda memerlukan kemampuan untuk memanggil fungsi pada instans COM. Implementasi IDemoStoreTypeNativeWrapper.StoreString() dapat berfungsi sebagai contoh penggunaan penunjuk fungsi C# unmanaged.

public static void StoreString(IntPtr inst, int len, string? str)
{
    IntPtr strLocal = Marshal.StringToCoTaskMemUni(str);
    int hr = ((delegate* unmanaged<IntPtr, int, IntPtr, int>)(*(*(void***)inst + 3 /* IDemoStoreType.StoreString slot */)))(inst, len, strLocal);
    if (hr != 0)
    {
        Marshal.FreeCoTaskMem(strLocal);
        Marshal.ThrowExceptionForHR(hr);
    }
}

Mari kita periksa dereferensi instans COM untuk mengakses implementasi vtable-nya. COM ABI mendefinisikan bahwa penunjuk pertama objek adalah ke vtable jenis dan, dari sana, slot yang diinginkan dapat diakses. Mari kita asumsikan alamat objek COM adalah 0x10000. Nilai yang berukuran-pointer pertama harus menjadi alamat vtable – dalam contoh ini 0x20000. Setelah berada di vtable, Anda mencari slot keempat (indeks 3 dalam pengindeksan berbasis-nol) untuk mengakses implementasi StoreString().

COM instance
0x10000  0x20000

VTable for IDemoStoreType
0x20000  <Address of QueryInterface>
0x20008  <Address of AddRef>
0x20010  <Address of Release>
0x20018  <Address of StoreString>

Dengan memiliki penunjuk fungsi memungkinkan Anda untuk mengirimkan ke fungsi anggota tersebut pada objek dengan meneruskan instans objek sebagai parameter pertama. Pola ini mestinya tampak akrab berdasarkan definisi fungsi implementasi Pembungkus Objek Terkelola.

Setelah metode CreateObject() diimplementasikan, subkelas ComWrappers akan dapat menghasilkan Pembungkus Objek Asli untuk instans COM yang mengimplementasikan IDemoGetType dan IDemoStoreType.

IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);

Langkah 4 - Menangani detail seumur hidup Pembungkus Objek Asli

Implementasi ComputeVtables() dan CreateObject() mencakup beberapa detail seumur hidup pembungkus, tetapi ada pertimbangan lebih lanjut. Meskipun bisa menjadi langkah singkat, proses ini juga dapat secara signifikan meningkatkan kompleksitas desain ComWrappers.

Tidak seperti Pembungkus Objek Terkelola, yang dikendalikan oleh panggilan ke metode AddRef() dan Release(), masa pakai Pembungkus Objek Asli ditangani secara tidak deterministik oleh GC. Pertanyaannya di sini adalah, kapan Pembungkus Objek Asli memanggil Release() pada IntPtr yang mewakili instans COM? Ada dua wadah umum:

  1. Finalizer Pembungkus Objek Asli bertanggung jawab untuk memanggil metode Release() instans COM. Ini adalah satu-satunya waktu yang aman untuk memanggil metode ini. Pada titik ini, telah ditentukan dengan benar oleh GC bahwa tidak ada referensi lain ke Pembungkus Objek Asli dalam runtime .NET. Mungkin ada kompleksitas di sini jika Anda mendukung Apartemen COM dengan benar; untuk informasi selengkapnya, lihat bagian Pertimbangan tambahan.

  2. Pembungkus Objek Asli mengimplementasikan IDisposable dan memanggil Release() di Dispose().

Catatan

Pola IDisposable hanya boleh didukung jika, selama panggilan CreateObject(), bendera CreateObjectFlags.UniqueInstance diteruskan. Jika persyaratan ini tidak diikuti, sangat mungkin Pembungkus Objek Asli yang dibuang akan digunakan kembali setelah dibuang.

Menggunakan subkelas ComWrappers

Anda sekarang memiliki subkelas ComWrappers yang dapat diuji. Untuk menghindari pembuatan pustaka asli yang mengembalikan instans COM yang mengimplementasikan IDemoGetType dan IDemoStoreType, Anda akan menggunakan Pembungkus Objek Terkelola dan memperlakukannya sebagai instans COM - hal ini harus bisa dilakukan untuk meneruskan COM padanya.

Mari kita buat Pembungkus Objek Terkelola terlebih dahulu. Membuat instans DemoImpl dan menampilkan status string saat ini.

var demo = new DemoImpl();

string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");

Sekarang Anda dapat membuat instans DemoComWrappers dan Pembungkus Objek Terkelola yang kemudian dapat Anda masukkan ke lingkungan COM.

var cw = new DemoComWrappers();

IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);

Alih-alih meneruskan Pembungkus Objek Terkelola ke lingkungan COM, pura-pura Anda baru saja menerima instans COM ini, sehingga Anda akan membuat Pembungkus Objek Asli untuk itu sebagai gantinya.

var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);

Dengan Pembungkus Objek Asli, Anda dapat mentransmisikannya ke salah satu antarmuka yang diinginkan dan menggunakannya sebagai objek terkelola normal. Anda dapat memeriksa instans DemoImpl dan mengamati dampak operasi pada Pembungkus Objek Asli yang membungkus Pembungkus Objek Terkelola yang pada gilirannya membungkus instans terkelola.

var getter = (IDemoGetType)rcw;
var store = (IDemoStoreType)rcw;

string msg = "hello world!";
store.StoreString(msg.Length, msg);
Console.WriteLine($"Setting string through wrapper: {msg}");

value = demo.GetString();
Console.WriteLine($"Get string through managed object: {value}");

msg = msg.ToUpper();
demo.StoreString(msg.Length, msg.ToUpper());
Console.WriteLine($"Setting string through managed object: {msg}");

value = getter.GetString();
Console.WriteLine($"Get string through wrapper: {value}");

Karena subkelas ComWrapper Anda dirancang untuk mendukung CreateObjectFlags.UniqueInstance, Anda dapat segera membersihkan Pembungkus Objek Asli alih-alih menunggu GC terjadi.

(rcw as IDisposable)?.Dispose();

Aktivasi COM dengan ComWrappers

Pembuatan objek COM biasanya dilakukan melalui Aktivasi COM – skenario kompleks di luar lingkup dokumen ini. Untuk memberikan pola konseptual yang harus diikuti, kami memperkenalkan API CoCreateInstance(), digunakan untuk Aktivasi COM, dan menggambarkan bagaimana API tersebut dapat digunakan dengan ComWrappers.

Asumsikan Anda memiliki kode C# berikut dalam aplikasi Anda. Contoh di bawah ini menggunakan CoCreateInstance() untuk mengaktifkan kelas COM dan sistem interop COM bawaan untuk mengawal instans COM ke antarmuka yang sesuai. Perhatikan bahwa penggunaan typeof(I).GUID terbatas pada pernyataan dan merupakan kasus penggunaan refleksi yang dapat berpengaruh jika kode tersebut ramah AOT.

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out object obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)obj;
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    [MarshalAs(UnmanagedType.Interface)] out object ppObj);

Mengonversi hal di atas untuk digunakan ComWrappers melibatkan penghapusan MarshalAs(UnmanagedType.Interface) dari P/Invoke CoCreateInstance() dan melakukan pengawalan secara manual.

static ComWrappers s_ComWrappers = ...;

public static I ActivateClass<I>(Guid clsid, Guid iid)
{
    Debug.Assert(iid == typeof(I).GUID);
    int hr = CoCreateInstance(ref clsid, IntPtr.Zero, /*CLSCTX_INPROC_SERVER*/ 1, ref iid, out IntPtr obj);
    if (hr < 0)
    {
        Marshal.ThrowExceptionForHR(hr);
    }
    return (I)s_ComWrappers.GetOrCreateObjectForComInstance(obj, CreateObjectFlags.None);
}

[DllImport("Ole32")]
private static extern int CoCreateInstance(
    ref Guid rclsid,
    IntPtr pUnkOuter,
    int dwClsContext,
    ref Guid riid,
    out IntPtr ppObj);

Dimungkinkan juga untuk mengabstraksi fungsi gaya pabrik seperti ActivateClass<I> dengan menyertakan logika aktivasi di konstruktor kelas untuk Pembungkus Objek Asli. Konstruktor dapat menggunakan API ComWrappers.GetOrRegisterObjectForComInstance() untuk mengaitkan objek terkelola yang baru dibuat dengan instans COM yang diaktifkan.

Pertimbangan tambahan

Kompilasi AOT Native – Ahead-of-time (AOT) memberikan peningkatan biaya startup karena kompilasi JIT dihindari. Menghilangkan keperluan akan kompilasi JIT juga sering diperlukan pada beberapa platform. Mendukung AOT adalah tujuan dari API ComWrappers, tetapi implementasi pembungkus apa pun harus berhati-hati untuk tidak secara tidak sengaja menimbulkan kasus di mana AOT terpecah, seperti menggunakan refleksi. Properti Type.GUID adalah contoh penggunaan refleksi, tetapi dengan cara yang tidak jelas. Properti Type.GUID menggunakan refleksi untuk memeriksa atribut jenis dan kemudian bisa juga nama jenis dan rakitan yang memuatnya dengan tujuan menghasilkan nilainya.

Pembuatan sumber – Sebagian besar kode yang diperlukan untuk interop COM dan implementasi ComWrappers kemungkinan dapat dibuat secara otomatis oleh beberapa alat. Sumber untuk kedua jenis pembungkus dapat dihasilkan berdasarkan definisi COM yang sesuai - misalnya, Pustaka Jenis (TLB), IDL, atau Rakitan Interop Primer (PIA).

Pendaftaran global – Karena API ComWrappers dirancang sebagai fase interop COM baru, API ini perlu memiliki cara tertentu untuk terintegrasi sebagian dengan sistem yang ada. Ada metode statis yang berdampak secara global pada API ComWrappers yang mengizinkan pendaftaran instans global untuk berbagai dukungan. Metode ini dirancang untuk instans ComWrappers yang mengharapkan untuk memberikan dukungan interop COM yang komprehensif dalam semua kasus - mirip dengan sistem interop COM bawaan.

Dukungan Pelacak Referensi - Dukungan ini terutama digunakan untuk skenario WinRT dan mewakili skenario tingkat lanjut. Untuk sebagian besar implementasi ComWrapper, bendera CreateComInterfaceFlags.TrackerSupport atau CreateObjectFlags.TrackerObject harus melemparkan NotSupportedException. Jika Anda ingin mengaktifkan dukungan ini, mungkin pada platform Windows atau bahkan non-Windows, sangat disarankan untuk mereferensikan rantai alat C#/WinRT.

Selain fitur seumur hidup, sistem jenis, dan fungsional yang dibahas sebelumnya, implementasi patuh-COM dari ComWrappers memerlukan pertimbangan tambahan. Untuk implementasi apa pun yang akan digunakan pada platform Windows, ada pertimbangan berikut:

  • Apartemen – Struktur organisasi COM untuk threading disebut "Apartemen" dan memiliki aturan ketat yang harus diikuti untuk operasi yang stabil. Tutorial ini tidak menerapkan Native Object Wrappers yang sadar-apartemen, tetapi implementasi siap-produksi apa pun harus diketahui apartemen. Untuk mencapai hal ini, sebaiknya gunakan API RoGetAgileReference yang diperkenalkan di Windows 8. Untuk versi sebelum Windows 8, pertimbangkan Tabel Antarmuka Global.

  • Keamanan – COM menyediakan model keamanan yang kaya untuk aktivasi kelas dan izin yang diproksikan.