Skip to content

Instantly share code, notes, and snippets.

@cnbluefire
Created August 18, 2025 11:12
Show Gist options
  • Select an option

  • Save cnbluefire/7438e4c062f34b89e6855ad57605219a to your computer and use it in GitHub Desktop.

Select an option

Save cnbluefire/7438e4c062f34b89e6855ad57605219a to your computer and use it in GitHub Desktop.
CsWin32 ComWrappers Test
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));
}
}
@xiaoyaocode163

Copy link
Copy Markdown

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment