C
C#•9mo ago
jborean

✅ COM + NativeAOT Help

I'm fairly new to COM and I'm trying to implement an RDP Dynamic Virtual Channel client dll which uses the IWTSPlugin COM interface https://learn.microsoft.com/en-us/windows/win32/api/tsvirtualchannels/nn-tsvirtualchannels-iwtsplugin. Can anyone point out what I might be doing wrong here https://gist.github.com/jborean93/394592c89fc8bfd54990d814a29b924f as this just crashes my process and the WTSPlugin-log.txt is never created. I'm not 100% confident on how I'm setting the COM pointer on the VirtualChannelGetInstance implementation but I know for sure that mstsc is calling that particular method it as the logs are showing that it is called and the process crashes because I am doing something dumb. I do have a few other questions that hopefully someone might be able to clarify + Do I need to save the WTSPlugin and StrategyBasedComWrappers instance or can they be discarded once I've passed the pointer to the unmanaged instance + What is the correct way to define another COM interface (pointer to the interface type in COM land) as an argument + Are there any recommendations for PreserveSig or not having it
Gist
IWTSPlugin.cs
GitHub Gist: instantly share code, notes, and snippets.
21 Replies
Unknown User
Unknown User•9mo ago
Message Not Public
Sign In & Join Server To View
reflectronic
reflectronic•9mo ago
i would prefer using ComInterfaceMarshaller<IWTSPlugin>.ConvertToUnmanaged over instantiating the StrategyBasedComWrappers yourself
jborean
jboreanOP•9mo ago
If it helps I'm compiling it for win-x64 with
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<InvariantGlobalization>true</InvariantGlobalization>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<InvariantGlobalization>true</InvariantGlobalization>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
</PropertyGroup>

</Project>
reflectronic
reflectronic•9mo ago
that doesn't change much, it's just a little cleaner and more efficient i would also skip the PreserveSig, it's just more work
jborean
jboreanOP•9mo ago
I'm assuming if I don't have PreserveSig it's just a void return here as none of those functions have an out return value
reflectronic
reflectronic•9mo ago
yea
jborean
jboreanOP•9mo ago
Thanks for the hint on ComInterfaceMarshaller, I'll start using that
reflectronic
reflectronic•9mo ago
where exactly do your logs say you get to because at a first glance everything seems OK
jborean
jboreanOP•9mo ago
only the VirtualChannelGetInstance is created the WTSPlugin-log.txt is not created which tells me it's failing to call Initialize()
reflectronic
reflectronic•9mo ago
how do you know it's crashing
jborean
jboreanOP•9mo ago
the whole mstsc process crashes and I have an WER event log I think I've got it working
[UnmanagedCallersOnly(EntryPoint = "VirtualChannelGetInstance", CallConvs = [typeof(CallConvStdcall)])]
private unsafe static int VirtualChannelGetInstance(
Guid* refiid,
int* pNumObjs,
void** ppObjArray
)
{
using StreamWriter writer = new(@"C:\temp\ProcessVirtualChannel\VirtualChannelGetInstance-log.txt", true);
string now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - VirtualChannelGetInstance - {*pNumObjs}");

if (*refiid != typeof(IWTSPlugin).GUID)
{
return unchecked((int)0x80004002); // E_NOINTERFACE
}

*pNumObjs = 1;
if (ppObjArray != null)
{
void* unmanagedPtr = ComInterfaceMarshaller<IWTSPlugin>.ConvertToUnmanaged(_plugin);
*ppObjArray = unmanagedPtr;

now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - Created pointer - {*pNumObjs}");
}
else
{
now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - setting pNumObjs");
}

return 0;
}
[UnmanagedCallersOnly(EntryPoint = "VirtualChannelGetInstance", CallConvs = [typeof(CallConvStdcall)])]
private unsafe static int VirtualChannelGetInstance(
Guid* refiid,
int* pNumObjs,
void** ppObjArray
)
{
using StreamWriter writer = new(@"C:\temp\ProcessVirtualChannel\VirtualChannelGetInstance-log.txt", true);
string now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - VirtualChannelGetInstance - {*pNumObjs}");

if (*refiid != typeof(IWTSPlugin).GUID)
{
return unchecked((int)0x80004002); // E_NOINTERFACE
}

*pNumObjs = 1;
if (ppObjArray != null)
{
void* unmanagedPtr = ComInterfaceMarshaller<IWTSPlugin>.ConvertToUnmanaged(_plugin);
*ppObjArray = unmanagedPtr;

now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - Created pointer - {*pNumObjs}");
}
else
{
now = DateTime.Now.ToString("[HH:mm:ss.fff]");
writer.WriteLine($"{now} - setting pNumObjs");
}

return 0;
}
Used ConvertToUnmanaged and played around with the ppObjArray signature
reflectronic
reflectronic•9mo ago
i mean that looks the same, but if it works it works
jborean
jboreanOP•9mo ago
you're telling me 🙂
reflectronic
reflectronic•9mo ago
idk i couldn't tell that anything was wrong
jborean
jboreanOP•9mo ago
I'm assuming I messed up my ppObjArray signature and setting that value somehow but as you said if it works it works Do I need to keep the WTSPlugin instance alive as a static field or can it be dropped after providing the pointer to it?
reflectronic
reflectronic•9mo ago
it can be dropped
jborean
jboreanOP•9mo ago
Thanks for the advice!
Unknown User
Unknown User•9mo ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX•9mo ago
Use the /close command to mark a forum thread as answered
Unknown User
Unknown User•9mo ago
Message Not Public
Sign In & Join Server To View
jborean
jboreanOP•9mo ago
As sorry I wasn’t aware that was a thing here 🙂
Want results from more Discord servers?
Add your server