Skip to content

Add modern COM interop support#173

Merged
Marc-André Moreau (mamoreau-devolutions) merged 5 commits into
masterfrom
com-interop
Jul 23, 2026
Merged

Add modern COM interop support#173
Marc-André Moreau (mamoreau-devolutions) merged 5 commits into
masterfrom
com-interop

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Summary

Adds source-generated COM interop alongside the preserved legacy default package surface.

  • Keeps legacy Interop.MSTSCLib and AxInterop.MSTSCLib available by default for net48 and net8.0-windows consumers.
  • Adds opt-in Generated COM interop with NativeAOT support, proxy helpers, typed factories, and connection-point events.
  • Adds an independent generated WinForms host via MsRdpExGeneratedWinForms=true.
  • Validates package capabilities and native RID payload architecture during packaging.

Fixes #97.

Credit

Many thanks to Tobias Käs (@weltkante) for the original experimental srcgen / srcgen2 implementation that this work builds upon.

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor Author

Copilot review

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown

Copilot review

Addressed one high-confidence issue from review and pushed a fix in ccdff6a (throw InvalidOperationException in CreateInstanceCore instead of returning it).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds opt-in source-generated COM interop and NativeAOT support while retaining the legacy package surface.

Changes:

  • Adds generated COM interfaces, proxies, events, factories, and WinForms hosts.
  • Adds package mode selection and architecture/capability validation.
  • Migrates the sample application and adds compatibility/package tests.

Reviewed changes

Copilot reviewed 62 out of 70 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
README.md Documents interop modes and APIs.
dotnet/MsRdpEx_Test/Variant.cs Adds VARIANT layout tests.
dotnet/MsRdpEx_Test/Utilities.cs Adds reflection test helpers.
dotnet/MsRdpEx_Test/MsRdpEx_Test.csproj Configures interop tests.
dotnet/MsRdpEx_Test/Compatibility.cs Tests legacy API compatibility.
dotnet/MsRdpEx_Test/BinaryString.cs Tests binary-string semantics.
dotnet/MsRdpEx_NativeAotInterop_Test/Program.cs Adds NativeAOT consumer checks.
dotnet/MsRdpEx_NativeAotInterop_Test/MsRdpEx_NativeAotInterop_Test.csproj Configures NativeAOT publishing.
dotnet/MsRdpEx_LegacyInterop_Test/Program.cs Adds legacy consumer fixture.
dotnet/MsRdpEx_LegacyInterop_Test/MsRdpEx_LegacyInterop_Test.csproj Configures legacy package test.
dotnet/MsRdpEx_Gen/Program.cs Adds interop source generator.
dotnet/MsRdpEx_Gen/MsRdpEx_Gen.csproj Configures generator build.
dotnet/MsRdpEx_App/RdpView.cs Adapts generated COM lifetimes.
dotnet/MsRdpEx_App/Program.cs Disables default COM visibility.
dotnet/MsRdpEx_App/MsRdpEx_App.csproj Uses generated interop projects.
dotnet/MsRdpEx_App/MainDlg.cs Migrates calls to compatibility helpers.
dotnet/Interop.MSTSCLib.Generated/Interop.MSTSCLib.Generated.csproj Builds standalone generated interop.
dotnet/Interop.MSTSCLib.Generated.WinForms/Interop.MSTSCLib.Generated.WinForms.csproj Builds independent WinForms host.
dotnet/Interop.MSTSCLib.Generated.WinForms/GeneratedRdpClientHost.cs Implements generated ActiveX hosting.
dotnet/Devolutions.MsRdpEx/MarshalHelpers.cs Adds source-generated UTF-8 marshalling.
dotnet/Devolutions.MsRdpEx/LoadBalanceInfo.cs Removes the legacy load-balance shim.
dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.targets Adds package interop selection.
dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.csproj Multi-targets and packages interop assets.
dotnet/Devolutions.MsRdpEx/Bindings.cs Migrates native COM bindings.
dotnet/CMakeLists.txt Includes new managed projects.
dotnet/AxInterop.MSTSCLib/RdpAxHost.cs Adapts ActiveX hosting for generated COM.
dotnet/AxInterop.MSTSCLib/MSTSCLib/TransportSettings.cs Declares transport interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Shell.cs Declares client-shell interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/SecuredSettings.cs Declares secured-settings interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/RemoteProgram.cs Declares RemoteApp interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/RemoteDesktopClient.cs Declares modern client interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/RedirectionInfo.cs Declares redirection settings.
dotnet/AxInterop.MSTSCLib/MSTSCLib/NonScriptableClient.cs Declares non-scriptable APIs.
dotnet/AxInterop.MSTSCLib/MSTSCLib/ExtendedSettings.cs Declares extended settings.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Drive.cs Declares drive redirection APIs.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Device.cs Declares device redirection APIs.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Debug.cs Declares debug interfaces.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Clipboard.cs Declares clipboard APIs.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Client.cs Declares core RDP interfaces and events.
dotnet/AxInterop.MSTSCLib/MSTSCLib/Camera.cs Declares camera redirection APIs.
dotnet/AxInterop.MSTSCLib/MSTSCLib/AdvancedSettings.cs Declares advanced settings.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/Variant.cs Implements VARIANT marshalling.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/RdpClientFactory.cs Adds typed COM activation.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/ProxyObject.cs Implements generated proxy wrapping.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/ProxyMetadata.cs Maps interfaces to proxies and IIDs.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/IDispatch.cs Declares generated IDispatch support.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/Extensions.cs Adds low-level interop adapters.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/BinaryStringRef.cs Adds borrowed binary BSTR support.
dotnet/AxInterop.MSTSCLib/Interop/NetCore/BinaryString.cs Adds managed binary BSTR support.
dotnet/AxInterop.MSTSCLib/Interop/Extensions.cs Adds compatibility extension methods.
dotnet/AxInterop.MSTSCLib/Interop/Desktop/ProxyObject.cs Adds desktop proxy passthrough.
dotnet/AxInterop.MSTSCLib/Interop/Desktop/Compatibility.cs Adds desktop compatibility attribute.
dotnet/AxInterop.MSTSCLib/Interop/Desktop/BinaryString.cs Adds desktop binary BSTR support.
dotnet/AxInterop.MSTSCLib/Interop/Compatibility.cs Adds classic event sink interfaces.
dotnet/AxInterop.MSTSCLib/Interop/BinaryString.cs Adds classic custom marshaller.
dotnet/AxInterop.MSTSCLib/Generated/Enums.cs Adds generated legacy-compatible enums.
dotnet/AxInterop.MSTSCLib/AxInterop.MSTSCLib.csproj Configures generated WinForms interop.
dotnet/AxInterop.MSTSCLib.Legacy/RdpAxHost.Legacy.cs Preserves legacy ActiveX hosting.
dotnet/AxInterop.MSTSCLib.Legacy/AxInterop.MSTSCLib.Legacy.csproj Builds legacy interop assemblies.
.github/workflows/build-package.yml Validates package consumers and PE payloads.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +27
public static nint ConvertToUnmanaged(BinaryStringRef value)
{
fixed (byte* pointer = value.content)
return BinaryStringInterop.AllocateByteBuffer(pointer, value.content.Length);
Comment on lines +48 to +59
nint result;
if (data.mTextData is not null)
{
fixed (char* pTextData = data.mTextData)
result = BinaryStringInterop.AllocateTextBuffer(pTextData, data.mTextData.Length);
}
else if (!data.mByteData.IsDefault)
{
fixed (byte* pByteData = data.mByteData.AsSpan())
result = BinaryStringInterop.AllocateByteBuffer(pByteData, data.mByteData.Length);
}
else throw new UnreachableException();
Comment on lines +284 to +285
default:
throw new NotSupportedException();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is GetPublisherCertificateChain appearing? I couldn't find it with a quick search. I'd double check this claim before bothering with it, during my tests I didn't run into the exception (or I'd have added the missing cases)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is declared as IMsRdpClientNonScriptable4.GetPublisherCertificateChain in dotnet/AxInterop.MSTSCLib/MSTSCLib/NonScriptableClient.cs and called by the generated redirection adapter. The VARIANT marshaller now round-trips the expected VT_ARRAY | VT_UI1 payload as byte[]; this also supports SetPublisherCertificateChain.

