diff --git a/Editor/ConfigureReinterop.cs b/Editor/ConfigureReinterop.cs index 5f5fc3a0..a1228d4d 100644 --- a/Editor/ConfigureReinterop.cs +++ b/Editor/ConfigureReinterop.cs @@ -247,6 +247,9 @@ public void ExposeToCPP() EditorApplication.ExecuteMenuItem("Window/General/Hierarchy"); EditorUtility.SetDirty(null); + + System.Exception exception = null; + var exceptionMessage = exception.Message; } } } diff --git a/Reinterop~/CodeGenerator.cs b/Reinterop~/CodeGenerator.cs index 93385823..3774bb1c 100644 --- a/Reinterop~/CodeGenerator.cs +++ b/Reinterop~/CodeGenerator.cs @@ -105,6 +105,7 @@ public IEnumerable DistributeToSourceFiles(IEnumerable ns = new List(); + if (context.BaseNamespace.Length > 0) + ns.Add(context.BaseNamespace); + ns.Add("Reinterop"); + + // If the first two namespaces are identical, remove the duplication. + // This is to avoid `Reinterop::Reinterop`. + if (ns.Count >= 2 && ns[0] == ns[1]) + ns.RemoveAt(0); + + return new CppType(InteropTypeKind.ClassWrapper, ns, "ReinteropException", null, 0); + } + + public static void Generate(CppGenerationContext context, IDictionary sourceFiles) + { + CppType type = GetCppType(context); + + string headerPath = Path.Combine(new[] { "include" }.Concat(type.Namespaces).Concat(new[] { type.Name + ".h" }).ToArray()); + + CppSourceFile? headerFile = null; + if (!sourceFiles.TryGetValue(headerPath, out headerFile)) + { + headerFile = new CppSourceFile(); + headerFile.IsHeaderFile = true; + headerFile.Filename = headerPath; + sourceFiles.Add(headerPath, headerFile); + } + + var headerNamespace = headerFile.GetNamespace(type.GetFullyQualifiedNamespace(false)); + headerNamespace.Members.Add( + $$""" + class ReinteropException : public std::runtime_error { + public: + ReinteropException(const DotNet::System::Exception& exception); + const ::DotNet::System::Exception& GetDotNetException() const; + + private: + ::DotNet::System::Exception _exception; + }; + """); + + headerFile.Includes.Add(""); + + CppType exceptionType = new CppType(InteropTypeKind.ClassWrapper, new[] { "DotNet", "System" }, "Exception", null, 0); + exceptionType.AddSourceIncludesToSet(headerFile.Includes); + + string sourcePath = Path.Combine("src", type.Name + ".cpp"); + + CppSourceFile? sourceFile = null; + if (!sourceFiles.TryGetValue(sourcePath, out sourceFile)) + { + sourceFile = new CppSourceFile(); + sourceFile.IsHeaderFile = false; + sourceFile.Filename = sourcePath; + sourceFiles.Add(sourcePath, sourceFile); + } + + type.AddSourceIncludesToSet(sourceFile.Includes); + + CppType stringType = new CppType(InteropTypeKind.ClassWrapper, new[] { "DotNet", "System" }, "String", null, 0); + stringType.AddSourceIncludesToSet(sourceFile.Includes); + + var sourceNamespace = sourceFile.GetNamespace(type.GetFullyQualifiedNamespace(false)); + sourceNamespace.Members.Add( + $$""" + ReinteropException::ReinteropException(const DotNet::System::Exception& exception) + : std::runtime_error(exception.Message().ToStlString()), + _exception(exception) {} + + const ::DotNet::System::Exception& ReinteropException::GetDotNetException() const { + return this->_exception; + } + """); + } + } +} diff --git a/Reinterop~/CppType.cs b/Reinterop~/CppType.cs index 181744af..8f5d3bb4 100644 --- a/Reinterop~/CppType.cs +++ b/Reinterop~/CppType.cs @@ -7,7 +7,8 @@ internal enum CppTypeFlags { Pointer = 1, Reference = 2, - Const = 4 + Const = 4, + DoublePointer = 9 // A double pointer is also a pointer } /// @@ -42,6 +43,7 @@ internal class CppType public static readonly CppType Single = CreatePrimitiveType(NoNamespace, "float"); public static readonly CppType Double = CreatePrimitiveType(NoNamespace, "double"); public static readonly CppType VoidPointer = CreatePrimitiveType(NoNamespace, "void", CppTypeFlags.Pointer); + public static readonly CppType VoidPointerPointer = CreatePrimitiveType(NoNamespace, "void", CppTypeFlags.DoublePointer); public static readonly CppType Void = CreatePrimitiveType(NoNamespace, "void"); public static readonly CppType NullPointer = CreatePrimitiveType(StandardNamespace, "nullptr_t", 0, IncludeCStdDef); @@ -201,11 +203,13 @@ public string GetFullyQualifiedName(bool startWithGlobal = true) } string modifier = Flags.HasFlag(CppTypeFlags.Const) ? "const " : ""; - string suffix = Flags.HasFlag(CppTypeFlags.Pointer) - ? "*" - : Flags.HasFlag(CppTypeFlags.Reference) - ? "&" - : ""; + string suffix = ""; + if (Flags.HasFlag(CppTypeFlags.DoublePointer)) + suffix = "**"; + else if (Flags.HasFlag(CppTypeFlags.Pointer)) + suffix = "*"; + else if (Flags.HasFlag(CppTypeFlags.Reference)) + suffix = "&"; string ns = GetFullyQualifiedNamespace(startWithGlobal); if (ns.Length > 0) return $"{modifier}{ns}::{Name}{template}{suffix}"; diff --git a/Reinterop~/Interop.cs b/Reinterop~/Interop.cs index 705dda56..ed052f0f 100644 --- a/Reinterop~/Interop.cs +++ b/Reinterop~/Interop.cs @@ -78,6 +78,9 @@ public static (string Name, string Content) CreateCSharpDelegateInit( invocationTarget = $"{csType.GetFullyQualifiedName()}{accessName}"; } + // Add a parameter in which to return the exception, if there is one. + CSharpType exceptionCsType = CSharpType.FromSymbol(context, context.Compilation.GetSpecialType(SpecialType.System_IntPtr)).AsPointer(); + CSharpType csReturnType = CSharpType.FromSymbol(context, returnType); CSharpType csInteropReturnType = csReturnType.AsInteropTypeReturn(); @@ -99,6 +102,8 @@ public static (string Name, string Content) CreateCSharpDelegateInit( }); } + interopParameterDetails = interopParameterDetails.Concat(new[] { (Name: "reinteropException", Type: exceptionCsType, InteropType: exceptionCsType.AsInteropTypeParameter()) }); + string interopReturnTypeString = csInteropReturnType.GetFullyQualifiedName(); string callParameterList = string.Join(", ", callParameterDetails.Select(parameter => parameter.Type.GetParameterConversionFromInteropType(parameter.Name))); @@ -226,6 +231,20 @@ public static (string Name, string Content) CreateCSharpDelegateInit( string baseName = GetUniqueNameForType(csType) + "_" + interopFunctionName; + string returnDefaultInstance = ""; + if (csInteropReturnType.SpecialType != SpecialType.System_Void) + { + if (csInteropReturnType.Symbol != null && + (csInteropReturnType.Symbol.TypeKind == TypeKind.Pointer || csInteropReturnType.Symbol.TypeKind == TypeKind.Class)) + { + returnDefaultInstance = "return null;"; + } + else + { + returnDefaultInstance = $$"""return new {{interopReturnTypeString}}();"""; + } + } + return ( Name: $"{baseName}Delegate", Content: @@ -236,7 +255,15 @@ public static (string Name, string Content) CreateCSharpDelegateInit( [AOT.MonoPInvokeCallback(typeof({{baseName}}Type))] private static unsafe {{interopReturnTypeString}} {{baseName}}({{interopParameterList}}) { - {{implementation.Replace(Environment.NewLine, Environment.NewLine + " ")}} + try + { + {{implementation.Replace(Environment.NewLine, Environment.NewLine + " ")}} + } + catch (Exception e) + { + *reinteropException = Reinterop.ObjectHandleUtility.CreateHandle(e); + {{returnDefaultInstance}} + } } """ ); diff --git a/Reinterop~/Methods.cs b/Reinterop~/Methods.cs index 3a776344..862d4819 100644 --- a/Reinterop~/Methods.cs +++ b/Reinterop~/Methods.cs @@ -119,6 +119,9 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene bool hasStructRewrite = Interop.RewriteStructReturn(ref interopParameters, ref returnType, ref interopReturnType); + // Add a parameter in which to return the exception, if there is one. + interopParameters = interopParameters.Concat(new[] { (ParameterName: "reinteropException", CallSiteName: "", Type: CppType.VoidPointerPointer, InteropType: CppType.VoidPointerPointer) }); + var interopParameterStrings = interopParameters.Select(parameter => $"{parameter.InteropType.GetFullyQualifiedName()} {parameter.ParameterName}"); // A private, static field of function pointer type that will call @@ -223,20 +226,25 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene // Method definition var parameterPassStrings = interopParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName)); + parameterPassStrings = parameterPassStrings.Concat(new[] {"&reinteropException"}).Where(s => !string.IsNullOrEmpty(s)); if (returnType.Name == "void" && !returnType.Flags.HasFlag(CppTypeFlags.Pointer)) { definition.Elements.Add(new( Content: $$""" {{templatePrefix}}{{returnType.GetFullyQualifiedName()}} {{definition.Type.Name}}{{typeTemplateSpecialization}}::{{method.Name}}{{templateSpecialization}}({{string.Join(", ", parameterStrings)}}){{afterModifiers}} { + void* reinteropException = nullptr; {{interopName}}({{string.Join(", ", parameterPassStrings)}}); + if (reinteropException != nullptr) + throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException))); } """, TypeDefinitionsReferenced: new[] { definition.Type, returnType, - CppObjectHandle.GetCppType(context) + CppObjectHandle.GetCppType(context), + CppReinteropException.GetCppType(context) }.Concat(parameters.Select(parameter => parameter.Type)) )); } @@ -269,7 +277,10 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene Content: $$""" {{templatePrefix}}{{returnType.GetFullyQualifiedName()}} {{definition.Type.Name}}{{typeTemplateSpecialization}}::{{method.Name}}{{templateSpecialization}}({{string.Join(", ", parameterStrings)}}){{afterModifiers}} { + void* reinteropException = nullptr; {{GenerationUtility.JoinAndIndent(invocation, " ")}} + if (reinteropException != nullptr) + throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException))); {{returnStatement}} } """, @@ -277,7 +288,8 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene { definition.Type, returnType, - CppObjectHandle.GetCppType(context) + CppObjectHandle.GetCppType(context), + CppReinteropException.GetCppType(context) }.Concat(parameters.Select(parameter => parameter.Type)) )); } diff --git a/Reinterop~/Properties.cs b/Reinterop~/Properties.cs index 2c9fcaa0..4b63eb6f 100644 --- a/Reinterop~/Properties.cs +++ b/Reinterop~/Properties.cs @@ -45,6 +45,9 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen bool hasStructRewrite = Interop.RewriteStructReturn(ref interopParameters, ref returnType, ref interopReturnType); + // Add a parameter in which to return the exception, if there is one. + interopParameters = interopParameters.Concat(new[] { (ParameterName: "reinteropException", CallSiteName: "", Type: CppType.VoidPointerPointer, InteropType: CppType.VoidPointerPointer) }); + var interopParameterStrings = interopParameters.Select(parameter => $"{parameter.InteropType.GetFullyQualifiedName()} {parameter.ParameterName}"); // A private, static field of function pointer type that will call @@ -97,20 +100,25 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen // Method definition var parameterPassStrings = interopParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName)); + parameterPassStrings = parameterPassStrings.Concat(new[] {"&reinteropException"}).Where(s => !string.IsNullOrEmpty(s)); if (returnType.Name == "void" && !returnType.Flags.HasFlag(CppTypeFlags.Pointer)) { definition.Elements.Add(new( Content: $$""" {{returnType.GetFullyQualifiedName()}} {{definition.Type.Name}}{{typeTemplateSpecialization}}::{{propertyName}}({{string.Join(", ", parameterStrings)}}){{afterModifiers}} { + void* reinteropException = nullptr; Property_{{method.Name}}({{string.Join(", ", parameterPassStrings)}}); + if (reinteropException != nullptr) + throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException))); } """, TypeDefinitionsReferenced: new[] { definition.Type, returnType, - CppObjectHandle.GetCppType(context) + CppObjectHandle.GetCppType(context), + CppReinteropException.GetCppType(context) }.Concat(parameters.Select(parameter => parameter.Type)) )); } @@ -129,7 +137,10 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen Content: $$""" {{returnType.GetFullyQualifiedName()}} {{definition.Type.Name}}{{typeTemplateSpecialization}}::{{propertyName}}({{string.Join(", ", parameterStrings)}}){{afterModifiers}} { + void* reinteropException = nullptr; {{GenerationUtility.JoinAndIndent(invocation, " ")}} + if (reinteropException != nullptr) + throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException))); return {{returnType.GetConversionFromInteropType(context, "result")}}; } """, @@ -137,7 +148,8 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen { definition.Type, returnType, - CppObjectHandle.GetCppType(context) + CppObjectHandle.GetCppType(context), + CppReinteropException.GetCppType(context) }.Concat(parameters.Select(parameter => parameter.Type)) )); } diff --git a/Runtime/ConfigureReinterop.cs b/Runtime/ConfigureReinterop.cs index 43ed8ed7..93415b2c 100644 --- a/Runtime/ConfigureReinterop.cs +++ b/Runtime/ConfigureReinterop.cs @@ -919,6 +919,8 @@ Cesium3DTilesetLoadFailureDetails tilesetDetails #endif TestReinterop.ThrowAnException(); + System.Exception exception = null; + var message = exception.Message; } } }