From 5481b585453b292edc4509affe428a51e5b6625c Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 14 Nov 2025 17:23:15 +1100 Subject: [PATCH 1/2] Use SafeHandle to hold pointer to native delegates. --- Reinterop~/CustomDelegateGenerator.cs | 43 ++++++++++++++------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Reinterop~/CustomDelegateGenerator.cs b/Reinterop~/CustomDelegateGenerator.cs index 3165f2b4..57e3837c 100644 --- a/Reinterop~/CustomDelegateGenerator.cs +++ b/Reinterop~/CustomDelegateGenerator.cs @@ -72,7 +72,7 @@ private void GenerateDelegate(CppGenerationContext context, GeneratedResult resu """ )); - // A a C# delegate type that wraps a std::function, and arrange for + // A C# delegate type that wraps a std::function, and arrange for // the invoke and dispose to be implemented in C++. CSharpType csType = CSharpType.FromSymbol(context, item.Type); @@ -88,7 +88,7 @@ private void GenerateDelegate(CppGenerationContext context, GeneratedResult resu string disposeCallbackName = $"{csType.GetFullyQualifiedNamespace().Replace(".", "_")}_{item.Type.Name}{genericTypeHash}_DisposeCallback"; var invokeParameters = callbackParameters.Select(p => $"{p.CsType.GetFullyQualifiedName()} {p.Name}"); - var invokeInteropParameters = new[] { "IntPtr callbackFunction" }.Concat(callbackParameters.Select(p => $"{p.CsType.AsInteropTypeParameter().GetFullyQualifiedName()} {p.Name}")); + var invokeInteropParameters = new[] { "ImplementationHandle callbackFunction" }.Concat(callbackParameters.Select(p => $"{p.CsType.AsInteropTypeParameter().GetFullyQualifiedName()} {p.Name}")); var callInvokeInteropParameters = new[] { "_callbackFunction" }.Concat(callbackParameters.Select(p => p.CsType.GetConversionToInteropType(p.Name))); var csReturnType = CSharpType.FromSymbol(context, invokeMethod.ReturnType); @@ -109,31 +109,34 @@ private void GenerateDelegate(CppGenerationContext context, GeneratedResult resu $$""" private class {{csType.Name}}{{genericTypeHash}}NativeFunction : System.IDisposable { - private IntPtr _callbackFunction; - - public {{csType.Name}}{{genericTypeHash}}NativeFunction(IntPtr callbackFunction) + internal class ImplementationHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { - _callbackFunction = callbackFunction; - } + public ImplementationHandle(IntPtr nativeImplementation) : base(true) + { + SetHandle(nativeImplementation); + } - ~{{csType.Name}}{{genericTypeHash}}NativeFunction() - { - Dispose(false); + [System.Runtime.ConstrainedExecution.ReliabilityContract(System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState, System.Runtime.ConstrainedExecution.Cer.Success)] + protected override bool ReleaseHandle() + { + {{disposeCallbackName}}(this.handle); + return true; + } } - - public void Dispose() + + [System.NonSerialized] + private ImplementationHandle _callbackFunction; + + public {{csType.Name}}{{genericTypeHash}}NativeFunction(IntPtr callbackFunction) { - Dispose(true); - GC.SuppressFinalize(this); + _callbackFunction = new ImplementationHandle(callbackFunction); } - private void Dispose(bool disposing) + public void Dispose() { - if (_callbackFunction != IntPtr.Zero) - { - {{disposeCallbackName}}(_callbackFunction); - _callbackFunction = IntPtr.Zero; - } + if (this._callbackFunction != null && !this._callbackFunction.IsInvalid) + this._callbackFunction.Dispose(); + this._callbackFunction = null; } public {{csReturnType.GetFullyQualifiedName()}} Invoke({{string.Join(", ", invokeParameters)}}) From 3261d7c62875675a33d39229c9343877ff922e0f Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Fri, 14 Nov 2025 20:03:26 +1100 Subject: [PATCH 2/2] Catch native exceptions from delegates and turn into managed. --- Reinterop~/CustomDelegateGenerator.cs | 50 ++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/Reinterop~/CustomDelegateGenerator.cs b/Reinterop~/CustomDelegateGenerator.cs index 57e3837c..477924c0 100644 --- a/Reinterop~/CustomDelegateGenerator.cs +++ b/Reinterop~/CustomDelegateGenerator.cs @@ -139,19 +139,27 @@ public void Dispose() this._callbackFunction = null; } - public {{csReturnType.GetFullyQualifiedName()}} Invoke({{string.Join(", ", invokeParameters)}}) + public unsafe {{csReturnType.GetFullyQualifiedName()}} Invoke({{string.Join(", ", invokeParameters)}}) { if (_callbackFunction == null) throw new System.ObjectDisposedException("{{csType.Name}}"); - - {{csResultImplementation}}{{invokeCallbackName}}({{string.Join(", ", callInvokeInteropParameters)}}); - {{csReturnImplementation}}; + + unsafe + { + System.IntPtr reinteropException = System.IntPtr.Zero; + {{csResultImplementation}}{{invokeCallbackName}}({{string.Join(", ", callInvokeInteropParameters)}}, &reinteropException); + if (reinteropException != System.IntPtr.Zero) + { + throw (System.Exception)Reinterop.ObjectHandleUtility.GetObjectAndFreeHandle(reinteropException); + } + {{csReturnImplementation}}; + } } [System.Runtime.InteropServices.DllImport("{{context.NativeLibraryName}}", CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)] private static extern void {{disposeCallbackName}}(IntPtr callbackFunction); [System.Runtime.InteropServices.DllImport("{{context.NativeLibraryName}}", CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)] - private static extern {{csReturnType.AsInteropTypeReturn().GetFullyQualifiedName()}} {{invokeCallbackName}}({{string.Join(", ", invokeInteropParameters)}}); + private static unsafe extern {{csReturnType.AsInteropTypeReturn().GetFullyQualifiedName()}} {{invokeCallbackName}}({{string.Join(", ", invokeInteropParameters)}}, IntPtr* reinteropException); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private unsafe delegate IntPtr {{csBaseName}}Type(IntPtr callbackFunction); @@ -168,12 +176,19 @@ public void Dispose() var interopParameters = new[] { (Name: "pCallbackFunction", CsType: CSharpType.FromSymbol(context, context.Compilation.GetSpecialType(SpecialType.System_IntPtr)), Type: CppType.VoidPointer, InteropType: CppType.VoidPointer) }.Concat(callbackParameters); var callParameters = callbackParameters.Select(p => p.Type.GetConversionFromInteropType(context, p.Name)); + CppType interopReturnType = returnType.AsInteropType(); + string resultImplementation = ""; string returnImplementation = "return;"; + string returnDefault = "return;"; if (invokeMethod.ReturnType.SpecialType != SpecialType.System_Void) { resultImplementation = "auto result = "; returnImplementation = $"return {returnType.GetConversionToInteropType(context, "result")};"; + if (interopReturnType.Flags.HasFlag(CppTypeFlags.Pointer)) + returnDefault = "return nullptr;"; + else + returnDefault = $$"""return {{interopReturnType.GetFullyQualifiedName()}}();"""; } result.CppImplementationInvoker.Functions.Add(new( @@ -182,12 +197,29 @@ public void Dispose() #if defined(_WIN32) __declspec(dllexport) #endif - {{returnType.AsInteropType().GetFullyQualifiedName()}} {{invokeCallbackName}}({{string.Join(", ", interopParameters.Select(p => $"{p.InteropType.GetFullyQualifiedName()} {p.Name}"))}}) { + {{interopReturnType.GetFullyQualifiedName()}} {{invokeCallbackName}}({{string.Join(", ", interopParameters.Select(p => $"{p.InteropType.GetFullyQualifiedName()} {p.Name}").Concat(new[] { "void** reinteropException" }))}}) { auto pFunc = reinterpret_cast*>(pCallbackFunction); - {{resultImplementation}}(*pFunc)({{string.Join(", ", callParameters)}}); - {{returnImplementation}} + try { + {{resultImplementation}}(*pFunc)({{string.Join(", ", callParameters)}}); + {{returnImplementation}} + } catch (::DotNet::Reinterop::ReinteropNativeException& e) { + *reinteropException = ::DotNet::Reinterop::ObjectHandle(e.GetDotNetException().GetHandle()).Release(); + {{returnDefault}} + } catch (std::exception& e) { + *reinteropException = ::DotNet::Reinterop::ReinteropException(::DotNet::System::String(e.what())).GetHandle().Release(); + {{returnDefault}} + } catch (...) { + *reinteropException = ::DotNet::Reinterop::ReinteropException(::DotNet::System::String("An unknown native exception occurred.")).GetHandle().Release(); + {{returnDefault}} + } } - """)); + """, + TypeDefinitionsReferenced: new[] + { + CppReinteropException.GetCppType(context), + CSharpReinteropException.GetCppWrapperType(context), + CppType.FromCSharp(context, context.Compilation.GetSpecialType(SpecialType.System_String)) + })); result.CppImplementationInvoker.Functions.Add(new( Content: