diff --git a/src/Generator/Generators/CLI/CLIGenerator.cs b/src/Generator/Generators/CLI/CLIGenerator.cs
index 23d91c68..5241b3ec 100644
--- a/src/Generator/Generators/CLI/CLIGenerator.cs
+++ b/src/Generator/Generators/CLI/CLIGenerator.cs
@@ -32,6 +32,10 @@ namespace CppSharp.Generators.CLI
public override bool SetupPasses()
{
+ // Note: The ToString override will only work if this pass runs
+ // after the MoveOperatorToCallPass.
+ if (Driver.Options.GenerateObjectOverrides)
+ Driver.TranslationUnitPasses.AddPass(new ObjectOverridesPass());
return true;
}
}
diff --git a/src/Generator/Options.cs b/src/Generator/Options.cs
index 3f1d617f..87bec23e 100644
--- a/src/Generator/Options.cs
+++ b/src/Generator/Options.cs
@@ -122,6 +122,13 @@ namespace CppSharp
///
public bool GenerateConversionOperators;
+ ///
+ /// If set to true the CLI generator will use ObjectOverridesPass to create
+ /// Equals, GetHashCode and (if the insertion operator << is overloaded) ToString
+ /// methods.
+ ///
+ public bool GenerateObjectOverrides;
+
//List of include directories that are used but not generated
public List NoGenIncludeDirs;
diff --git a/src/Generator/Passes/ObjectOverridesPass.cs b/src/Generator/Passes/ObjectOverridesPass.cs
index 0b67d4e0..c734b53b 100644
--- a/src/Generator/Passes/ObjectOverridesPass.cs
+++ b/src/Generator/Passes/ObjectOverridesPass.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using CppSharp.AST;
using CppSharp.Generators;
using CppSharp.Generators.CLI;
@@ -7,10 +8,17 @@ using CppSharp.Passes;
namespace CppSharp
{
+ // This pass adds Equals and GetHashCode methods to classes.
+ // It will also add a ToString method if the insertion operator (<<)
+ // of the class is overloaded.
+ // Note that the OperatorToClassPass needs to run first in order for
+ // this to work.
public class ObjectOverridesPass : TranslationUnitPass
{
+ private bool needsStreamInclude;
private void OnUnitGenerated(GeneratorOutput output)
{
+ needsStreamInclude = false;
foreach (var template in output.Templates)
{
foreach (var block in template.FindBlocks(CLIBlockKind.MethodBody))
@@ -18,6 +26,20 @@ namespace CppSharp
var method = block.Declaration as Method;
VisitMethod(method, block);
}
+ if (needsStreamInclude)
+ {
+ var sourcesTemplate = template as CLISourcesTemplate;
+ if (sourcesTemplate != null)
+ {
+ foreach (var block in sourcesTemplate.FindBlocks(CLIBlockKind.Includes))
+ {
+ block.WriteLine("#include ");
+ block.WriteLine("");
+ break;
+ }
+ break;
+ }
+ }
}
}
@@ -33,6 +55,9 @@ namespace CppSharp
if (method.Name == "Equals" && method.Parameters.Count == 1)
GenerateEquals(@class, block, method);
+
+ if (method.Name == "ToString" && method.Parameters.Count == 0)
+ GenerateToString(@class, block, method);
}
void GenerateGetHashCode(Block block)
@@ -54,6 +79,14 @@ namespace CppSharp
block.Write("return __Instance == obj->__Instance;");
}
+ void GenerateToString(Class @class, Block block, Method method)
+ {
+ needsStreamInclude = true;
+ block.WriteLine("std::ostringstream os;");
+ block.WriteLine("os << *NativePtr;");
+ block.Write("return clix::marshalString(os.str());");
+ }
+
private bool isHooked;
public override bool VisitClassDecl(Class @class)
{
@@ -67,9 +100,38 @@ namespace CppSharp
if (!VisitDeclaration(@class))
return false;
+ // We can't handle value types yet
+ // The generated code assumes that a NativePtr is available
if (@class.IsValueType)
return false;
+ foreach (var method in @class.Methods)
+ {
+ if (!IsInsertionOperator(method))
+ continue;
+
+ // Create the ToString method
+ var stringType = GetType("std::string");
+ if (stringType == null)
+ stringType = new CILType(typeof(string));
+ var toStringMethod = new Method()
+ {
+ Name = "ToString",
+ Namespace = @class,
+ ReturnType = new QualifiedType(stringType),
+ IsSynthetized = true,
+ IsOverride = true,
+ IsProxy = true
+ };
+
+ @class.Methods.Add(toStringMethod);
+
+ Driver.Diagnostics.Debug("Function converted to ToString: {0}::{1}",
+ @class.Name, method.Name);
+
+ break;
+ }
+
var methodEqualsParam = new Parameter
{
Name = "object",
@@ -86,8 +148,8 @@ namespace CppSharp
IsOverride = true,
IsProxy = true
};
- @class.Methods.Add(methodEquals);
-
+ @class.Methods.Add(methodEquals);
+
var methodHashCode = new Method
{
Name = "GetHashCode",
@@ -101,5 +163,56 @@ namespace CppSharp
@class.Methods.Add(methodHashCode);
return true;
}
+
+ private Dictionary typeCache;
+ protected AST.Type GetType(string typeName)
+ {
+ if (typeCache == null)
+ typeCache = new Dictionary();
+ AST.Type result;
+ if (!typeCache.TryGetValue(typeName, out result))
+ {
+ var typeDef = Driver.ASTContext.FindTypedef(typeName)
+ .FirstOrDefault();
+ if (typeDef != null)
+ result = new TypedefType() { Declaration = typeDef };
+ typeCache.Add(typeName, result);
+ }
+ return result;
+ }
+
+ private bool IsInsertionOperator(Method method)
+ {
+ // Do some basic check
+ if (!method.IsOperator)
+ return false;
+ if (method.OperatorKind != CXXOperatorKind.LessLess)
+ return false;
+ if (method.Parameters.Count != 2)
+ return false;
+
+ // Check that first parameter is a std::ostream&
+ var fstType = method.Parameters[0].Type as PointerType;
+ if (fstType == null)
+ return false;
+ var oStreamType = GetType("std::ostream");
+ if (oStreamType == null)
+ return false;
+ if (!oStreamType.Equals(fstType.Pointee))
+ return false;
+
+ // Check that second parameter is a const CLASS&
+ var sndType = method.Parameters[1].Type as PointerType;
+ if (sndType == null)
+ return false;
+ if (!sndType.QualifiedPointee.Qualifiers.IsConst)
+ return false;
+ var @class = method.Namespace as Class;
+ var classType = new TagType(@class);
+ if (!classType.Equals(sndType.Pointee))
+ return false;
+
+ return true;
+ }
}
}
diff --git a/tests/CLITemp/CLITemp.Tests.cs b/tests/CLITemp/CLITemp.Tests.cs
index d3dc0605..9870f259 100644
--- a/tests/CLITemp/CLITemp.Tests.cs
+++ b/tests/CLITemp/CLITemp.Tests.cs
@@ -1,6 +1,7 @@
using CppSharp.Utils;
using NUnit.Framework;
using CLITemp;
+using System;
public class CLITests : GeneratorTestFixture
{
@@ -11,4 +12,12 @@ public class CLITests : GeneratorTestFixture
var sum = new Types().AttributedSum(3, 4);
Assert.That(sum, Is.EqualTo(7));
}
+
+ [Test]
+ public void TestToStringOverride()
+ {
+ var date = new Date(24, 12, 1924);
+ var s = date.ToString();
+ Assert.AreEqual("24/12/1924", s);
+ }
}
\ No newline at end of file
diff --git a/tests/CLITemp/CLITemp.cs b/tests/CLITemp/CLITemp.cs
index 4e13a56a..71a42f17 100644
--- a/tests/CLITemp/CLITemp.cs
+++ b/tests/CLITemp/CLITemp.cs
@@ -14,6 +14,7 @@ namespace CppSharp.Tests
public override void Setup(Driver driver)
{
driver.Options.GenerateFinalizers = true;
+ driver.Options.GenerateObjectOverrides = true;
base.Setup(driver);
}
diff --git a/tests/CLITemp/CLITemp.h b/tests/CLITemp/CLITemp.h
index de46ecc7..8d612e51 100644
--- a/tests/CLITemp/CLITemp.h
+++ b/tests/CLITemp/CLITemp.h
@@ -1,5 +1,7 @@
#include "../Tests.h"
+#include
+
// Tests for C++ types
struct DLL_API Types
{
@@ -23,4 +25,23 @@ class DLL_API TestProtectedDestructors
~TestProtectedDestructors();
};
-TestProtectedDestructors::~TestProtectedDestructors() {}
\ No newline at end of file
+TestProtectedDestructors::~TestProtectedDestructors() {}
+
+// Tests the insertion operator (<<) to ToString method pass
+class DLL_API Date
+{
+public:
+ Date(int m, int d, int y)
+ {
+ mo = m; da = d; yr = y;
+ }
+ // Not picked up by parser yet
+ //friend std::ostream& operator<<(std::ostream& os, const Date& dt);
+ int mo, da, yr;
+};
+
+std::ostream& operator<<(std::ostream& os, const Date& dt)
+{
+ os << dt.mo << '/' << dt.da << '/' << dt.yr;
+ return os;
+}