Samouczek: korzystanie z interfejsu ComWrappers API

W tym samouczku dowiesz się, jak prawidłowo podklasować ComWrappers typ, aby zapewnić zoptymalizowane i przyjazne dla AOT rozwiązanie międzyoperacyjności modelu COM. Przed rozpoczęciem tego samouczka należy zapoznać się z modelem COM, jego architekturą i istniejącymi rozwiązaniami międzyoperatorowymi modelu COM.

W tym samouczku zaimplementujesz następujące definicje interfejsu. Te interfejsy i ich implementacje będą przedstawiać następujące elementy:

  • Typy marshalling i unmarshalling w granicach COM/.NET.
  • Dwa odrębne podejścia do korzystania z natywnych obiektów COM na platformie .NET.
  • Zalecany wzorzec włączania niestandardowej międzyoperajności modelu COM na platformie .NET 5 i nowszych.

Cały kod źródłowy używany w tym samouczku jest dostępny w repozytorium dotnet/samples.

Uwaga

W zestawie .NET 8 SDK i nowszych wersjach generator źródła jest dostarczany do automatycznego generowania implementacji interfejsu ComWrappers API. Aby uzyskać więcej informacji, zobacz ComWrappers generowanie źródła.

Definicje języka C#

interface IDemoGetType
{
    string? GetString();
}

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

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

ComWrappers Omówienie projektu

Interfejs ComWrappers API został zaprojektowany tak, aby zapewnić minimalną interakcję wymaganą do współdziałania modelu COM ze środowiskiem uruchomieniowym platformy .NET 5+. Oznacza to, że wiele elementów, które istnieją z wbudowanym systemem międzyoperacyjności COM, nie są obecne i muszą być zbudowane z podstawowych bloków konstrukcyjnych. Dwa główne obowiązki interfejsu API to:

Te korzyści są osiągane przez wymaganie tworzenia otoki i pozyskiwania ComWrappers w celu przejścia przez interfejs API.

ComWrappers Ponieważ interfejs API ma tak mało obowiązków, oznacza to, że większość pracy międzyoperacyjnej powinna być obsługiwana przez konsumenta — to prawda. Jednak dodatkowa praca jest w dużej mierze mechaniczna i może być wykonywana przez rozwiązanie do generowania źródła. Na przykład łańcuch narzędzi C#/WinRT to rozwiązanie do generowania źródła, które jest oparte na ComWrappers systemie w celu zapewnienia obsługi międzyoperacyjności winRT.

Implementowanie podklasy ComWrappers

ComWrappers Zapewnienie podklasy oznacza zapewnienie wystarczającej ilości informacji do środowiska uruchomieniowego platformy .NET w celu utworzenia i rejestrowania otoek dla obiektów zarządzanych przewidywanych w obiektach COM i COM do platformy .NET. Zanim przyjrzymy się konspekcie podklasy, należy zdefiniować niektóre terminy.

Otoka obiektów zarządzanych — zarządzane obiekty platformy .NET wymagają otoki w celu włączenia użycia ze środowiska non-.NET. Te otoki są historycznie nazywane otokami wywoływanymi COM (CCW).

Otoka obiektów natywnych — obiekty COM implementowane w języku non-.NET wymagają otoki w celu włączenia użycia z platformy .NET. Te otoki są historycznie nazywane otokami wywoływanymi środowiska uruchomieniowego (RCW).

Krok 1. Definiowanie metod implementowania i zrozumienia ich intencji

Aby rozszerzyć ComWrappers typ, należy zaimplementować następujące trzy metody. Każda z tych metod reprezentuje udział użytkownika w tworzeniu lub usuwaniu typu otoki. Metody ComputeVtables() i CreateObject() tworzą odpowiednio otokę obiektów zarządzanych i otokę obiektów natywnych. Metoda ReleaseObjects() jest używana przez środowisko uruchomieniowe, aby wysłać żądanie dla dostarczonej kolekcji otoek, które mają zostać "zwolnione" z bazowego obiektu natywnego. W większości przypadków treść ReleaseObjects() metody może po prostu zgłosić NotImplementedExceptionwartość , ponieważ jest wywoływana tylko w zaawansowanym scenariuszu obejmującym strukturę Tracker odwołań.

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

