File: System\Private\Windows\Ole\DataObjectProxy.cs
Web Access
Project: src\src\System.Private.Windows.Core\tests\System.Private.Windows.Core.Tests\System.Private.Windows.Core.Tests.csproj (System.Private.Windows.Core.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
using Lifetime = Windows.Win32.System.Com.Lifetime<Windows.Win32.System.Com.IDataObject.Vtbl, System.Private.Windows.Ole.DataObjectProxy>;
 
namespace System.Private.Windows.Ole;
 
/// <summary>
///  Emulates an OLE proxy for unit testing purposes.
/// </summary>
internal unsafe class DataObjectProxy : IDataObject.Interface, IDisposable
{
    // Agile for ensured cleanup
    private readonly AgileComPointer<IDataObject> _agileOriginal;
    private readonly IDataObject* _original;
    private readonly AgileComPointer<IDataObject> _agileProxy;
    public IDataObject* Proxy { get; }
 
    public DataObjectProxy(IDataObject* original)
    {
        // Don't track disposal, we depend on finalization for testing.
 
        _original = original;
        _agileOriginal = new(
#if DEBUG
            original, takeOwnership: true, trackDisposal: false
#else
            original, takeOwnership: true
#endif
            );
 
        Proxy = CCW.Create(this);
 
        _agileProxy = new(
#if DEBUG
            Proxy, takeOwnership: true, trackDisposal: false
#else
            Proxy, takeOwnership: true
#endif
            );
    }
 
    public HRESULT GetData(FORMATETC* pformatetcIn, STGMEDIUM* pmedium) => _original->GetData(pformatetcIn, pmedium);
    public HRESULT GetDataHere(FORMATETC* pformatetc, STGMEDIUM* pmedium) => _original->GetDataHere(pformatetc, pmedium);
    public HRESULT QueryGetData(FORMATETC* pformatetc) => _original->QueryGetData(pformatetc);
    public HRESULT GetCanonicalFormatEtc(FORMATETC* pformatectIn, FORMATETC* pformatetcOut) => _original->GetCanonicalFormatEtc(pformatectIn, pformatetcOut);
    public HRESULT SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, BOOL fRelease) => _original->SetData(pformatetc, pmedium, fRelease);
    public HRESULT EnumFormatEtc(uint dwDirection, IEnumFORMATETC** ppenumFormatEtc) => _original->EnumFormatEtc(dwDirection, ppenumFormatEtc);
    public HRESULT DAdvise(FORMATETC* pformatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) => _original->DAdvise(pformatetc, advf, pAdvSink, pdwConnection);
    public HRESULT DUnadvise(uint dwConnection) => _original->DUnadvise(dwConnection);
    public HRESULT EnumDAdvise(IEnumSTATDATA** ppenumAdvise) => _original->EnumDAdvise(ppenumAdvise);
 
    public void Dispose()
    {
        _agileOriginal.Dispose();
        _agileProxy.Dispose();
    }
 
    internal static class CCW
    {
        private static readonly IDataObject.Vtbl* s_vtable = AllocateVTable();
 
        private static unsafe IDataObject.Vtbl* AllocateVTable()
        {
            // Allocate and create a singular VTable for this type projection.
            IDataObject.Vtbl* vtable = (IDataObject.Vtbl*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(CCW), sizeof(IDataObject.Vtbl));
 
            // IUnknown
            vtable->QueryInterface_1 = &QueryInterface;
            vtable->AddRef_2 = &AddRef;
            vtable->Release_3 = &Release;
            vtable->GetData_4 = &GetData;
            vtable->GetDataHere_5 = &GetDataHere;
            vtable->QueryGetData_6 = &QueryGetData;
            vtable->GetCanonicalFormatEtc_7 = &GetCanonicalFormatEtc;
            vtable->SetData_8 = &SetData;
            vtable->EnumFormatEtc_9 = &EnumFormatEtc;
            vtable->DAdvise_10 = &DAdvise;
            vtable->DUnadvise_11 = &DUnadvise;
            vtable->EnumDAdvise_12 = &EnumDAdvise;
            return vtable;
        }
 
        /// <summary>
        ///  Creates a manual COM Callable Wrapper for the given <paramref name="object"/>.
        /// </summary>
        public static unsafe IDataObject* Create(DataObjectProxy @object) =>
            (IDataObject*)Lifetime.Allocate(@object, s_vtable);
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT QueryInterface(IDataObject* @this, Guid* iid, void** ppObject)
        {
            if (iid is null || ppObject is null)
            {
                return HRESULT.E_POINTER;
            }
 
            if (iid->Equals(IDataObject.IID_Guid) || iid->Equals(IUnknown.IID_Guid))
            {
                *ppObject = @this;
                Lifetime.AddRef(@this);
                return HRESULT.S_OK;
            }
 
            *ppObject = null;
            DataObjectProxy? proxy = Lifetime.GetObject(@this);
            if (proxy is null)
            {
                return HRESULT.E_NOINTERFACE;
            }
 
            // Unwrap our "proxy" object by calling the the original object. This should roughly match the
            // OLE proxy behavior which returns it's own pointer for the IID_IUnknown and IID_IDataObject interfaces.
            return proxy._original->QueryInterface(iid, ppObject);
        }
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe uint AddRef(IDataObject* @this) => Lifetime.AddRef(@this);
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe uint Release(IDataObject* @this) => Lifetime.Release(@this);
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT GetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) =>
            Lifetime.GetObject(@this)?.GetData(pFormatetc, pMedium) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT GetDataHere(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium) =>
            Lifetime.GetObject(@this)?.GetDataHere(pFormatetc, pMedium) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT QueryGetData(IDataObject* @this, FORMATETC* pFormatetc) =>
            Lifetime.GetObject(@this)?.QueryGetData(pFormatetc) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT GetCanonicalFormatEtc(IDataObject* @this, FORMATETC* pFormatetcIn, FORMATETC* pFormatetcOut) =>
            Lifetime.GetObject(@this)?.GetCanonicalFormatEtc(pFormatetcIn, pFormatetcOut) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT SetData(IDataObject* @this, FORMATETC* pFormatetc, STGMEDIUM* pMedium, BOOL fRelease) =>
            Lifetime.GetObject(@this)?.SetData(pFormatetc, pMedium, fRelease) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT EnumFormatEtc(IDataObject* @this, uint dwDirection, IEnumFORMATETC** ppEnumFormatEtc) =>
            Lifetime.GetObject(@this)?.EnumFormatEtc(dwDirection, ppEnumFormatEtc) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT DAdvise(IDataObject* @this, FORMATETC* pFormatetc, uint advf, IAdviseSink* pAdvSink, uint* pdwConnection) =>
            Lifetime.GetObject(@this)?.DAdvise(pFormatetc, advf, pAdvSink, pdwConnection) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT DUnadvise(IDataObject* @this, uint dwConnection) =>
            Lifetime.GetObject(@this)?.DUnadvise(dwConnection) ?? HRESULT.COR_E_OBJECTDISPOSED;
 
        [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
        private static unsafe HRESULT EnumDAdvise(IDataObject* @this, IEnumSTATDATA** ppEnumAdvise) =>
            Lifetime.GetObject(@this)?.EnumDAdvise(ppEnumAdvise) ?? HRESULT.COR_E_OBJECTDISPOSED;
    }
}