Aracılığıyla paylaş


Kılavuz: API'yi ComWrappers kullanma

Bu öğreticide, iyileştirilmiş ve AOT uyumlu bir COM birlikte çalışma çözümü sağlamak için ComWrappers türünü uygun bir şekilde alt sınıf oluşturmayı öğreneceksiniz. Bu öğreticiye başlamadan önce COM, mimarisi ve mevcut COM birlikte çalışma çözümleri hakkında bilgi sahibi olmanız gerekir.

Bu öğreticide aşağıdaki arabirim tanımlarını uygulayacaksınız. Bu arabirimler ve uygulamaları şunları gösterir:

  • COM/.NET arayüzünde türleri dizileme ve geri dizileme.
  • .NET'te yerel COM nesnelerini kullanmaya yönelik iki farklı yaklaşım.
  • .NET 5 ve sonrasında özel COM birlikte çalışabilirlik özelliğini etkinleştirmek için önerilen desen.

Bu öğreticide kullanılan tüm kaynak kodu dotnet/samples deposunda kullanılabilir.

Uyarı

.NET 8 SDK ve sonraki sürümlerinde, sizin için otomatik olarak bir API uygulaması oluşturmak için bir ComWrappers kaynak oluşturucu sağlanır. Daha fazla bilgi için bkz ComWrappers . kaynak oluşturma.

C# tanımları

interface IDemoGetType
{
    string? GetString();
}

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

Win32 C++ tanımları

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

Tasarıma ComWrappers genel bakış

API, ComWrappers .NET 5+ çalışma zamanı ile COM ile birlikte çalışmak için gereken en az etkileşimi sağlamak üzere tasarlanmıştır. Bu, yerleşik COM birlikte çalışma sistemiyle var olan özelliklerin çoğunun mevcut olmadığı ve temel yapı bloklarından oluşturulması gerektiği anlamına gelir. API'nin iki birincil sorumluluğu şunlardır:

  • Verimli nesne tanımlama (örneğin, bir IUnknown* örnek ve yönetilen nesne arasında eşleme).
  • Çöp Toplayıcı (GC) etkileşimi.

Bu verimlilikler, SARMALAYıCı oluşturma ve alma işleminin ComWrappers API üzerinden geçmesi gerekerek gerçekleştirilir.

ComWrappers API'nin çok az sorumluluğu olduğundan, birlikte çalışma çalışmalarının çoğunun tüketici tarafından ele alınması gerektiği anlamına gelir; bu doğrudur. Bununla birlikte, ek iş büyük ölçüde mekaniktir ve bir kaynak oluşturma çözümü tarafından gerçekleştirilebilir. Örneğin, C#/WinRT araç zinciri, WinRT birlikte çalışma desteği sağlamak amacıyla ComWrappers üzerine inşa edilmiş bir kaynak üretim çözümüdür.

ComWrappers alt sınıfı uygulama

Alt sınıf sağlamak ComWrappers , COM'a yansıtılan yönetilen nesneler ve .NET'e yansıtılan COM nesneleri için sarmalayıcılar oluşturmak ve kaydetmek için .NET çalışma zamanına yeterli bilgi vermek anlamına gelir. Alt sınıfın ana hatlarını incelemeden önce bazı terimler tanımlamamız gerekir.

Yönetilen Nesne Sarmalayıcı – Yönetilen .NET nesneleri, non-.NET bir ortamdan kullanımı etkinleştirmek için sarmalayıcılar gerektirir. Bu sarmalayıcılar geçmişte COM Çağrılabilir Sarmalayıcılar (CCW) olarak adlandırılır.

Yerel Nesne Sarmalayıcı – non-.NET dilde uygulanan COM nesneleri, sarmalayıcıların .NET'ten kullanımı etkinleştirmesini gerektirir. Bu sarmalayıcılar geçmişte Çalışma Zamanı Çağrılabilir Sarmalayıcıları (RCW) olarak adlandırılır.

1. Adım – Amaçlarını uygulamak ve anlamak için yöntemler tanımlama

