Självstudie: Använda API:et ComWrappers
I den här självstudien får du lära dig hur du underklassar ComWrappers
typen korrekt för att tillhandahålla en optimerad och AOT-vänlig COM-interop-lösning. Innan du påbörjar den här självstudien bör du känna till COM, dess arkitektur och befintliga COM-interop-lösningar.
I den här självstudien implementerar du följande gränssnittsdefinitioner. Dessa gränssnitt och deras implementeringar visar:
- Sorterings- och omarshallingstyper över COM/.NET-gränsen.
- Två distinkta metoder för att använda inbyggda COM-objekt i .NET.
- Ett rekommenderat mönster för att aktivera anpassad COM-interop i .NET 5 och senare.
All källkod som används i den här självstudien är tillgänglig på lagringsplatsen dotnet/samples.
Kommentar
I .NET 8 SDK och senare versioner tillhandahålls en källgenerator som automatiskt genererar en ComWrappers
API-implementering åt dig. Mer information finns i ComWrappers
källgenerering.
C#-definitioner
interface IDemoGetType
{
string? GetString();
}
interface IDemoStoreType
{
void StoreString(int len, string? str);
}
Win32 C++-definitioner
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;
};
Översikt över designen ComWrappers
API:et ComWrappers
har utformats för att ge den minimala interaktion som krävs för att utföra COM-interop med .NET 5+-körningen. Det innebär att många av de finesser som finns med det inbyggda COM-interopsystemet inte finns och måste byggas upp från grundläggande byggstenar. De två primära ansvarsområdena för API:et är:
- Effektiv objektidentifiering (till exempel mappning mellan en
IUnknown*
instans och ett hanterat objekt). - GC-interaktion (Garbage Collector).
Dessa effektivitetsvinster uppnås genom att kräva att omslutningsverktyget skapas och förvärvas för att gå igenom API:et ComWrappers
.
Eftersom API:et ComWrappers
har så få ansvarsområden är det självklart att det mesta av interop-arbetet ska hanteras av konsumenten – det stämmer. Det extra arbetet är dock till stor del mekaniskt och kan utföras av en källgenereringslösning. Till exempel är C#/WinRT-verktygskedjan en källgenereringslösning som bygger på ComWrappers
för att ge Stöd för WinRT-interop.
Implementera en ComWrappers
underklass
Att tillhandahålla en ComWrappers
underklass innebär att ge tillräckligt med information till .NET-körningen för att skapa och registrera omslutningar för hanterade objekt som projiceras i COM- och COM-objekt som projiceras i .NET. Innan vi tittar på en disposition av underklassen bör vi definiera vissa termer.
Hanterad objektomslutning – Hanterade .NET-objekt kräver omslutning för att aktivera användning från en non-.NET miljö. Dessa omslutningar kallas tidigare COM Callable Wrappers (CCW).
Native Object Wrapper – COM-objekt som implementeras på ett non-.NET språk kräver omslutning för att aktivera användning från .NET. Dessa omslutningar kallas tidigare Runtime Callable Wrappers (RCW).
Steg 1 – Definiera metoder för att implementera och förstå deras avsikt
Om du vill utöka ComWrappers
typen måste du implementera följande tre metoder. Var och en av dessa metoder representerar användarens deltagande i skapandet eller borttagningen av en typ av omslutning. Metoderna ComputeVtables()
och CreateObject()
skapar en hanterad objektomslutning respektive inbyggt objektomslutning. Metoden ReleaseObjects()
används av körningen för att göra en begäran om att den angivna samlingen med omslutningar ska "frisläppas" från det underliggande interna objektet. I de flesta fall kan metodens ReleaseObjects()
brödtext helt enkelt kasta NotImplementedException, eftersom den bara anropas i ett avancerat scenario som involverar referensspårarramverket.
// 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();
}
Om du vill implementera ComputeVtables()
metoden bestämmer du vilka hanterade typer som du vill stödja. I den här självstudien stöder vi de två tidigare definierade gränssnitten (IDemoGetType
och IDemoStoreType
) och en hanterad typ som implementerar de två gränssnitten (DemoImpl
).
class DemoImpl : IDemoGetType, IDemoStoreType
{
string? _string;
public string? GetString() => _string;
public void StoreString(int _, string? str) => _string = str;
}
CreateObject()
För metoden måste du också bestämma vad du vill stödja. I det här fallet vet vi dock bara vilka COM-gränssnitt vi är intresserade av, inte COM-klasserna. De gränssnitt som används från COM-sidan är desamma som de som vi projicerar från .NET-sidan (dvs IDemoGetType
. och IDemoStoreType
).
Vi implementerar ReleaseObjects()
inte i den här självstudien.
Steg 2 – Implementera ComputeVtables()
Vi börjar med Hanterad objektomslutning – de här omslutningarna är enklare. Du skapar en virtuell metodtabell, eller en virtuell tabell, för varje gränssnitt för att projicera dem i COM-miljön. I den här självstudien definierar du en vtable som en sekvens med pekare, där varje pekare representerar en implementering av en funktion i ett gränssnitt – ordningen är mycket viktig här. I COM ärver varje gränssnitt från IUnknown
. Typen IUnknown
har tre metoder som definierats i följande ordning: QueryInterface()
, AddRef()
och Release()
. IUnknown
Efter metoderna kommer de specifika gränssnittsmetoderna. Tänk till exempel på IDemoGetType
och IDemoStoreType
. Konceptuellt skulle de virtuella tabellerna för typerna se ut så här:
IDemoGetType | IDemoStoreType
==================================
QueryInterface | QueryInterface
AddRef | AddRef
Release | Release
GetString | StoreString
Om vi tittar på DemoImpl
har vi redan en implementering för GetString()
och StoreString()
, men hur är det med IUnknown
funktionerna? Hur du implementerar en IUnknown
instans ligger utanför omfånget för den här självstudien, men det kan göras manuellt i ComWrappers
. Men i den här självstudien låter du körningen hantera den delen. Du kan hämta implementeringen IUnknown
med hjälp av ComWrappers.GetIUnknownImpl()
metoden .
Det kan verka som om du har implementerat alla metoder, men tyvärr är bara IUnknown
funktionerna förbrukningsbara i en COM-vtable. Eftersom COM ligger utanför körningen måste du skapa inbyggda funktionspekare för implementeringen DemoImpl
. Detta kan göras med hjälp av C#-funktionspekare och UnmanagedCallersOnlyAttribute
. Du kan skapa en funktion som ska infogas i den virtuella tabellen genom att skapa en static
funktion som efterliknar COM-funktionssignaturen. Följande är ett exempel på COM-signaturen för IDemoGetType.GetString()
– kom ihåg från COM ABI att det första argumentet är själva instansen.
[UnmanagedCallersOnly]
public static int GetString(IntPtr _this, IntPtr* str);
Omslutningsimplementeringen av IDemoGetType.GetString()
bör bestå av marshallinglogik och sedan en sändning till det hanterade objekt som omsluts. Allt tillstånd för avsändning finns i det angivna _this
argumentet. Argumentet _this
kommer faktiskt att vara av typen ComInterfaceDispatch*
. Den här typen representerar en lågnivåstruktur med ett enda fält, Vtable
, som kommer att diskuteras senare. Ytterligare information om den här typen och dess layout är en implementeringsinformation om körningen och bör inte vara beroende av. Använd följande kod för att hämta den hanterade instansen från en ComInterfaceDispatch*
instans:
IDemoGetType inst = ComInterfaceDispatch.GetInstance<IDemoGetType>((ComInterfaceDispatch*)_this);
Nu när du har en C#-metod som kan infogas i en vtable kan du skapa den virtuella tabellen. Observera användningen av RuntimeHelpers.AllocateTypeAssociatedMemory()
för att allokera minne på ett sätt som fungerar med oanvändbara sammansättningar.
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;
Allokeringen av virtuella datorer är den första delen av implementeringen ComputeVtables()
. Du bör också skapa omfattande COM-definitioner för typer som du planerar att stödja – tänk DemoImpl
och vilka delar av den som ska kunna användas från COM. Med hjälp av ComInterfaceEntry
de konstruerade virtuella datorerna kan du nu skapa en serie instanser som representerar den fullständiga vyn av det hanterade objektet i 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;
Allokeringen av virtuella datorer och poster för hanterat objektomslutning kan och bör göras i förväg eftersom data kan användas för alla instanser av typen. Arbetet här kan utföras i en static
konstruktor eller en modulinitierare, men det bör göras i förväg så ComputeVtables()
att metoden är så enkel och snabb som möjligt.
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;
}
När du har implementerat ComputeVtables()
metoden kan underklassen ComWrappers
skapa hanterade objektomslutningar för instanser av DemoImpl
. Tänk på att den returnerade hanterade objektomslutningen från anropet till GetOrCreateComInterfaceForObject()
är av typen IUnknown*
. Om det interna API:et som skickas till omslutningen kräver ett annat gränssnitt måste ett Marshal.QueryInterface()
för det gränssnittet utföras.
var cw = new DemoComWrappers();
var demo = new DemoImpl();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
Steg 3 – Implementera CreateObject()
Att skapa en intern objektomslutning har fler implementeringsalternativ och mycket mer nyanser än att konstruera en hanterad objektomslutning. Den första frågan att ta itu med är hur tillåtande underklassen ComWrappers
kommer att vara i stöd för COM-typer. För att stödja alla COM-typer, vilket är möjligt, måste du skriva en stor mängd kod eller använda några smarta användningsområden för Reflection.Emit
. I den här självstudien har du bara stöd för COM-instanser som implementerar både IDemoGetType
och IDemoStoreType
. Eftersom du vet att det finns en begränsad uppsättning och har begränsat att alla angivna COM-instanser måste implementera båda gränssnitten kan du tillhandahålla en enda, statiskt definierad omslutning. Dynamiska fall är dock tillräckligt vanliga i COM för att vi ska utforska båda alternativen.
Statiskt inbyggt objektomslutning
Nu ska vi titta på den statiska implementeringen först. Den statiska inbyggda objektomslutningen omfattar att definiera en hanterad typ som implementerar .NET-gränssnitten och kan vidarebefordra anropen på den hanterade typen till COM-instansen. En grov kontur av den statiska omslutningen följer.
// 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();
}
Om du vill skapa en instans av den här klassen och ange den som omslutning måste du definiera en princip. Om den här typen används som omslutning verkar det som att eftersom den implementerar båda gränssnitten bör den underliggande COM-instansen även implementera båda gränssnitten. Med tanke på att du antar den här principen måste du bekräfta detta via anrop till Marshal.QueryInterface()
på COM-instansen.
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
};
Dynamiskt inbyggt objektomslutning
Dynamiska omslutningar är mer flexibla eftersom de ger ett sätt för typer att frågas vid körning i stället för statiskt. För att kunna tillhandahålla den här supporten använder IDynamicInterfaceCastable
du – mer information finns här. Observera att DemoNativeDynamicWrapper
endast implementerar det här gränssnittet. De funktioner som gränssnittet tillhandahåller är en chans att avgöra vilken typ som stöds vid körning. Källan för den här självstudien gör en statisk kontroll under skapandet, men det är helt enkelt för koddelning eftersom kontrollen kan skjutas upp tills ett anrop görs till 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();
}
Nu ska vi titta på ett av de gränssnitt som kommer att DemoNativeDynamicWrapper
stödja dynamiskt. Följande kod tillhandahåller implementeringen av IDemoStoreType
funktionen med standardgränssnittsmetoder .
[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);
}
}
Det finns två viktiga saker att notera i det här exemplet:
- Attributet
DynamicInterfaceCastableImplementationAttribute
. Det här attributet krävs för alla typer som returneras från enIDynamicInterfaceCastable
metod. Det har den extra fördelen att göra IL-trimning enklare, vilket innebär att AOT-scenarier är mer tillförlitliga. - Den gjutna till
DemoNativeDynamicWrapper
. Detta är en del av den dynamiska karaktären hosIDynamicInterfaceCastable
. Den typ som returneras frånIDynamicInterfaceCastable.GetInterfaceImplementation()
används för att "täcka" den typ som implementerarIDynamicInterfaceCastable
. Kärnan här är pekarenthis
är inte vad den låtsas vara eftersom vi tillåter ett ärende frånDemoNativeDynamicWrapper
tillIDemoStoreTypeNativeWrapper
.
Vidarebefordra anrop till COM-instansen
Oavsett vilken intern objektomslutning som används behöver du möjlighet att anropa funktioner på en COM-instans. Implementeringen av IDemoStoreTypeNativeWrapper.StoreString()
kan fungera som ett exempel på hur unmanaged
du använder C#-funktionspekare.
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);
}
}
Nu ska vi undersöka dereferencing av COM-instansen för att få åtkomst till dess vtable-implementering. COM ABI definierar att den första pekaren för ett objekt är till typens vtable och därifrån kan önskat fack nås. Vi antar att com-objektets adress är 0x10000
. Det första pekarstora värdet ska vara adressen till den virtuella tabellen – i det här exemplet 0x20000
. När du är på den virtuella datorn letar du efter det fjärde facket (index 3 i nollbaserad indexering) för att få åtkomst till implementeringen StoreString()
.
COM instance
0x10000 0x20000
VTable for IDemoStoreType
0x20000 <Address of QueryInterface>
0x20008 <Address of AddRef>
0x20010 <Address of Release>
0x20018 <Address of StoreString>
Med funktionspekaren kan du sedan skicka till den medlemsfunktionen på objektet genom att skicka objektinstansen som den första parametern. Det här mönstret bör se bekant ut baserat på funktionsdefinitionerna för implementeringen av Managed Object Wrapper.
CreateObject()
När metoden har implementerats kan underklassen ComWrappers
skapa inbyggda objektomslutningar för COM-instanser som implementerar både IDemoGetType
och IDemoStoreType
.
IntPtr iunk = ...; // Get a COM instance from native code.
object rcw = cw.GetOrCreateObjectForComInstance(iunk, CreateObjectFlags.UniqueInstance);
Steg 4 – Hantera information om intern objektomslutningslivslängd
Implementeringarna ComputeVtables()
och CreateObject()
omfattade viss information om omslutningens livslängd, men det finns ytterligare överväganden. Även om detta kan vara ett kort steg kan det också avsevärt öka designens ComWrappers
komplexitet.
Till skillnad från den hanterade objektomslutningen, som styrs av anrop till dess AddRef()
och Release()
metoder, hanteras livslängden för en intern objektomslutning icke-terministiskt av GC. Frågan här är när anropar Release()
den inbyggda objektomslutningen på den IntPtr
som representerar COM-instansen? Det finns två allmänna bucketar:
Den interna objektomslutningens finalator ansvarar för att anropa COM-instansens
Release()
metod. Det här är den enda gången det är säkert att anropa den här metoden. I det här läget har det fastställts korrekt av GC att det inte finns några andra referenser till den inbyggda objektomslutningen i .NET-körningen. Det kan finnas komplexitet här om du har rätt stöd för COM Apartments; Mer information finns i avsnittet Ytterligare överväganden .Den inbyggda objektomslutningen implementerar och anropar
IDisposable
Release()
iDispose()
.
Kommentar
Mönstret IDisposable
bör endast stödjas om flaggan skickades in under anropet CreateObject()
CreateObjectFlags.UniqueInstance
. Om det här kravet inte följs är det möjligt att kasserade inbyggda objektomslutningar återanvänds efter att de har kasserats.
Använda underklassen ComWrappers
Nu har du en ComWrappers
underklass som kan testas. För att undvika att skapa ett inbyggt bibliotek som returnerar en COM-instans som implementerar IDemoGetType
och IDemoStoreType
använder du den hanterade objektomslutningen och behandlar den som en COM-instans – detta måste vara möjligt för att kunna skicka com ändå.
Nu ska vi skapa en hanterad objektomslutning först. Instansiera en DemoImpl
instans och visa dess aktuella strängtillstånd.
var demo = new DemoImpl();
string? value = demo.GetString();
Console.WriteLine($"Initial string: {value ?? "<null>"}");
Nu kan du skapa en instans av DemoComWrappers
och en hanterad objektomslutning som du sedan kan skicka till en COM-miljö.
var cw = new DemoComWrappers();
IntPtr ccw = cw.GetOrCreateComInterfaceForObject(demo, CreateComInterfaceFlags.None);
I stället för att skicka den hanterade objektomslutningen till en COM-miljö låtsas du att du just har fått den här COM-instansen, så att du skapar en intern objektomslutning för den i stället.
var rcw = cw.GetOrCreateObjectForComInstance(ccw, CreateObjectFlags.UniqueInstance);
Med inbyggt objektomslutning bör du kunna omvandla det till ett av de önskade gränssnitten och använda det som ett normalt hanterat objekt. Du kan undersöka instansen DemoImpl
och observera effekten av åtgärder på den inbyggda objektomslutningen som omsluter en hanterad objektomslutning som i sin tur omsluter den hanterade instansen.
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}");
Eftersom underklassen ComWrapper
har utformats för att stödja CreateObjectFlags.UniqueInstance
kan du rensa den interna objektomslutningen omedelbart i stället för att vänta på att en GC ska inträffa.
(rcw as IDisposable)?.Dispose();
COM-aktivering med ComWrappers
Skapandet av COM-objekt utförs vanligtvis via COM-aktivering – ett komplext scenario utanför omfånget för det här dokumentet. För att tillhandahålla ett konceptuellt mönster att följa introducerar vi API:et CoCreateInstance()
, som används för COM-aktivering, och visar hur det kan användas med ComWrappers
.
Anta att du har följande C#-kod i ditt program. I exemplet nedan används CoCreateInstance()
för att aktivera en COM-klass och det inbyggda COM-interopsystemet för att konvertera COM-instansen till rätt gränssnitt. Observera att användningen av typeof(I).GUID
är begränsad till en kontroll och handlar om att använda reflektion som kan påverka om koden är AOT-vänlig.
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);
Om du konverterar ovanstående till att använda ComWrappers
måste du ta bort MarshalAs(UnmanagedType.Interface)
från CoCreateInstance()
P/Invoke och utföra marshallingen manuellt.
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);
Det går också att abstrahera bort funktioner i fabriksstil, till exempel ActivateClass<I>
genom att inkludera aktiveringslogik i klasskonstruktorn för en intern objektomslutning. Konstruktorn kan använda API:et ComWrappers.GetOrRegisterObjectForComInstance()
för att associera det nyligen konstruerade hanterade objektet med den aktiverade COM-instansen.
Ytterligare överväganden
Intern AOT – AOT-kompilering (i förväg) ger bättre startkostnad eftersom JIT-kompilering undviks. Att ta bort behovet av JIT-kompilering krävs också ofta på vissa plattformar. Att stödja AOT var ett mål för API:et ComWrappers
, men all omslutningsimplementering måste vara försiktig så att du inte oavsiktligt introducerar fall där AOT bryts ned, till exempel med reflektion. Egenskapen Type.GUID
är ett exempel på var reflektion används, men på ett icke-uppenbart sätt. Egenskapen Type.GUID
använder reflektion för att inspektera typens attribut och sedan potentiellt typens namn och innehåller sammansättning för att generera dess värde.
Källgenerering – Merparten av den kod som behövs för COM-interop och en ComWrappers
implementering kan troligen genereras automatiskt av vissa verktyg. Källa för båda typerna av omslutningar kan genereras med rätt COM-definitioner – till exempel Typbibliotek (TLB), IDL eller en primär interop-sammansättning (PIA).
Global registrering – Eftersom API:et ComWrappers
utformades som en ny fas av COM-interop behövde det ha ett sätt att delvis integrera med det befintliga systemet. Statiska metoder påverkas globalt på API:et ComWrappers
som tillåter registrering av en global instans för olika stöd. Dessa metoder är utformade för ComWrappers
instanser som förväntar sig att tillhandahålla omfattande COM-interop-stöd i alla fall , vilket liknar det inbyggda COM-interop-systemet.
Stöd för referensspårare – Det här stödet används primärt för WinRT-scenarier och representerar ett avancerat scenario. För de flesta ComWrapper
implementeringar bör antingen en CreateComInterfaceFlags.TrackerSupport
flagga eller CreateObjectFlags.TrackerObject
en flagga utlösa en NotSupportedException. Om du vill aktivera det här stödet, kanske på en Windows- eller till och med icke-Windows-plattform, rekommenderar vi starkt att du refererar till C#/WinRT-verktygskedjan.
Förutom livslängd, typsystem och funktionella funktioner som beskrivs tidigare kräver en COM-kompatibel implementering av ComWrappers
ytterligare överväganden. För alla implementeringar som ska användas på Windows-plattformen finns följande överväganden:
Lägenheter – COM:s organisationsstruktur för trådning kallas "Lägenheter" och har strikta regler som måste följas för stabil drift. Den här självstudien implementerar inte lägenhetsmedvetna inbyggda objektomslutningar, men alla produktionsklara implementeringar bör vara lägenhetsmedvetna. För att åstadkomma detta rekommenderar vi att du använder API:et
RoGetAgileReference
som introducerades i Windows 8. För versioner före Windows 8 bör du överväga den globala gränssnittstabellen.Säkerhet – COM tillhandahåller en omfattande säkerhetsmodell för klassaktivering och proxied-behörighet.