Aby zaimplementować metodę ComputeVtables() , zdecyduj, które typy zarządzane mają być obsługiwane. W tym samouczku będziemy obsługiwać dwa wcześniej zdefiniowane interfejsy (IDemoGetType i IDemoStoreType) oraz typ zarządzany, który implementuje dwa interfejsy (DemoImpl).

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

W przypadku CreateObject() metody należy również określić, co chcesz obsługiwać. W tym przypadku jednak wiemy tylko interfejsy COM, które nas interesują, a nie klasy COM. Interfejsy używane po stronie modelu COM są takie same jak te, które projektujemy po stronie platformy .NET (czyli IDemoGetType i IDemoStoreType).

W tym samouczku nie będziemy implementować ReleaseObjects() .

Krok 2 . Implementowanie ComputeVtables()

Zacznijmy od otoki zarządzanego obiektu — te otoki są łatwiejsze. Utworzysz tabelę metody wirtualnej lub tabelę vtable dla każdego interfejsu w celu ich projekcji w środowisku COM. W tym samouczku zdefiniujesz tabelę wirtualną jako sekwencję wskaźników, gdzie każdy wskaźnik reprezentuje implementację funkcji w interfejsie — kolejność jest bardzo ważna. W modelu COM każdy interfejs dziedziczy z IUnknownklasy . Typ IUnknown ma trzy metody zdefiniowane w następującej kolejności: QueryInterface(), AddRef()i Release(). IUnknown Po dokonaniu metod określonych metod interfejsu. Rozważmy na przykład elementy IDemoGetType i IDemoStoreType. Koncepcyjnie tabele wirtualne dla typów będą wyglądać następująco:

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

Patrząc na DemoImplelement , mamy już implementację dla GetString() i StoreString(), ale co z IUnknown funkcjami? Jak zaimplementować IUnknown wystąpienie wykracza poza zakres tego samouczka, ale można to zrobić ręcznie w programie ComWrappers. Jednak w tym samouczku pozwolisz, aby środowisko uruchomieniowe obsłużyło tę część. Implementację IUnknown można uzyskać przy użyciu ComWrappers.GetIUnknownImpl() metody .

Może się wydawać, że zaimplementowano wszystkie metody, ale niestety tylko IUnknown te funkcje są eksploatacyjne w tabeli wirtualnej COM. Ponieważ com znajduje się poza środowiskiem uruchomieniowym, należy utworzyć natywne wskaźniki funkcji do DemoImpl implementacji. Można to zrobić przy użyciu wskaźników funkcji języka C# i UnmanagedCallersOnlyAttribute. Funkcję do wstawienia do tabeli wirtualnej można utworzyć, tworząc static funkcję, która naśladuje podpis funkcji COM. Poniżej znajduje się przykład podpisu IDemoGetType.GetString() COM — przypomnij sobie z modelu COM ABI, że pierwszym argumentem jest samo wystąpienie.

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

Implementacja IDemoGetType.GetString() otoki powinna składać się z logiki marshalingu, a następnie wysłania do obiektu zarządzanego, który jest opakowany. Cały stan wysyłania znajduje się w podanym _this argumencie. Argument _this będzie faktycznie typu ComInterfaceDispatch*. Ten typ reprezentuje strukturę niskiego poziomu z pojedynczym polem, Vtablektóre zostanie omówione później. Dalsze szczegóły tego typu i jego układu są szczegółami implementacji środowiska uruchomieniowego i nie powinny być zależne. Aby pobrać wystąpienie zarządzane z ComInterfaceDispatch* wystąpienia, użyj następującego kodu:

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

