Tutorial: Menggunakan ComWrappers API

Dalam tutorial ini, Anda akan mempelajari cara menyubkelas tipe ComWrappers dengan benar untuk memberikan solusi interop COM yang dioptimalkan dan kompatibel dengan AOT. Sebelum memulai tutorial ini, Anda harus 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:

  • Jenis marshalling dan unmarshalling di seluruh 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 seterusnya.

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

Nota

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

ComWrappers API dirancang untuk menyediakan interaksi minimal yang diperlukan untuk menyelesaikan interop COM dengan runtime .NET 5+. Ini berarti bahwa banyak keunggulan yang ada dalam sistem interop COM bawaan tidak hadir, oleh karena itu, harus dibangun dari blok bangunan dasar. Dua tanggung jawab utama API adalah:

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

Efisiensi ini dicapai dengan mengharuskan pembuatan dan akuisisi pembungkus melalui ComWrappers API.

ComWrappers Karena API memiliki begitu sedikit tanggung jawab, itu adalah alasan bahwa sebagian besar pekerjaan interop harus ditangani oleh konsumen - ini benar. Namun, pekerjaan tambahan sebagian besar bersifat 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 berarti memberikan informasi yang cukup kepada runtime .NET untuk membuat dan merekam pembungkus bagi objek terkelola yang diproyeksikan ke dalam COM dan bagi objek COM yang diproyeksikan ke dalam .NET. Sebelum kita melihat kerangka subkelas, kita harus menentukan 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 tipe ComWrappers, Anda harus menerapkan tiga metode berikut. Masing-masing metode ini mewakili partisipasi pengguna dalam pembuatan atau penghapusan jenis pembungkus. Metode ComputeVtables() dan CreateObject() membuat Pembungkus Objek Terkelola dan Pembungkus Objek Asli, masing-masing. Metode ReleaseObjects() ini digunakan oleh runtime untuk membuat permintaan agar koleksi pembungkus yang diberikan "dilepaskan" dari objek asli yang mendasarinya. Dalam kebanyakan kasus, isi ReleaseObjects() metode hanya dapat melemparkan 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(), tentukan 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 (DemoImpl).

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

Untuk metode CreateObject() ini, 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 yang kita 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 membangun Tabel Metode Virtual, atau vtable, untuk setiap antarmuka untuk memproyeksikannya ke lingkungan COM. Untuk tutorial ini, Anda akan menentukan vtable sebagai urutan 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, metode antarmuka khusus menyusul. Misalnya, pertimbangkan IDemoGetType dan IDemoStoreType. Secara konseptual, vtable untuk jenisnya 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 IUnknown fungsi? Cara menerapkan 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 itu. Anda bisa mendapatkan implementasi IUnknown menggunakan metode ComWrappers.GetIUnknownImpl().

Ini mungkin tampak seperti Anda telah menerapkan semua metode, tetapi sayangnya, hanya fungsi IUnknown yang dapat digunakan dalam COM vtable. Karena COM berada di luar runtime, Anda harus membuat penunjuk fungsi native untuk 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 meniru tanda tangan fungsi COM. Berikut ini adalah contoh signature COM untuk IDemoGetType.GetString() – ingat bahwa dalam COM ABI, argumen pertama adalah instance itu sendiri.

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

Implementasi pembungkus IDemoGetType.GetString() harus terdiri dari logika marshalling dan kemudian mendistribusikan ke objek terkelola yang sedang dibungkus. Semua status untuk pengiriman terkandung dalam argumen yang disediakan _this . Argumen _this sebenarnya akan berjenis ComInterfaceDispatch*. Jenis ini mewakili struktur tingkat rendah dengan satu bidang, Vtable, yang akan dibahas nanti. Detail lebih lanjut mengenai tipe ini dan tata letaknya adalah detail implementasi waktu proses dan tidak sebaiknya diandalkan. Untuk mengambil instans terkelola dari ComInterfaceDispatch* instans, gunakan kode berikut:

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