Türü genişletmek ComWrappers için aşağıdaki üç yöntemi uygulamanız gerekir. Bu yöntemlerin her biri, kullanıcının bir sarmalayıcı türü oluşturma veya silme işlemine katılımını temsil eder. ComputeVtables() ve CreateObject() yöntemleri sırasıyla bir Yönetilen Nesne Sarmalayıcı ve Yerel Nesne Sarmalayıcı oluşturur. Çalışma zamanı, sağlanan sarmalayıcı koleksiyonunun, temel alınan yerel nesneden "serbest bırakılması" için istekte bulunmak amacıyla ReleaseObjects() yöntemini kullanır. Çoğu durumda, ReleaseObjects() yöntemi, yalnızca NotImplementedException içeren gelişmiş bir senaryoda çağrıldığında basitçe fırlatabilir.

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

ComputeVtables() yöntemini uygulamak için, hangi yönetilen türleri desteklemek istediğinize karar verin. Bu öğreticide, daha önce tanımlanmış iki arabirimi (IDemoGetType ve IDemoStoreType) ve bu iki arabirimi uygulayan bir yönetilen türü (DemoImpl) destekleyeceğiz.

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

yöntemi için CreateObject() neyi desteklemek istediğinizi de belirlemeniz gerekir. Ancak bu durumda, COM sınıflarını değil yalnızca ilgilendiğimiz COM arabirimlerini biliyoruz. COM tarafından tüketilen arabirimler, .NET tarafından yansıtılan arabirimlerle aynıdır (yani IDemoGetType ve IDemoStoreType).

Bu öğreticide ReleaseObjects() uygulamayacağız.

2. Adım – Uygulama ComputeVtables()

Yönetilen Nesne Sarmalayıcı ile başlayalım; bu sarmalayıcılar daha kolaydır. Bunları COM ortamına yansıtmak için her arabirim için bir Sanal Yöntem Tablosu veya vtable oluşturacaksınız. Bu öğreticide, bir vtable'ı işaretçi dizisi olarak tanımlayacaksınız; burada her işaretçi bir arabirimdeki işlevin uygulamasını temsil eder; burada sıra çok önemlidir. COM'da her arabirim IUnknown'den devralır. Türün IUnknown şu sırada tanımlanan üç yöntemi vardır: QueryInterface(), AddRef() ve Release(). IUnknown Yöntemler geldikten sonra belirli arabirim yöntemleri gelir. Örneğin, IDemoGetType ve IDemoStoreType'i göz önünde bulundurun. Kavramsal olarak, türlerin vtable'ları aşağıdaki gibi görünür:

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

DemoImpl öğesine baktığımızda, GetString() ve StoreString() için zaten bir uygulamamız var, ama IUnknown fonksiyonları ne olacak? Bir IUnknown örneğin nasıl uygulanacağı bu eğitimin kapsamı dışındadır, ancak ComWrappers içinde elle yapılabilir. Ancak bu öğreticide çalışma zamanının bu bölümü işlemesine izin vereceksiniz. IUnknown uygulamasını ComWrappers.GetIUnknownImpl() yöntemini kullanarak alabilirsiniz.

Tüm yöntemleri uygulamış gibi görünebilirsiniz, ancak ne yazık ki yalnızca IUnknown fonksiyonlar bir COM vtable içinde kullanılabilir. COM çalışma zamanının dışında olduğundan, uygulamanız için DemoImpl yerel işlev işaretçileri oluşturmanız gerekir. Bu işlem C# işlev işaretçileri ve UnmanagedCallersOnlyAttributekullanılarak yapılabilir. COM işlev imzasını taklit eden bir işlev oluşturarak vtable'a eklenecek bir static işlev oluşturabilirsiniz. Aşağıda IDemoGetType.GetString() için bir COM imzası örneği verilmiştir – COM ABI'den hatırlayın ki, ilk bağımsız değişken örneğin kendisidir.

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

sarmalayıcı uygulaması IDemoGetType.GetString() , sıralama mantığından ve ardından sarmalanan yönetilen nesneye bir gönderimden oluşmalıdır. Dağıtım için tüm durum, sağlanan _this bağımsız değişken içinde yer alır. Argüman _this aslında ComInterfaceDispatch* türünde olacaktır. Bu tür, Vtabledaha sonra tartışılacak tek bir alanı olan düşük düzeyli bir yapıyı temsil eder. Bu tür ve düzeni hakkındaki diğer ayrıntılar, çalışma zamanının uygulama ayrıntılarıdır ve bunlara güvenilmemelidir. Yönetilen örneği bir ComInterfaceDispatch* örnekten almak için aşağıdaki kodu kullanın:

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

Artık bir vtable'a eklenebilen bir C# yönteminiz olduğuna göre, vtable'ı oluşturabilirsiniz. Belleği RuntimeHelpers.AllocateTypeAssociatedMemory() derlemelerle uyumlu olacak şekilde ayırmak için kullanımına dikkat edin.

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;

"ComputeVtables() uygulamasının ilk adımı, sanal tablo (vtables) tahsisidir." Ayrıca, desteklemeyi planladığınız türler için kapsamlı COM tanımları oluşturmanız gerekir; bu tanımlardan hangi bölümlerin COM'dan kullanılabilir olması gerektiğini düşünün. Artık, yapılandırılan vtable'ları kullanarak, COM'da yönetilen nesnenin ComInterfaceEntry tam görünümünü temsil eden bir dizi örnek oluşturabilirsiniz.

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;

Yönetilen Nesne Sarmalayıcı için vtable'ların ve girişlerin ayrılması, verilerin türün tüm örnekleri için kullanılabilmesi için önceden yapılabilir ve yapılması gerekir. Buradaki iş bir static oluşturucuda veya modül başlatıcıda gerçekleştirilebilir, ancak yöntemin mümkün olduğunca basit ve hızlı olması için ComputeVtables() önceden yapılması gerekir.

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

Bir kez ComputeVtables() yöntemi uygulandığında, ComWrappers alt sınıfı DemoImpl örnekleri için Yönetilen Nesne Sarmalayıcıları üretebilecektir. GetOrCreateComInterfaceForObject() çağrısında döndürülen Yönetilen Nesne Sarmalayıcı'nın IUnknown* türünde olduğunu unutmayın. Sarmalayıcıya geçirilen yerel API farklı bir arabirim gerektiriyorsa, bu arabirim için bir Marshal.QueryInterface() gerçekleştirilmelidir.

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

3. Adım – Uygulama CreateObject()

Yerel Nesne Sarmalayıcı oluşturma işlemi, Yönetilen Nesne Sarmalayıcı oluşturmaktan çok daha fazla uygulama seçeneğine ve çok daha fazla ayrıntıya sahiptir. Ele alınması gereken ilk soru, alt sınıfın ComWrappers destekleyici COM türlerinde ne kadar izinli olacağıdır. Mümkün olan tüm COM türlerini desteklemek için önemli miktarda kod yazmanız veya bazı akıllı kullanımlarını Reflection.Emitkullanmanız gerekir. Bu öğreticide, yalnızca hem IDemoGetTypehem de IDemoStoreType uygulayan COM örneklerini destekleyeceksiniz. Sonlu bir küme olduğunu bildiğiniz ve sağlanan tüm COM örneklerinin her iki arabirimi de uygulaması gerektiğini kısıtladığınız için, tek, statik olarak tanımlanmış bir sarmalayıcı sağlayabilirsiniz; ancak com'da dinamik durumlar her iki seçeneği de keşfedeceğimiz kadar yaygındır.

Statik Doğal Nesne Sarmalayıcı

Önce statik uygulamaya bakalım. Statik Yerel Nesne Sarmalayıcı, .NET arabirimlerini uygulayan bir yönetilen tür tanımlamayı içerir ve yönetilen türdeki çağrıları COM örneğine iletebilir. Statik sarıcı için kaba bir taslak aşağıda verilmiştir.

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

Bu sınıfın bir örneğini oluşturmak ve sarmalayıcı olarak sağlamak için bazı ilkeler tanımlamanız gerekir. Bu tür sarmalayıcı olarak kullanılıyorsa, her iki arabirimi de uyguladığından, temel alınan COM örneğinin her iki arabirimi de uygulaması gerektiği görünür. Bu ilkeyi benimsediğinize göre, COM örneğindeki çağrılar Marshal.QueryInterface() aracılığıyla bunu onaylamanız gerekir.

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

Dinamik Yerel Nesne Sarmalayıcı

Dinamik sarmalayıcılar, türlerin statik değil, çalışma zamanında sorgulanabilmesi için bir yol sağladığından daha esnektir. Bu desteği sağlamak için IDynamicInterfaceCastable kullanacaksınız. Yalnızca DemoNativeDynamicWrapper bu arabirimi uyguladığını gözlemleyin. Arabirimin sağladığı işlevsellik, çalışma zamanında hangi türün desteklendiğini belirleme şansıdır. Bu öğreticinin kaynağı oluşturma sırasında statik bir denetim yapar, ancak bu yalnızca kod paylaşımına yöneliktir çünkü denetim çağrısı yapılana DemoNativeDynamicWrapper.IsInterfaceImplemented()kadar ertelenebilir.

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

Şimdi dinamik olarak desteklenecek arabirimlerden DemoNativeDynamicWrapper birine bakalım. Aşağıdaki kod, IDemoStoreType özelliğinin kullanılmasını sağlar.

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

Bu örnekte dikkat edilmesi gereken iki önemli nokta vardır:

  1. DynamicInterfaceCastableImplementationAttribute özniteliği. Bu öznitelik, bir IDynamicInterfaceCastable yönteminden döndürülen herhangi bir türde gereklidir. IL kırpmayı kolaylaştırma avantajına sahiptir ve bu da AOT senaryolarının daha güvenilir olduğu anlamına gelir.
  2. DemoNativeDynamicWrapper için dönüştürme. Bu, IDynamicInterfaceCastable'nin dinamik doğasının bir parçasıdır. IDynamicInterfaceCastable.GetInterfaceImplementation()'dan döndürülen tür, IDynamicInterfaceCastable türünü uygulayan türü "paketlemek" için kullanılır. Buradaki esas, işaretçi this göründüğü gibi değil çünkü bir durumdan DemoNativeDynamicWrapperIDemoStoreTypeNativeWrapper izin veriyoruz.

Çağrıları COM örneğine iletme

Hangi Yerel Nesne Sarmalayıcının kullanıldığına bakılmaksızın, COM örneğinde işlevleri çağırabilmeniz gerekir. IDemoStoreTypeNativeWrapper.StoreString()'nin uygulanması, C# işlev işaretçilerini unmanaged kullanmanın bir örneği olarak işlev görebilir.

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

Şimdi COM örneğinin dereference işlemini yaparak vtable uygulamasına erişmeyi inceleyelim. COM ABI, bir nesnenin ilk işaretçisinin türün vtable'ı olduğunu ve buradan istenen yuvaya erişilebileceğini tanımlar. COM nesnesinin adresinin olduğunu 0x10000varsayalım. İşaretçi boyutundaki ilk değer, bu örnekteki 0x20000vtable'ın adresi olmalıdır. Vtable'a ulaştığınızda, StoreString() uygulamasına erişmek için dördüncü yuvayı (sıfır tabanlı dizinde dizin 3) ararsınız.

COM instance
0x10000  0x20000

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

İşlev işaretçisinin olması, nesne örneğini ilk parametre olarak geçirerek bu nesnedeki üye işlevine göndermenizi sağlar. Bu desen, Yönetilen Nesne Sarmalayıcı uygulamasının işlev tanımlarına göre tanıdık görünmelidir.

CreateObject() Yöntem uygulandıktan sonra, ComWrappers alt sınıf hem IDemoGetTypehem de IDemoStoreType uygulayan COM örnekleri için Yerel Nesne Sarmalayıcıları üretebilecektir.

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

4. Adım – Yerel Nesne Sarmalayıcı yaşam süresi ayrıntılarını yönetme

ComputeVtables() ve CreateObject() uygulamaları bazı sarmalayıcı ömrü ayrıntılarını ele alır, ancak dikkat edilmesi gereken başka noktalar da vardır. Bu kısa bir adım olsa da, tasarımın karmaşıklığını ComWrappers önemli ölçüde artırabilir.

