Skip to content

Commit

Permalink
Propagate C# exceptions to C++ across Reinterop boundary.
Browse files Browse the repository at this point in the history
  • Loading branch information
kring committed Jan 24, 2025
1 parent 597f14b commit 5cc7921
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 11 deletions.
3 changes: 3 additions & 0 deletions Editor/ConfigureReinterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ public void ExposeToCPP()
EditorApplication.ExecuteMenuItem("Window/General/Hierarchy");

EditorUtility.SetDirty(null);

System.Exception exception = null;
var exceptionMessage = exception.Message;
}
}
}
1 change: 1 addition & 0 deletions Reinterop~/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public IEnumerable<CppSourceFile> DistributeToSourceFiles(IEnumerable<GeneratedR

// Create source files for the standard types.
CppObjectHandle.Generate(this.Options, sourceFiles);
CppReinteropException.Generate(this.Options, sourceFiles);

// Create source files for the generated types.
foreach (GeneratedResult? generated in generatedResults)
Expand Down
82 changes: 82 additions & 0 deletions Reinterop~/CppReinteropException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace Reinterop
{
internal class CppReinteropException
{
public static CppType GetCppType(CppGenerationContext context)
{
List<string> ns = new List<string>();
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<string, CppSourceFile> 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("<stdexcept>");

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;
}
""");
}
}
}
16 changes: 10 additions & 6 deletions Reinterop~/CppType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ internal enum CppTypeFlags
{
Pointer = 1,
Reference = 2,
Const = 4
Const = 4,
DoublePointer = 9 // A double pointer is also a pointer
}

/// <summary>
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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}";
Expand Down
29 changes: 28 additions & 1 deletion Reinterop~/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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)));
Expand Down Expand Up @@ -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:
Expand All @@ -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}}
}
}
"""
);
Expand Down
16 changes: 14 additions & 2 deletions Reinterop~/Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
));
}
Expand Down Expand Up @@ -269,15 +277,19 @@ 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}}
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
returnType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
Expand Down
16 changes: 14 additions & 2 deletions Reinterop~/Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
));
}
Expand All @@ -129,15 +137,19 @@ 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")}};
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
returnType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
Expand Down
2 changes: 2 additions & 0 deletions Runtime/ConfigureReinterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,8 @@ Cesium3DTilesetLoadFailureDetails tilesetDetails
#endif

TestReinterop.ThrowAnException();
System.Exception exception = null;
var message = exception.Message;
}
}
}

0 comments on commit 5cc7921

Please sign in to comment.