Sekarang setelah Anda memiliki metode C# yang dapat dimasukkan ke dalam vtable, Anda dapat membangun vtable. Perhatikan penggunaan RuntimeHelpers.AllocateTypeAssociatedMemory() untuk mengalokasikan memori dengan cara yang 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 mana darinya yang harus dapat digunakan dari COM. Dengan menggunakan vtable yang dibangun, Anda sekarang dapat membuat serangkaian ComInterfaceEntry instans 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 tersebut dapat digunakan untuk semua instans dari tipe tersebut. Pekerjaan di sini dapat dilakukan di static konstruktor atau penginisialisasi modul, tetapi harus dilakukan sebelumnya agar metode ComputeVtables() 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 ditangani adalah seberapa permisif ComWrappers subkelas dalam mendukung jenis COM. Untuk mendukung semua jenis COM, yang memungkinkan, Anda harus menulis sejumlah besar kode atau memanfaatkan Reflection.Emit dengan cerdas. Untuk tutorial ini, Anda hanya akan mendukung instans COM yang mengimplementasikan keduanya, 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 di COM sehingga kita akan menjelajahi kedua opsi.

Pembungkus Objek Natif Statis

Mari kita lihat implementasi statis terlebih dahulu. Pembungkus Objek Asli statis melibatkan mendefinisikan tipe terkelola yang mengimplementasikan antarmuka .NET dan dapat meneruskan panggilan pada tipe terkelola ke instance COM. Berikut adalah kerangka kasar untuk 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 kelas ini dan menyediakannya sebagai pembungkus, Anda harus menentukan beberapa kebijakan. Jika jenis ini digunakan sebagai pembungkus, tampaknya karena mengimplementasikan kedua antarmuka, instans COM yang mendasar harus mengimplementasikan kedua antarmuka juga. Mengingat bahwa 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 Dinamis

Pembungkus dinamis lebih fleksibel karena menyediakan cara untuk tipe yang dapat dikueri saat runtime daripada secara statis. Untuk memberikan dukungan ini, Anda akan menggunakan IDynamicInterfaceCastable. Amati yang DemoNativeDynamicWrapper hanya mengimplementasikan antarmuka ini. Fungsionalitas yang disediakan antarmuka adalah kesempatan untuk menentukan jenis apa yang didukung saat runtime. 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 DemoNativeDynamicWrapper akan mendukung 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 tipe apa pun yang dikembalikan oleh metode IDynamicInterfaceCastable. Ini memiliki manfaat tambahan untuk membuat pemangkasan IL lebih mudah, yang berarti skenario AOT lebih dapat diandalkan.
  2. Proses pemancaran ke DemoNativeDynamicWrapper. Ini adalah bagian dari sifat dinamis dari IDynamicInterfaceCastable. Jenis yang dikembalikan dari IDynamicInterfaceCastable.GetInterfaceImplementation() digunakan untuk “meliputi” jenis yang mengimplementasikan IDynamicInterfaceCastable. Inti di sini adalah this pointer tidak seperti yang sebenarnya karena kita mengizinkan kasus dari DemoNativeDynamicWrapper ke IDemoStoreTypeNativeWrapper.

Mengalihkan 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 unmanaged penunjuk fungsi C#.

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 dari suatu objek mengarah ke vtable tipe tersebut, dan dari situ, slot yang diinginkan dapat diakses. Mari kita asumsikan alamat objek COM adalah 0x10000. Nilai pertama berukuran pointer harus alamat vtable – dalam contoh ini 0x20000. Setelah berada di vtable, Anda mencari slot keempat (indeks 3 dalam pengindeksan berbasis nol) untuk mengakses implementasinya.

COM instance
0x10000  0x20000

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

Memiliki penunjuk fungsi kemudian memungkinkan Anda untuk memanggil fungsi anggota tersebut pada objek dengan meneruskan instans objek sebagai parameter pertama. Pola ini akan terlihat akrab berdasarkan definisi fungsi implementasi Managed Object Wrapper.

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

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

Langkah 4 - Menangani detail siklus hidup Pembungkus Objek Asli