Yönetilen Nesne Sarmalayıcı'nın AddRef() ve Release() yöntemlerine yapılan çağrılarla denetlendiği durumlardan farklı olarak, Yerel Nesne Sarmalayıcının ömrü, GC tarafından belirsiz bir şekilde yönetilir. Buradaki soru, Yerel Nesne Sarmalayıcısı'nın, COM örneğini temsil eden Release() üzerinde IntPtr'yı ne zaman çağırdığıdır. İki genel kategori vardır:

  1. Yerel Nesne Sarmalayıcı'nın Sonlandırıcısı, COM örneğinin Release() yöntemini çağırmaktan sorumludur. Bu yöntemi çağırmanın güvenli olduğu tek zaman budur. Bu noktada, .NET çalışma zamanında Yerel Nesne Sarmalayıcı'ya başka bir referansın kalmadığı GC tarafından doğru bir şekilde belirlendiği aşikardır. COM Dairelerini düzgün bir şekilde destekliyorsanız burada karmaşıklık olabilir; daha fazla bilgi için Dikkat edilmesi gereken ek noktalar bölümüne bakın.

  2. Yerel Nesne Sarmalayıcı IDisposable uygular ve Release() çağırır Dispose() içinde.

Uyarı

IDisposable paterni yalnızca CreateObject() çağrısı sırasında CreateObjectFlags.UniqueInstance bayrağı geçirildiyse desteklenmelidir. Bu gereksinime uyulmazsa, Yerel Nesne Sarmalayıcıları imha edildikten sonra yeniden kullanılabilir.

ComWrappers Alt sınıfı kullanma

Artık test edilebilen bir ComWrappers alt sınıfınız var. Yönetilen Nesne Sarmalayıcı'yı kullanarak IDemoGetType ve IDemoStoreType'i uygulayan bir COM örneği döndüren bir yerel kitaplık oluşturmaktan kaçınırsınız ve bunu bir COM örneği gibi ele almalısınız. Bunu COM olarak geçirebilmek için bu mümkün olmalıdır.

Önce bir Yönetilen Nesne Sarmalayıcı oluşturalım. Örneğin DemoImpl örneğini oluşturun ve geçerli dize durumunu görüntüleyin.

var demo = new DemoImpl();

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

Artık bir DemoComWrappers örneği ve bir Yönetilen Nesne Sarmalayıcısı oluşturabilir ve bunları bir COM ortamına geçirebilirsiniz.

var cw = new DemoComWrappers();

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

Yönetilen Nesne Sarmalayıcı'yı bir COM ortamına geçirmek yerine, bu COM örneğini yeni almış gibi yapın, böylece bunun yerine yerel nesne sarmalayıcı oluşturursunuz.

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

Yerel Nesne Sarmalayıcı ile bunu istenen arabirimlerden birine dönüştürebilmeniz ve normal yönetilen nesne olarak kullanabilmeniz gerekir. Siz DemoImpl örneğini inceleyebilir ve bu örnek üzerinde işlemlerin yönetilen Nesne Sarmalayıcısı'nı sarmalayan Yerel Nesne Sarmalayıcısı üzerindeki etkilerini gözlemleyebilirsiniz.

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

Alt sınıfınız ComWrapper'ü destekleyecek şekilde CreateObjectFlags.UniqueInstance tasarlandığı için GC'nin gerçekleşmesini beklemek yerine Yerel Nesne Sarmalayıcı'yı hemen temizleyebilirsiniz.

(rcw as IDisposable)?.Dispose();

COM Aktivasyon ile ComWrappers

COM nesnelerinin oluşturulması genellikle bu belgenin kapsamı dışında karmaşık bir senaryo olan COM Etkinleştirmesi aracılığıyla gerçekleştirilir. API'yi, COM Etkinleştirmesi için kullanılan CoCreateInstance() tanıtırız ve ComWrappers ile nasıl kullanılabileceğini gösteririz, böylece izlenecek kavramsal bir desen sağlanır.

Uygulamanızda aşağıdaki C# kodunun olduğunu varsayalım. Aşağıdaki örnek, bir COM sınıfını aktif hale getirmek ve yerleşik COM birlikte çalışma sistemiyle COM örneğini uygun arabirime aktarmak için CoCreateInstance() kullanır. Dikkat edin, typeof(I).GUID kullanımı bir onay ile sınırlıdır ve kodun AOT dostu olup olmadığı durumunda etkilenebilen bir yansıma kullanımı örneğidir.

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

Yukarıdakini ComWrappers kullanıma dönüştürmek, MarshalAs(UnmanagedType.Interface) öğesini P/Invoke'dan kaldırmayı ve sıralamayı el ile gerçekleştirmeyi içerir.

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

Yerel Nesne Sarmalayıcı için sınıf oluşturucusunda etkinleştirme mantığını ekleyerek, fabrika tarzı işlevleri ActivateClass<I> gibi soyutlamak da mümkündür. Oluşturucu, yeni yapılan yönetilen nesneyi etkinleştirilmiş COM örneğiyle ilişkilendirmek için API'yi kullanabilir ComWrappers.GetOrRegisterObjectForComInstance() .

Dikkat edilmesi gereken ek noktalar

Yerel AOT – JIT derlemesi önlendiğinden, önceden (AOT) derleme başlangıç maliyetini iyileştirir. Bazı platformlarda JIT derleme gereksinimini ortadan kaldırmak da genellikle gereklidir. AOT'nin desteklenmesi API'nin ComWrappers bir hedefiydi, ancak herhangi bir sarmalayıcı uygulaması, yansıma kullanımı gibi AOT'nin bozulmasına neden olabilecek durumları yanlışlıkla yaratmamaya dikkat etmelidir. Type.GUID özelliği, yansımanın kullanıldığı ancak belirgin olmayan bir şekilde kullanıldığı bir örnektir. Type.GUID özelliği, türün özniteliklerini incelemek için yansıma kullanır; ardından, değerini oluşturmak amacıyla türün adı ve içeren derlemeyi potansiyel olarak inceleyebilir.

Kaynak oluşturma : COM birlikte çalışma ve uygulama ComWrappers için gereken kodun çoğu büyük olasılıkla bazı araçlar tarafından otomatik olarak oluşturulabilir. Tür Kitaplığı (TLB), IDL veya Birincil Birlikte Çalışma Derlemesi (PIA) gibi uygun COM tanımları göz önünde bulundurularak her iki sarmalayıcı türü için de kaynak oluşturulabilir.

Genel kayıtComWrappers API, COM birlikte çalışmasının yeni bir aşaması olarak tasarlandığından, mevcut sistemle kısmen tümleştirmenin bir yolunun olması gerekiyordu. ComWrappers API'de çeşitli destek için küresel bir örneğin kaydedilmesine izin tanıyan, küresel etkiye sahip statik yöntemler vardır. Bu yöntemler, yerleşik COM birlikte çalışma sistemine benzer şekilde her durumda kapsamlı COM birlikte çalışma desteği sağlamayı bekleyen örnekler için ComWrappers tasarlanmıştır.

Başvuru İzleyicisi desteği – Bu destek WinRT senaryoları için birincil olarak kullanılır ve gelişmiş bir senaryodur. Çoğu ComWrapper uygulaması için, ya bir CreateComInterfaceFlags.TrackerSupport ya da CreateObjectFlags.TrackerObject bayrağının bir NotSupportedException oluşturması gerekir. Bu desteği bir Windows platformunda veya hatta Windows dışı bir platformda etkinleştirmek istiyorsanız , C#/WinRT araç zincirine başvurmanız kesinlikle önerilir.

Daha önce ele alınan yaşam süresi, tür sistemi ve işlevsel özelliklerin yanı sıra, COM ile uyumlu bir uygulama ComWrappers için dikkat edilmesi gereken ek noktalar vardır. Windows platformunda kullanılacak herhangi bir uygulama için aşağıdaki noktalar vardır:

  • Daireler – COM'un iş parçacığı oluşturma organizasyon yapısı "Apartmanlar" olarak adlandırılır ve kararlı operasyonlar için uyulması gereken katı kurallara sahiptir. Bu öğreticide daireye duyarlı Yerel Nesne Sarmalayıcıları uygulanmamaktadır, ancak üretime hazır tüm uygulamaların daireye duyarlı olması gerekir. Bunu başarmak için Windows 8'de kullanıma sunulan API'yi kullanmanızı RoGetAgileReference öneririz. Windows 8 öncesi sürümler için Genel Arabirim Tablosu'nu göz önünde bulundurun.

  • Güvenlik – COM, sınıf etkinleştirme ve yakın izin için zengin bir güvenlik modeli sağlar.