Skip to content

Commit

Permalink
Merge pull request #548 from CesiumGS/reinterop-exceptions
Browse files Browse the repository at this point in the history
Add exception support to Reinterop
  • Loading branch information
kring authored Feb 2, 2025
2 parents 852ebae + ddc0eba commit 0769efa
Show file tree
Hide file tree
Showing 21 changed files with 507 additions and 24 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ jobs:
run: |
cd d:\cesium\CesiumForUnityBuildProject\Packages\com.cesium.unity
dotnet publish Reinterop~ -o .
- name: Print disk space 1
if: success() || failure() # run this step even if previous step failed
run: |
get-psdrive
- name: Build Package
run: |
cd d:\cesium\CesiumForUnityBuildProject\Packages\com.cesium.unity
Expand All @@ -128,6 +132,10 @@ jobs:
$ENV:EZVCPKG_BASEDIR="D:/.ezvcpkg"
# Run the build
dotnet run --project Build~
- name: Print disk space 2
if: success() || failure() # run this step even if previous step failed
run: |
get-psdrive
- name: Publish Logs
if: success() || failure() # run this step even if previous step failed
uses: actions/upload-artifact@v4
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## ? - ?

##### Fixes :wrench:

- Exceptions in either C# or C++ code are now handled much more reliably.

## v1.14.1 - 2025-01-02

This is the last release of Cesium for Unity that will support Unity 2021 LTS (2021.3). Future versions will require Unity 2022 LTS (2022.3) or Unity 6.
Expand Down
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;
}
}
}
50 changes: 50 additions & 0 deletions Reinterop~/CSharpReinteropException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Microsoft.CodeAnalysis;

namespace Reinterop
{
/// <summary>
/// Inserts the "ReinteropException" class into an assembly as it is compiled.
/// This is intended to be used from a RegisterPostInitializationOutput
/// callback on an IIncrementalGenerator.
/// </summary>
internal class CSharpReinteropException
{
public static void Generate(GeneratorExecutionContext context)
{
context.AddSource("ReinteropException", Source);
}

public static CppType GetCppWrapperType(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 const string Source =
"""
namespace Reinterop
{
[Reinterop]
internal class ReinteropException : System.Exception
{
public ReinteropException(string message) : base(message) {}
internal static void ExposeToCPP()
{
ReinteropException e = new ReinteropException("message");
string s = e.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
20 changes: 17 additions & 3 deletions Reinterop~/Constructors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo

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: "&reinteropException", Type: CppType.VoidPointerPointer, InteropType: CppType.VoidPointerPointer) });

var interopParameterStrings = interopParameters.Select(parameter => $"{parameter.InteropType.GetFullyQualifiedName()} {parameter.ParameterName}");

string interopFunctionName = $"Construct_{Interop.HashParameters(constructor.Parameters)}";
Expand Down Expand Up @@ -96,16 +99,20 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo
$$"""
{{definition.Type.Name}} {{definition.Type.Name}}{{templateSpecialization}}::Construct({{string.Join(", ", parameterStrings)}})
{
void* reinteropException = nullptr;
{{definition.Type.Name}} result;
{{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}});
if (reinteropException != nullptr)
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
return result;
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
interopReturnType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
Expand All @@ -124,15 +131,22 @@ private static void GenerateSingleNonStatic(CppGenerationContext context, TypeTo
Content:
$$"""
{{definition.Type.Name}}{{templateSpecialization}}::{{definition.Type.Name}}({{string.Join(", ", parameterStrings)}})
: _handle({{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}}))
: _handle([&]() mutable {
void* reinteropException = nullptr;
void* handle = {{interopFunctionName}}({{string.Join(", ", parameterPassStrings)}});
if (reinteropException != nullptr)
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
return handle;
}())
{
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
interopReturnType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}.Concat(parameters.Select(parameter => parameter.Type))
));
}
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, "ReinteropNativeException", 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 ReinteropNativeException : public std::runtime_error {
public:
ReinteropNativeException(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(
$$"""
ReinteropNativeException::ReinteropNativeException(const DotNet::System::Exception& exception)
: std::runtime_error(exception.Message().ToStlString()),
_exception(exception) {}
const ::DotNet::System::Exception& ReinteropNativeException::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
28 changes: 25 additions & 3 deletions Reinterop~/Fields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ private static void GenerateSingleFieldAccessors(CppGenerationContext context, T

bool hasStructRewrite = Interop.RewriteStructReturn(ref getParameters, ref getType, ref getInteropType);

// Add a parameter in which to return the exception, if there is one.
getParameters = getParameters.Concat(new[] { (ParameterName: "reinteropException", CallSiteName: "&reinteropException", Type: CppType.VoidPointerPointer, InteropType: CppType.VoidPointerPointer) });
interopSetParameters = interopSetParameters + ", void** reinteropException";

var interopGetParameters = getParameters.Select(parameter => $"{parameter.InteropType.GetFullyQualifiedName()} {parameter.ParameterName}");
var interopGetParametersCall = getParameters.Select(parameter => parameter.Type.GetConversionToInteropType(context, parameter.CallSiteName));

Expand Down Expand Up @@ -145,7 +149,11 @@ private static void GenerateSingleFieldAccessors(CppGenerationContext context, T

string[] invocation = new[]
{
$"void* reinteropException = nullptr;",
$"auto result = Field_get_{field.Name}({string.Join(", ", interopGetParametersCall)});",
$"if (reinteropException != nullptr) {{",
$" throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));",
$"}}",
$"return {getType.GetConversionFromInteropType(context, "result")};"
};
if (hasStructRewrite)
Expand All @@ -154,17 +162,25 @@ private static void GenerateSingleFieldAccessors(CppGenerationContext context, T
{
invocation = new[]
{
$"void* reinteropException = nullptr;",
$"{getType.GenericArguments.FirstOrDefault().GetFullyQualifiedName()} result;",
$"std::uint8_t resultIsValid = Field_get_{field.Name}({string.Join(", ", interopGetParametersCall)});",
$"if (reinteropException != nullptr) {{",
$" throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));",
$"}}",
$"return resultIsValid ? std::make_optional(std::move({getType.GetConversionFromInteropType(context, "result")})) : std::nullopt;"
};
}
else
{
invocation = new[]
{
$"void* reinteropException = nullptr;",
$"{getType.GetFullyQualifiedName()} result;",
$"Field_get_{field.Name}({string.Join(", ", interopGetParametersCall)});",
$"if (reinteropException != nullptr) {{",
$" throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));",
$"}}",
$"return {getType.GetConversionFromInteropType(context, "result")};"
};
}
Expand All @@ -181,22 +197,28 @@ private static void GenerateSingleFieldAccessors(CppGenerationContext context, T
{
definition.Type,
getType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}
));

definition.Elements.Add(new(
Content:
$$"""
void {{definition.Type.Name}}::{{field.Name}}({{setType.GetFullyQualifiedName()}} value){{(field.IsStatic ? "" : " const")}} {
Field_set_{{field.Name}}({{interopSetParametersCall}});
void* reinteropException = nullptr;
Field_set_{{field.Name}}({{interopSetParametersCall}}, &reinteropException);
if (reinteropException != nullptr) {
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
}
}
""",
TypeDefinitionsReferenced: new[]
{
definition.Type,
setType,
CppObjectHandle.GetCppType(context)
CppObjectHandle.GetCppType(context),
CppReinteropException.GetCppType(context)
}
));
}
Expand Down
Loading

0 comments on commit 0769efa

Please sign in to comment.