return ProxyObject.Pack(ComInterfaceMarshaller<object>.ConvertToManaged((void*)data.Content1));

case VariantType.ByRefModifier | VariantType.Error: return *(int*)data.Content1;
case VariantType.ByRefModifier | VariantType.Boolean: return *(short*)data.Content1;
Comment on lines +152 to +153
case VariantType.ByRefModifier | VariantType.Currency: return *(long*)data.Content1;
case VariantType.ByRefModifier | VariantType.DateTime: return *(double*)data.Content1;
Comment on lines +29 to +30
<Error Condition="'$(MsRdpExComInterop)' == 'Generated' And !Exists('$(_MsRdpExGeneratedInteropPath)')"
Text="MsRdpExComInterop=Generated requires '$(_MsRdpExGeneratedInteropPath)', but the selected Devolutions.MsRdpEx package does not contain it. Select a package with MsRdpExGeneratedInteropSupported=true or use Legacy." />
Comment thread .github/workflows/build-package.yml Outdated
dotnet test .\dotnet\MsRdpEx_Test\MsRdpEx_Test.csproj -c Release -p:CMakeOutputPath="$BuildDir\Release" -v:minimal
dotnet run --project .\dotnet\MsRdpEx_LegacyInterop_Test\MsRdpEx_LegacyInterop_Test.csproj -c Release -p:MsRdpExTestPackageSource="$PackageSource" -p:MsRdpExPackageVersion="$PackageVersion" --no-launch-profile
dotnet publish .\dotnet\MsRdpEx_NativeAotInterop_Test\MsRdpEx_NativeAotInterop_Test.csproj -c Release -o .\nativeaot-test -p:MsRdpExTestPackageSource="$PackageSource" -p:MsRdpExPackageVersion="$PackageVersion" -v:minimal
& .\nativeaot-test\MsRdpEx_NativeAotInterop_Test.exe
Preserve legacy NuGet interop as the default while adding source-generated COM projections, NativeAOT support, generated WinForms hosting, consumer validation, and compatibility tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Marc-André Moreau (mamoreau-devolutions) pushed a commit that referenced this pull request Jul 24, 2026
…175)

#173 removed this hand-written declaration along with the rest of the
manual interop surface — the generated interop types `LoadBalanceInfo`
as `BinaryString` and no longer needs it. But legacy-interop consumers
on the current package lose their only way to set a byte-exact
`LoadBalanceInfo`.

Restored verbatim from `2026.7.3`, relocated into
`AxInterop.MSTSCLib.Legacy` so it ships in the legacy interop assembly
and `Devolutions.MsRdpEx` stays MSTSCLib-free.

Context: lets RDM bump to the current package (recording-cursor fix,
#174) while staying on the legacy interop, without carrying this
declaration itself. Verified against RDM: builds clean,
`LoadBalanceInfo` path unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Update ActiveX and COM interop

4 participants