Created
August 18, 2025 11:12
-
-
Save cnbluefire/7438e4c062f34b89e6855ad57605219a to your computer and use it in GitHub Desktop.
CsWin32 ComWrappers Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Runtime.InteropServices; | |
| using System; | |
| using Windows.Win32.Foundation; | |
| using Windows.Win32.System.Com; | |
| using System.Runtime.CompilerServices; | |
| using System.IO; | |
| using Windows.Win32; | |
| using System.Collections; | |
| using CsWin32ComWrappersTest; | |
| using System.Security.Cryptography; | |
| using System.Diagnostics; | |
| namespace CsWin32ComWrappersTest | |
| { | |
| internal class Program | |
| { | |
| private static readonly CustomComWrappers ComWrappers = new(); | |
| unsafe static void Main(string[] args) | |
| { | |
| var stream = new MemoryStream(100); | |
| stream.Write([1, 2, 3, 4, 5, 6, 7, 8, 9]); | |
| var wrapper = new ManagedIStream(stream); | |
| var punk = (IUnknown*)ComWrappers.GetOrCreateComInterfaceForObject( | |
| wrapper, | |
| CreateComInterfaceFlags.None); | |
| // GetOrCreateComInterfaceForObject always returns an IUnknown pointer, | |
| // so we need to QueryInterface to get the IStream interface. | |
| punk->QueryInterface<IStream>(out var pStream).ThrowOnFailure(); | |
| ulong pos = 0; | |
| pStream->Seek(0, SeekOrigin.Begin, &pos).ThrowOnFailure(); | |
| Span<byte> buffer = stackalloc byte[9]; | |
| uint bytesRead = 0; | |
| pStream->Read( | |
| Unsafe.AsPointer(ref MemoryMarshal.AsRef<byte>(buffer)), | |
| (uint)buffer.Length, | |
| &bytesRead).ThrowOnFailure(); | |
| var span = buffer[..(int)bytesRead]; | |
| Console.WriteLine(string.Join(", ", span.ToArray())); | |
| } | |
| } | |
| public class CustomComWrappers : ComWrappers | |
| { | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/WinFormsComWrappers.cs#L25 | |
| protected override unsafe ComWrappers.ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) | |
| { | |
| if (obj is not IManagedWrapper vtables) | |
| { | |
| Debug.Fail("object does not implement IManagedWrapper"); | |
| count = 0; | |
| return null; | |
| } | |
| // Bind the vtables for the interfaces implemented by the object. | |
| ComInterfaceTable table = vtables.GetComInterfaceTable(); | |
| count = table.Count; | |
| return table.Entries; | |
| } | |
| protected override object? CreateObject(nint externalComObject, CreateObjectFlags flags) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| protected override void ReleaseObjects(IEnumerable objects) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| } | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IManagedWrapper.cs | |
| internal unsafe interface IManagedWrapper | |
| { | |
| ComInterfaceTable GetComInterfaceTable(); | |
| } | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IManagedWrapper.cs | |
| internal unsafe interface IManagedWrapper<TComInterface> : IManagedWrapper | |
| where TComInterface : unmanaged, IVTable, IComIID | |
| { | |
| // Allocates a ComInterfaceTable include VTable for the given interface type. | |
| private static ComInterfaceTable InterfaceTable { get; set; } = ComInterfaceTable.Create<TComInterface>(); | |
| ComInterfaceTable IManagedWrapper.GetComInterfaceTable() => InterfaceTable; | |
| } | |
| // https://github.com/dotnet/wpf/blob/4aff730d0aed8d28668f591ca85dd6289b5f23f1/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/IO/Packaging/ManagedIStream.cs | |
| public class ManagedIStream : IManagedWrapper<IStream>, IStream.Interface | |
| { | |
| private const int E_NOTIMPL = unchecked((int)0x80004001); | |
| private const int COR_E_IO = unchecked((int)0x80131620); | |
| private Stream _ioStream; | |
| internal ManagedIStream(Stream ioStream) | |
| { | |
| ArgumentNullException.ThrowIfNull(ioStream); | |
| _ioStream = ioStream; | |
| } | |
| unsafe HRESULT IStream.Interface.Read(void* pv, uint cb, uint* pcbRead) | |
| { | |
| var bytesRead = _ioStream.Read(MemoryMarshal.CreateSpan(ref Unsafe.AsRef<byte>(pv), unchecked((int)cb))); | |
| if (pcbRead != null) | |
| { | |
| Unsafe.AsRef<uint>(pcbRead) = unchecked((uint)bytesRead); | |
| } | |
| return (HRESULT)0; | |
| } | |
| unsafe HRESULT IStream.Interface.Write(void* pv, uint cb, uint* pcbWritten) | |
| { | |
| var pos = _ioStream.Position; | |
| _ioStream.Write(MemoryMarshal.CreateSpan(ref Unsafe.AsRef<byte>(pv), unchecked((int)cb))); | |
| if (pcbWritten != null) | |
| { | |
| Unsafe.AsRef<uint>(pcbWritten) = unchecked((uint)(_ioStream.Position - pos)); | |
| } | |
| return (HRESULT)0; | |
| } | |
| unsafe HRESULT IStream.Interface.Seek(long dlibMove, SeekOrigin dwOrigin, ulong* plibNewPosition) | |
| { | |
| long position = _ioStream.Seek(dlibMove, dwOrigin); | |
| // Dereference newPositionPtr and assign to the pointed location. | |
| if (plibNewPosition != null) | |
| { | |
| Unsafe.AsRef<ulong>(plibNewPosition) = unchecked((ulong)position); | |
| } | |
| return (HRESULT)0; | |
| } | |
| HRESULT IStream.Interface.SetSize(ulong libNewSize) | |
| { | |
| _ioStream.SetLength(unchecked((long)libNewSize)); | |
| return (HRESULT)0; | |
| } | |
| unsafe HRESULT IStream.Interface.Stat(STATSTG* pstatstg, uint grfStatFlag) | |
| { | |
| ref STATSTG statstg = ref Unsafe.AsRef<STATSTG>(pstatstg); | |
| statstg = new STATSTG | |
| { | |
| type = 2, // STGTY_STREAM | |
| cbSize = unchecked((ulong)_ioStream.Length), | |
| // Return access information in grfMode. | |
| grfMode = 0 // default value for each flag will be false | |
| }; | |
| if (_ioStream.CanRead && _ioStream.CanWrite) | |
| { | |
| statstg.grfMode |= STGM.STGM_READWRITE; | |
| } | |
| else if (_ioStream.CanRead) | |
| { | |
| statstg.grfMode |= STGM.STGM_READ; | |
| } | |
| else if (_ioStream.CanWrite) | |
| { | |
| statstg.grfMode |= STGM.STGM_WRITE; | |
| } | |
| else | |
| { | |
| return (HRESULT)COR_E_IO; | |
| } | |
| return (HRESULT)0; | |
| } | |
| #region Unimplemented methods | |
| unsafe HRESULT IStream.Interface.CopyTo(IStream* pstm, ulong cb, ulong* pcbRead, ulong* pcbWritten) | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| HRESULT IStream.Interface.Commit(uint grfCommitFlags) | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| HRESULT IStream.Interface.Revert() | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| HRESULT IStream.Interface.LockRegion(ulong libOffset, ulong cb, uint dwLockType) | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| HRESULT IStream.Interface.UnlockRegion(ulong libOffset, ulong cb, uint dwLockType) | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| unsafe HRESULT IStream.Interface.Clone(IStream** ppstm) | |
| { | |
| return (HRESULT)E_NOTIMPL; | |
| } | |
| #endregion Unimplemented methods | |
| unsafe HRESULT ISequentialStream.Interface.Read(void* pv, uint cb, uint* pcbRead) | |
| { | |
| return ((IStream.Interface)this).Read(pv, cb, pcbRead); | |
| } | |
| unsafe HRESULT ISequentialStream.Interface.Write(void* pv, uint cb, uint* pcbWritten) | |
| { | |
| return ((IStream.Interface)this).Write(pv, cb, pcbWritten); | |
| } | |
| } | |
| } | |
| namespace Windows.Win32 | |
| { | |
| unsafe partial class ComHelpers | |
| { | |
| // Populate vtable using IUnknown method implemented by ComWrappers | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/WinFormsComWrappers.cs#L17 | |
| static partial void PopulateIUnknownImpl<TComInterface>(IUnknown.Vtbl* vtable) where TComInterface : unmanaged | |
| { | |
| CustomComWrappers.GetIUnknownImpl(out nint fpQueryInterface, out nint fpAddRef, out nint fpRelease); | |
| vtable->QueryInterface_1 = (delegate* unmanaged[Stdcall]<IUnknown*, Guid*, void*, HRESULT>)fpQueryInterface; | |
| vtable->AddRef_2 = (delegate* unmanaged[Stdcall]<IUnknown*, uint>)fpAddRef; | |
| vtable->Release_3 = (delegate* unmanaged[Stdcall]<IUnknown*, uint>)fpRelease; | |
| } | |
| } | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/ComInterfaceTable.cs#L9 | |
| internal readonly unsafe struct ComInterfaceTable | |
| { | |
| public ComWrappers.ComInterfaceEntry* Entries { get; init; } | |
| public int Count { get; init; } | |
| /// <summary> | |
| /// Create an interface table for the given interface. | |
| /// </summary> | |
| public static ComInterfaceTable Create<TComInterface>() | |
| where TComInterface : unmanaged, IComIID, IVTable | |
| { | |
| Span<ComWrappers.ComInterfaceEntry> entries = AllocateEntries<TComInterface>(1); | |
| entries[0] = GetEntry<TComInterface>(); | |
| return new() | |
| { | |
| Entries = (ComWrappers.ComInterfaceEntry*)Unsafe.AsPointer(ref entries[0]), | |
| Count = entries.Length | |
| }; | |
| } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| private static Span<ComWrappers.ComInterfaceEntry> AllocateEntries<T>(int count) | |
| { | |
| Span<ComWrappers.ComInterfaceEntry> entries = new( | |
| (ComWrappers.ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(T), sizeof(ComWrappers.ComInterfaceEntry) * (count + 1)), | |
| count); | |
| return entries; | |
| } | |
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |
| private static ComWrappers.ComInterfaceEntry GetEntry<TComInterface>() where TComInterface : unmanaged, IComIID, IVTable | |
| => new() | |
| { | |
| Vtable = (nint)TComInterface.VTable, | |
| IID = *GetIID<TComInterface>() | |
| }; | |
| // https://github.com/dotnet/winforms/blob/f020fe71f615cb51aa61970f5aaa757bb981499e/src/System.Private.Windows.Core/src/Windows/Win32/System/Com/IID.cs#L32 | |
| private static Guid* GetIID<T>() where T : unmanaged, IComIID | |
| => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid)); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
what dll or class(*.cs) need to get a full project ,i want to make a com dll for vb6,vba to use.
do you have a new project For aot com dll?
class1{
public void Test()
{
Win32API.Msgbox("Test");
}
public int Calculate(int x, int y) => x * y + 100;
}
how to set it to dll,and call from vba,vb6?