Teraz, gdy masz metodę języka C#, którą można wstawić do tabeli wirtualnej, możesz skonstruować tabelę wirtualną. Zwróć uwagę na użycie RuntimeHelpers.AllocateTypeAssociatedMemory() funkcji przydzielania pamięci w sposób, który działa z zestawami , które można zwolnić .

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;

Alokacja tabel wirtualnych jest pierwszą częścią implementacji ComputeVtables(). Należy również utworzyć kompleksowe definicje MODELU COM dla typów, które planujesz obsługiwać — pomyśl DemoImpl i jakie części powinny być użyteczne z modelu COM. Korzystając z skonstruowanych tabel wirtualnych, można teraz utworzyć serię ComInterfaceEntry wystąpień reprezentujących pełny widok obiektu zarządzanego w modelu 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;

Alokacja tabel wirtualnych i wpisów dla otoki obiektów zarządzanych może i powinna być wykonywana z wyprzedzeniem, ponieważ dane mogą być używane dla wszystkich wystąpień typu. W tym miejscu można wykonać pracę w konstruktorze static lub inicjatorze modułu, ale należy wykonać ją z wyprzedzeniem, aby ComputeVtables() metoda była tak prosta i szybka, jak to możliwe.

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

Po zaimplementowaniu ComputeVtables() metody podklasa ComWrappers będzie mogła tworzyć otoki obiektów zarządzanych dla wystąpień DemoImplklasy . Należy pamiętać, że zwrócona otoka obiektu zarządzanego z wywołania do GetOrCreateComInterfaceForObject() jest typu IUnknown*. Jeśli natywny interfejs API przekazywany do otoki wymaga innego interfejsu, Marshal.QueryInterface() należy wykonać dla tego interfejsu.

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

Krok 3 . Implementowanie CreateObject()

Konstruowanie otoki obiektów natywnych ma więcej opcji implementacji i znacznie więcej niuansów niż konstruowanie otoki obiektów zarządzanych. Pierwsze pytanie do rozwiązania polega na tym, jak permissive podklasa ComWrappers będzie obsługiwać typy COM. Aby obsługiwać wszystkie typy MODELU COM, co jest możliwe, należy napisać znaczną ilość kodu lub stosować kilka sprytnych zastosowań Reflection.Emitprogramu . Na potrzeby tego samouczka będziesz obsługiwać tylko wystąpienia modelu COM, które implementują zarówno polecenia , jak IDemoGetType i IDemoStoreType. Ponieważ wiesz, że istnieje zestaw skończony i mają ograniczone, że każde dostarczone wystąpienie COM musi zaimplementować oba interfejsy, można udostępnić jedną, statycznie zdefiniowaną otokę; jednak przypadki dynamiczne są na tyle powszechne w modelu COM, że poznamy obie opcje.

Otoka obiektów statycznych natywnych

Najpierw przyjrzyjmy się implementacji statycznej. Statyczna otoka obiektów natywnych obejmuje zdefiniowanie typu zarządzanego, który implementuje interfejsy platformy .NET i może przekazywać wywołania typu zarządzanego do wystąpienia modelu COM. Poniżej przedstawiono przybliżony kontur statycznej otoki.

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

Aby utworzyć wystąpienie tej klasy i podać je jako otokę, należy zdefiniować pewne zasady. Jeśli ten typ jest używany jako otoka, wydaje się, że ponieważ implementuje oba interfejsy, bazowe wystąpienie MODELU COM również powinno implementować oba interfejsy. Biorąc pod uwagę, że te zasady są wdrażane, należy to potwierdzić za pomocą wywołań w Marshal.QueryInterface() wystąpieniu modelu 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
};

Dynamiczne otoki obiektów natywnych