Implementasi ComputeVtables() dan CreateObject() mencakup beberapa detail masa aktif pembungkus, tetapi masih ada pertimbangan lain. Meskipun ini bisa menjadi langkah singkat, ini juga dapat secara signifikan meningkatkan kompleksitas ComWrappers desain.

Tidak seperti Pembungkus Objek Terkelola, yang dikendalikan oleh panggilan ke metode AddRef() dan Release()-nya, 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 instance COM? Ada dua wadah umum:

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

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

Nota

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

Menggunakan ComWrappers subkelas

Anda sekarang memiliki ComWrappers subkelas 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 – ini harus dimungkinkan agar dapat meneruskannya sebagai instans COM.

Mari kita buat Pembungkus Objek Terkelola terlebih dahulu. Buat sebuah instans DemoImpl dan tampilkan status string saat ini.

var demo = new DemoImpl();

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

Sekarang Anda dapat membuat sebuah instance dari DemoComWrappers dan sebuah Objek Terkelola yang dibungkus yang kemudian dapat Anda keluarkan ke dalam lingkungan COM.

var cw = new DemoComWrappers();

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

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

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

Dengan Pembungkus Objek Asli, Anda harus dapat mentransmisikannya ke salah satu antarmuka yang diinginkan dan menggunakannya sebagai objek terkelola normal. Anda dapat memeriksa DemoImpl instans 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 Anda ComWrapper 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 menyediakan pola konseptual yang harus diikuti, kami memperkenalkan CoCreateInstance() API, digunakan untuk Aktivasi COM, dan menggambarkan bagaimana hal itu dapat digunakan dengan ComWrappers.

Asumsikan Anda memiliki kode C# berikut di aplikasi Anda. Contoh di bawah ini menggunakan CoCreateInstance() untuk mengaktifkan kelas COM dan sistem interop COM bawaan untuk marshal instans COM ke antarmuka yang sesuai. Perhatikan bahwa penggunaan typeof(I).GUID terbatas pada assert (pernyataan) dan merupakan kasus penggunaan refleksi yang dapat berdampak jika kode ramah terhadap 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 yang di atas agar menggunakan ComWrappers melibatkan menghapus MarshalAs(UnmanagedType.Interface) dari CoCreateInstance() P/Invoke dan melakukan marshalling 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 ComWrappers.GetOrRegisterObjectForComInstance() API untuk mengaitkan objek terkelola yang baru dibangun dengan instans COM yang diaktifkan.

Pertimbangan tambahan

Kompilasi AOT Native – Ahead-of-time (AOT) memberikan peningkatan biaya startup karena kompilasi JIT dihindari. Menghapus kebutuhan akan kompilasi JIT juga sering diperlukan di beberapa platform. Dukungan terhadap AOT adalah salah satu tujuan dari API ComWrappers, tetapi implementasi pembungkus apa pun harus berhati-hati agar tidak secara tidak sengaja menyebabkan AOT mengalami kegagalan, misalnya karena penggunaan refleksi. Properti Type.GUID adalah contoh di mana pantulan digunakan, tetapi dengan cara yang tidak jelas. Properti Type.GUID menggunakan refleksi untuk memeriksa atribut jenis dan mungkin juga nama jenis serta assembly yang mengandung untuk menghasilkan nilainya.

Pembuatan sumber – Sebagian besar kode yang diperlukan untuk interop COM dan ComWrappers implementasi kemungkinan dapat dibuat secara otomatis oleh beberapa alat. Sumber untuk kedua jenis pembungkus dapat dihasilkan menggunakan definisi COM yang benar - misalnya, Type Library (TLB), IDL, atau Primary Interop Assembly (PIA).

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

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

Untuk implementasi yang sesuai dengan COM, selain masa pakai, sistem tipe, dan fitur fungsional yang dibahas sebelumnya, masih diperlukan pertimbangan tambahan ComWrappers. 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 Pembungkus Objek Asli yang memperhatikan kebutuhan apartemen, tetapi implementasi yang siap produksi apa pun harus memperhatikan kebutuhan apartemen. Untuk mencapai hal ini, sebaiknya gunakan API yang RoGetAgileReference 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 diproksi.