Skip to content

Commit

Permalink
Flesh out exception handling and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
kring committed Jan 24, 2025
1 parent b5da340 commit e31a45b
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 15 deletions.
16 changes: 16 additions & 0 deletions Reinterop~/CSharpReinteropException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ 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
Expand All @@ -26,6 +41,7 @@ public ReinteropException(string message) : base(message) {}
internal static void ExposeToCPP()
{
ReinteropException e = new ReinteropException("message");
string s = e.Message;
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions Reinterop~/CppReinteropException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static CppType GetCppType(CppGenerationContext context)
if (ns.Count >= 2 && ns[0] == ns[1])
ns.RemoveAt(0);

return new CppType(InteropTypeKind.ClassWrapper, ns, "ReinteropException", null, 0);
return new CppType(InteropTypeKind.ClassWrapper, ns, "ReinteropNativeException", null, 0);
}

public static void Generate(CppGenerationContext context, IDictionary<string, CppSourceFile> sourceFiles)
Expand All @@ -35,9 +35,9 @@ public static void Generate(CppGenerationContext context, IDictionary<string, Cp
var headerNamespace = headerFile.GetNamespace(type.GetFullyQualifiedNamespace(false));
headerNamespace.Members.Add(
$$"""
class ReinteropException : public std::runtime_error {
class ReinteropNativeException : public std::runtime_error {
public:
ReinteropException(const DotNet::System::Exception& exception);
ReinteropNativeException(const DotNet::System::Exception& exception);
const ::DotNet::System::Exception& GetDotNetException() const;
private:
Expand Down Expand Up @@ -69,11 +69,11 @@ class ReinteropException : public std::runtime_error {
var sourceNamespace = sourceFile.GetNamespace(type.GetFullyQualifiedNamespace(false));
sourceNamespace.Members.Add(
$$"""
ReinteropException::ReinteropException(const DotNet::System::Exception& exception)
ReinteropNativeException::ReinteropNativeException(const DotNet::System::Exception& exception)
: std::runtime_error(exception.Message().ToStlString()),
_exception(exception) {}
const ::DotNet::System::Exception& ReinteropException::GetDotNetException() const {
const ::DotNet::System::Exception& ReinteropNativeException::GetDotNetException() const {
return this->_exception;
}
""");
Expand Down
4 changes: 2 additions & 2 deletions Reinterop~/Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene
void* reinteropException = nullptr;
{{interopName}}({{string.Join(", ", parameterPassStrings)}});
if (reinteropException != nullptr)
throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
}
""",
TypeDefinitionsReferenced: new[]
Expand Down Expand Up @@ -280,7 +280,7 @@ public static void GenerateSingleMethod(CppGenerationContext context, TypeToGene
void* reinteropException = nullptr;
{{GenerationUtility.JoinAndIndent(invocation, " ")}}
if (reinteropException != nullptr)
throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
{{returnStatement}}
}
""",
Expand Down
6 changes: 4 additions & 2 deletions Reinterop~/MethodsImplementedInCpp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate
try {
{{GenerationUtility.JoinAndIndent(new[] { getCallTarget }, " ")}}
{{new[] { implementation }.JoinAndIndent(" ")}}
} catch (::DotNet::Reinterop::ReinteropException& e) {
} catch (::DotNet::Reinterop::ReinteropNativeException& e) {
*reinteropException = ::DotNet::Reinterop::ObjectHandle(e.GetDotNetException().GetHandle()).Release();
{{returnDefault}}
} catch (std::exception& e) {
Expand All @@ -357,7 +357,9 @@ private static void GenerateMethod(CppGenerationContext context, TypeToGenerate
implType,
returnType,
objectHandleType,
CppReinteropException.GetCppType(context)
CppReinteropException.GetCppType(context),
CSharpReinteropException.GetCppWrapperType(context),
CppType.FromCSharp(context, context.Compilation.GetSpecialType(SpecialType.System_String))
}.Concat(parameters.Select(parameter => parameter.Type))
.Concat(parameters.Select(parameter => parameter.InteropType)),
AdditionalIncludes: hasStructRewrite ? new[] { "<utility>" } : null // for std::move
Expand Down
6 changes: 3 additions & 3 deletions Reinterop~/Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ 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));
parameterPassStrings = parameterPassStrings.Concat(new[] { "&reinteropException" }).Where(s => !string.IsNullOrEmpty(s));
if (returnType.Name == "void" && !returnType.Flags.HasFlag(CppTypeFlags.Pointer))
{
definition.Elements.Add(new(
Expand All @@ -110,7 +110,7 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen
void* reinteropException = nullptr;
Property_{{method.Name}}({{string.Join(", ", parameterPassStrings)}});
if (reinteropException != nullptr)
throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
}
""",
TypeDefinitionsReferenced: new[]
Expand Down Expand Up @@ -140,7 +140,7 @@ private static void GenerateSingleMethod(CppGenerationContext context, TypeToGen
void* reinteropException = nullptr;
{{GenerationUtility.JoinAndIndent(invocation, " ")}}
if (reinteropException != nullptr)
throw Reinterop::ReinteropException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
throw Reinterop::ReinteropNativeException(::DotNet::System::Exception(::DotNet::Reinterop::ObjectHandle(reinteropException)));
return {{returnType.GetConversionFromInteropType(context, "result")}};
}
""",
Expand Down
26 changes: 25 additions & 1 deletion Reinterop~/RoslynSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public void Execute(GeneratorExecutionContext context)
// Create a new Compilation with the CSharpObjectHandleUtility created above.
// Newer versions of Roslyn make this easy, but not the one in Unity.
CSharpParseOptions options = (CSharpParseOptions)((CSharpCompilation)context.Compilation).SyntaxTrees[0].Options;
Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(CSharpObjectHandleUtility.Source), options));
Compilation compilation = context.Compilation.AddSyntaxTrees(
CSharpSyntaxTree.ParseText(SourceText.From(CSharpObjectHandleUtility.Source), options),
CSharpSyntaxTree.ParseText(SourceText.From(CSharpReinteropException.Source), options)
);

// Add ObjectHandleUtility's ExposeToCPP to the receiver.
INamedTypeSymbol? objectHandleUtilityType = compilation.GetTypeByMetadataName("Reinterop.ObjectHandleUtility");
Expand All @@ -59,6 +62,27 @@ public void Execute(GeneratorExecutionContext context)
}
}

// Add ReinteropExceptions's ExposeToCPP to the receiver.
INamedTypeSymbol? reinteropExceptionType = compilation.GetTypeByMetadataName("Reinterop.ReinteropException");
if (reinteropExceptionType != null)
{
var exposeToCpp = CSharpTypeUtility.FindMembers(reinteropExceptionType, "ExposeToCPP");
foreach (ISymbol symbol in exposeToCpp)
{
IMethodSymbol? method = symbol as IMethodSymbol;
if (method == null)
continue;

foreach (var reference in method.DeclaringSyntaxReferences)
{
if (reference.GetSyntax() is MethodDeclarationSyntax methodDeclaration)
{
receiver.ExposeToCppMethods.Add(methodDeclaration);
}
}
}
}

Dictionary<string, object> properties = new Dictionary<string, object>();
foreach (var property in receiver.Properties)
{
Expand Down
2 changes: 2 additions & 0 deletions Runtime/TestReinterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal partial class TestReinterop
{
public partial bool CallThrowAnExceptionFromCppAndCatchIt();
public partial bool CallThrowAnExceptionFromCppAndDontCatchIt();
public partial bool ThrowCppStdException();
public partial bool ThrowOtherCppExceptionType();

public static void ThrowAnException()
{
Expand Down
34 changes: 32 additions & 2 deletions Tests/TestReinterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,41 @@ public void TestADotNetExceptionCanBeCaughtInCpp()
public void TestADotNetExceptionCanPropagateThroughCpp()
{
CesiumForUnity.TestReinterop o = new CesiumForUnity.TestReinterop();
try {
try
{
o.CallThrowAnExceptionFromCppAndDontCatchIt();
} catch (System.Exception e)
}
catch (System.Exception e)
{
Assert.AreEqual("Test Exception!", e.Message);
}
}

[Test]
public void TestACppStdExceptionCanBeCaughtInCSharp()
{
CesiumForUnity.TestReinterop o = new CesiumForUnity.TestReinterop();
try
{
o.ThrowCppStdException();
}
catch (System.Exception e)
{
Assert.AreEqual("An exceptional hello from C++!", e.Message);
}
}

[Test]
public void TestAGeneralCppExceptionCanBeCaughtInCSharp()
{
CesiumForUnity.TestReinterop o = new CesiumForUnity.TestReinterop();
try
{
o.ThrowOtherCppExceptionType();
}
catch (System.Exception e)
{
Assert.AreEqual("An unknown native exception occurred.", e.Message);
}
}
}
12 changes: 12 additions & 0 deletions native~/Runtime/src/TestReinteropImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <DotNet/CesiumForUnity/TestReinterop.h>

#include <stdexcept>

namespace CesiumForUnityNative {

bool TestReinteropImpl::CallThrowAnExceptionFromCppAndCatchIt(
Expand All @@ -21,4 +23,14 @@ bool TestReinteropImpl::CallThrowAnExceptionFromCppAndDontCatchIt(
return false;
}

bool TestReinteropImpl::ThrowCppStdException(
const DotNet::CesiumForUnity::TestReinterop& instance) {
throw std::exception("An exceptional hello from C++!");
}

bool TestReinteropImpl::ThrowOtherCppExceptionType(
const DotNet::CesiumForUnity::TestReinterop& instance) {
throw "This is a dodgy exception.";
}

} // namespace CesiumForUnityNative
4 changes: 4 additions & 0 deletions native~/Runtime/src/TestReinteropImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class TestReinteropImpl {
const DotNet::CesiumForUnity::TestReinterop& instance);
static bool CallThrowAnExceptionFromCppAndDontCatchIt(
const DotNet::CesiumForUnity::TestReinterop& instance);
static bool
ThrowCppStdException(const DotNet::CesiumForUnity::TestReinterop& instance);
static bool ThrowOtherCppExceptionType(
const DotNet::CesiumForUnity::TestReinterop& instance);
};

} // namespace CesiumForUnityNative

0 comments on commit e31a45b

Please sign in to comment.