C# Native AoT - How to get pointer to Managed class to pass as parameter to Com Object
See the question and my original answer on StackOverflowCsWin32 has a number of issues and shortcomings (not always their fault), especially with AOT publishing, for example: How can I implement COM interface imported with cswin32 without Marshaling for AOT? or CsWin32 is effectively unusable with trim/AOT
So you must redeclare the INetworkListManagerEvents
interface as the generated one is not marked with the needed [GeneratedComClass]
attribute. At lest you can use Cs/Win32 as an example you can copy.
Here is some sample code that should work, <rant>
it's unfortunately an inconsistent mixture of structs, interfaces, raw pointers, IntPtr, etc </rant>
:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Windows.Win32;
using Windows.Win32.Networking.NetworkListManager;
using Windows.Win32.System.Com;
namespace ConsoleApp;
internal class Program
{
unsafe static void Main()
{
// get the IConnectionPoint for INetworkListManagerEvents
PInvoke.CoCreateInstance<INetworkListManager>(typeof(NetworkListManager).GUID, null, CLSCTX.CLSCTX_ALL, out var mgr).ThrowOnFailure();
mgr->QueryInterface<IConnectionPointContainer>(out var container).ThrowOnFailure();
IConnectionPoint* cp;
container->FindConnectionPoint(typeof(INetworkListManagerEvents).GUID, &cp);
// Create an instance of the event handler
var events = new Events();
// create an IUnknown pointer for the event handler
var wrappers = new StrategyBasedComWrappers();
var unk = wrappers.GetOrCreateComInterfaceForObject(events, CreateComInterfaceFlags.None);
cp->Advise((IUnknown*)unk, out var cookie);
Console.WriteLine("Waiting... press ENTER to stop.");
Console.ReadLine();
cp->Unadvise(cookie);
cp->Release();
}
}
[GeneratedComClass]
public partial class Events : INetworkListManagerEvents
{
void INetworkListManagerEvents.ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
{
Console.WriteLine($"Connectivity changed: {newConnectivity}");
}
}
public enum NLM_CONNECTIVITY
{
NLM_CONNECTIVITY_DISCONNECTED = 0,
NLM_CONNECTIVITY_IPV4_NOTRAFFIC = 1,
NLM_CONNECTIVITY_IPV6_NOTRAFFIC = 2,
NLM_CONNECTIVITY_IPV4_SUBNET = 16,
NLM_CONNECTIVITY_IPV4_LOCALNETWORK = 32,
NLM_CONNECTIVITY_IPV4_INTERNET = 64,
NLM_CONNECTIVITY_IPV6_SUBNET = 256,
NLM_CONNECTIVITY_IPV6_LOCALNETWORK = 512,
NLM_CONNECTIVITY_IPV6_INTERNET = 1024,
}
[GeneratedComInterface, Guid("dcb00001-570f-4a9b-8d69-199fdba5723b")]
public partial interface INetworkListManagerEvents
{
void ConnectivityChanged(NLM_CONNECTIVITY newConnectivity);
}
Note you cannot even reuse the NLM_CONNECTIVITY
enum because the ComWrapper Source Generator doesn't want to use it either, you must redeclare it too.
NativeMethods.txt:
CoCreateInstance
IConnectionPointContainer
INetworkListManager
NetworkListManager
NativeMethods.json:
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false
}
.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>