Dynamiczne otoki są bardziej elastyczne, ponieważ umożliwiają wykonywanie zapytań dotyczących typów w czasie wykonywania zamiast statycznie. Aby zapewnić tę pomoc techniczną, będziesz korzystać IDynamicInterfaceCastable z tej pomocy — więcej szczegółów można znaleźć tutaj. Zauważ, że DemoNativeDynamicWrapper tylko implementuje ten interfejs. Funkcja zapewniana przez interfejs jest szansą na określenie, jaki typ jest obsługiwany w czasie wykonywania. Źródło tego samouczka wykonuje sprawdzanie statyczne podczas tworzenia, ale jest to po prostu do udostępniania kodu, ponieważ sprawdzanie może zostać odroczone do momentu wywołania metody 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();
}

Przyjrzyjmy się jednemu z interfejsów, które DemoNativeDynamicWrapper będą dynamicznie obsługiwane. Poniższy kod zawiera implementację IDemoStoreType funkcji domyślnych metod interfejsu.

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

W tym przykładzie należy zwrócić uwagę na dwie ważne kwestie:

  1. Atrybut DynamicInterfaceCastableImplementationAttribute . Ten atrybut jest wymagany dla dowolnego typu, który jest zwracany z IDynamicInterfaceCastable metody. Ma dodatkową zaletę ułatwienia przycinania IL, co oznacza, że scenariusze AOT są bardziej niezawodne.
  2. Rzutowanie na DemoNativeDynamicWrapper. Jest to część dynamicznego charakteru .IDynamicInterfaceCastable Typ zwracany z IDynamicInterfaceCastable.GetInterfaceImplementation() jest używany do "koc" typu, który implementuje IDynamicInterfaceCastable. W tym miejscu wskaźnik nie jest this tym, co udaje, ponieważ zezwalamy na przypadek z DemoNativeDynamicWrapper do IDemoStoreTypeNativeWrapper.

Przekazywanie wywołań do wystąpienia modelu COM

Niezależnie od tego, która otoka obiektów natywnych jest używana, potrzebna jest możliwość wywoływania funkcji w wystąpieniu modelu COM. Implementacja IDemoStoreTypeNativeWrapper.StoreString() programu może służyć jako przykład zastosowania unmanaged wskaźników funkcji języka 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);
    }
}

Przeanalizujmy wyłuszczanie wystąpienia MODELU COM, aby uzyskać dostęp do implementacji tabeli wirtualnej. ABI modelu COM definiuje, że pierwszym wskaźnikiem obiektu jest tabela wirtualna typu, a następnie można uzyskać dostęp do żądanego miejsca. Załóżmy, że adres obiektu COM to 0x10000. Pierwsza wartość o rozmiarze wskaźnika powinna być adresem tabeli wirtualnej — w tym przykładzie 0x20000. Po przejściu do tabeli wirtualnej wyszukaj czwarte miejsce (indeks 3 w indeksie zerowym), aby uzyskać dostęp do implementacji StoreString() .

COM instance
0x10000  0x20000

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

Dzięki wskaźnikowi funkcji można następnie wysłać do tej funkcji składowej w tym obiekcie, przekazując wystąpienie obiektu jako pierwszy parametr. Ten wzorzec powinien wyglądać znajomo na podstawie definicji funkcji implementacji otoki obiektów zarządzanych.

Po zaimplementowaniu CreateObject() metody podklasa ComWrappers będzie mogła tworzyć natywne otoki obiektów dla wystąpień COM, które implementują zarówno IDemoGetType klasy , jak i IDemoStoreType.

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

Krok 4. Obsługa szczegółów okresu istnienia otoki obiektów natywnych

Implementacje ComputeVtables() i CreateObject() obejmowały pewne szczegóły okresu istnienia otoki, ale istnieją dalsze zagadnienia. Chociaż może to być krótki krok, może również znacznie zwiększyć złożoność ComWrappers projektu.

W przeciwieństwie do otoki obiektów zarządzanych, która jest kontrolowana przez wywołania metody AddRef() i Release() , okres istnienia otoki obiektów natywnych jest nieokreślono obsługiwany przez GC. Pytanie brzmi: kiedy otoka obiektów natywnych wywołuje Release()IntPtr wystąpienie MODELU COM? Istnieją dwa ogólne zasobniki:

  1. Finalizator otoki obiektów natywnych jest odpowiedzialny za wywoływanie metody wystąpienia Release() MODELU COM. Jest to jedyny moment, kiedy można bezpiecznie wywołać tę metodę. W tym momencie została ona prawidłowo określona przez GC, że w środowisku uruchomieniowym platformy .NET nie ma żadnych innych odwołań do otoki obiektów natywnych. Jeśli prawidłowo obsługujesz apartamenty COM, może wystąpić tu złożoność; Aby uzyskać więcej informacji, zobacz sekcję Dodatkowe zagadnienia .

  2. Otoka obiektów natywnych implementuje IDisposable i wywołuje metodę Release() w pliku Dispose().

Uwaga

Wzorzec IDisposable powinien być obsługiwany tylko wtedy, gdy podczas wywołania CreateObject() przekazano flagę CreateObjectFlags.UniqueInstance . Jeśli to wymaganie nie jest zgodne, po usunięciu można ponownie użyć otoki obiektów natywnych po usunięciu.

Używanie podklasy ComWrappers

Masz teraz podklasę ComWrappers , którą można przetestować. Aby uniknąć tworzenia biblioteki natywnej, która zwraca wystąpienie MODELU COM, które implementuje IDemoGetType i IDemoStoreType, użyjesz otoki obiektów zarządzanych i traktujesz ją jako wystąpienie MODELU COM — musi to być możliwe, aby mimo to przekazać go com.

Najpierw utwórzmy otokę obiektów zarządzanych. DemoImpl Utwórz wystąpienie wystąpienia i wyświetl jego bieżący stan ciągu.

var demo = new DemoImpl();

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

Teraz możesz utworzyć wystąpienie i otokę obiektów zarządzanych DemoComWrappers , które można następnie przekazać do środowiska COM.

var cw = new DemoComWrappers();

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

Zamiast przekazywać otokę obiektów zarządzanych do środowiska COM, udaj, że właśnie odebrano to wystąpienie MODELU COM, więc utworzysz dla niego otokę obiektów natywnych.

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

Dzięki otoce obiektów natywnych powinno być możliwe rzutowanie go do jednego z żądanych interfejsów i użycie go jako normalnego zarządzanego obiektu. Możesz zbadać DemoImpl wystąpienie i obserwować wpływ operacji na otokę obiektów natywnych, która opakowuje otokę obiektów zarządzanych, która z kolei opakowuje wystąpienie zarządzane.

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

Ponieważ podklasa ComWrapper została zaprojektowana tak, aby obsługiwała CreateObjectFlags.UniqueInstanceusługę , można natychmiast wyczyścić otokę obiektów natywnych zamiast czekać na wystąpienie GC.

(rcw as IDisposable)?.Dispose();

Aktywacja MODELU COM za pomocą polecenia ComWrappers

Tworzenie obiektów COM jest zwykle wykonywane za pośrednictwem aktywacji MODELU COM — złożony scenariusz poza zakresem tego dokumentu. Aby zapewnić wzorzec koncepcyjny do zastosowania, wprowadzamy CoCreateInstance() interfejs API, używany do aktywacji modelu COM i ilustrujemy, jak można go używać z usługą ComWrappers.

Załóżmy, że w aplikacji masz następujący kod w języku C#. W poniższym przykładzie użyto CoCreateInstance() metody do aktywowania klasy COM i wbudowanego systemu międzyoperatorowego MODELU COM w celu marshalingu wystąpienia MODELU COM do odpowiedniego interfejsu. Należy pamiętać, że użycie elementu typeof(I).GUID jest ograniczone do potwierdzenia i jest przypadkiem użycia odbicia, które może mieć wpływ na to, czy kod jest przyjazny dla 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);

Konwertowanie powyższych elementów do użycia ComWrappers obejmuje usunięcie MarshalAs(UnmanagedType.Interface) elementu z CoCreateInstance() P/Invoke i ręczne przeprowadzanie marshalingu.

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

Istnieje również możliwość abstrakcji funkcji w stylu fabrycznym, takich jak ActivateClass<I> uwzględnienie logiki aktywacji w konstruktorze klasy dla otoki obiektów natywnych. Konstruktor może używać interfejsu ComWrappers.GetOrRegisterObjectForComInstance() API do skojarzenia nowo skonstruowanego obiektu zarządzanego z aktywowanym wystąpieniem MODELU COM.

Uwagi dodatkowe

Natywna kompilacja AOT — kompilacja Z wyprzedzeniem (AOT ) zapewnia ulepszony koszt uruchamiania, ponieważ kompilacja JIT jest unikana. Usunięcie potrzeby kompilacji JIT jest również często wymagane na niektórych platformach. Obsługa AOT była celem interfejsu ComWrappers API, ale każda implementacja otoki musi być ostrożna, aby nie przypadkowo wprowadzać przypadków, w których podział AOT, na przykład użycie odbicia. Właściwość Type.GUID jest przykładem użycia odbicia, ale w nieoczywisty sposób. Właściwość Type.GUID używa odbicia w celu sprawdzenia atrybutów typu, a następnie potencjalnie nazwy typu i zawierającego zestaw w celu wygenerowania jego wartości.

Generowanie źródła — większość kodu potrzebnego do współdziałania modelu COM i implementacja ComWrappers może zostać wygenerowana automatycznie przez niektóre narzędzia. Źródło obu typów otoek można wygenerować, biorąc pod uwagę odpowiednie definicje COM — na przykład biblioteka typów (TLB), IDL lub podstawowy zestaw międzyoperacyjny (PIA).

Rejestracja globalna — ponieważ ComWrappers interfejs API został zaprojektowany jako nowa faza współdziałania modelu COM, konieczne było częściowe zintegrowanie z istniejącym systemem. Istnieją globalnie wpływające na metody statyczne interfejsu ComWrappers API, które umożliwiają rejestrację wystąpienia globalnego na potrzeby różnych obsługi. Te metody są przeznaczone dla ComWrappers wystąpień, które spodziewają się zapewnić kompleksową obsługę międzyoperacyjności MODELU COM we wszystkich przypadkach — tzn. wbudowany system międzyoperacyjności MODELU COM.

Obsługa monitora odwołań — ta obsługa jest podstawowa używana w scenariuszach WinRT i reprezentuje zaawansowany scenariusz. W przypadku większości ComWrapper implementacji flaga CreateComInterfaceFlags.TrackerSupport lub CreateObjectFlags.TrackerObject powinna zgłaszać NotSupportedExceptionwartość . Jeśli chcesz włączyć tę obsługę, być może na platformie Windows, a nawet innej niż Windows, zdecydowanie zaleca się odwołanie do łańcucha narzędzi C#/WinRT.

Oprócz okresu istnienia, systemu typów i funkcji, które zostały omówione wcześniej, implementacja zgodna ze ComWrappers standardem COM wymaga dodatkowych zagadnień. W przypadku każdej implementacji, która będzie używana na platformie Windows, należy wziąć pod uwagę następujące zagadnienia:

  • Apartamenty – struktura organizacyjna COM do wątkowania nazywa się "Apartamenty" i ma ścisłe zasady, które należy przestrzegać w celu zapewnienia stabilnych operacji. Ten samouczek nie implementuje natywnych otoek obiektów z obsługą apartamentów, ale każda implementacja gotowa do produkcji powinna być świadoma apartamentów. W tym celu zalecamy użycie interfejsu API wprowadzonego RoGetAgileReference w systemie Windows 8. W przypadku wersji starszych niż Windows 8 rozważ tabelę interfejsu globalnego.

  • Zabezpieczenia — com udostępnia zaawansowany model zabezpieczeń umożliwiający aktywację klas i uprawnienia proxied.