diff --git a/build/Tests.lua b/build/Tests.lua index b403a7bf..a0a9fa31 100644 --- a/build/Tests.lua +++ b/build/Tests.lua @@ -12,11 +12,11 @@ function SetupExampleProject() SetupParser() end -function SetupTestProject(name, file, lib) +function SetupTestProject(name, extraFiles) SetupTestGeneratorProject(name) SetupTestNativeProject(name) - SetupTestProjectsCSharp(name) - SetupTestProjectsCLI(name) + SetupTestProjectsCSharp(name, nil, extraFiles) + SetupTestProjectsCLI(name, extraFiles) end function SetupTestCSharp(name) @@ -107,7 +107,7 @@ function LinkNUnit() } end -function SetupTestProjectsCSharp(name, depends) +function SetupTestProjectsCSharp(name, depends, extraFiles) project(name .. ".CSharp") SetupManagedTestProject() @@ -118,6 +118,11 @@ function SetupTestProjectsCSharp(name, depends) { path.join(gendir, name, name .. ".cs"), } + if extraFiles ~= nil then + for _, file in pairs(extraFiles) do + files { path.join(gendir, name, file .. ".cs") } + end + end linktable = { "CppSharp.Runtime" } @@ -138,7 +143,7 @@ function SetupTestProjectsCSharp(name, depends) links { "CppSharp.Runtime" } end -function SetupTestProjectsCLI(name) +function SetupTestProjectsCLI(name, extraFiles) if not os.is_windows() then return end @@ -156,8 +161,14 @@ function SetupTestProjectsCLI(name) files { path.join(gendir, name, name .. ".cpp"), - path.join(gendir, name, name .. ".h"), + path.join(gendir, name, name .. ".h") } + if extraFiles ~= nil then + for _, file in pairs(extraFiles) do + files { path.join(gendir, name, file .. ".cpp") } + files { path.join(gendir, name, file .. ".h") } + end + end includedirs { path.join(testsdir, name), incdir } links { name .. ".Native" } diff --git a/src/AST/Declaration.cs b/src/AST/Declaration.cs index 6c341235..0632d603 100644 --- a/src/AST/Declaration.cs +++ b/src/AST/Declaration.cs @@ -360,6 +360,7 @@ namespace CppSharp.AST { public Type Type { get { return QualifiedType.Type; } } public QualifiedType QualifiedType { get; set; } + public bool IsSynthetized { get; set; } public override T Visit(IDeclVisitor visitor) { diff --git a/src/Generator/AST/ASTRecord.cs b/src/Generator/AST/ASTRecord.cs index bd5bf040..81c2f0d0 100644 --- a/src/Generator/AST/ASTRecord.cs +++ b/src/Generator/AST/ASTRecord.cs @@ -149,6 +149,12 @@ namespace CppSharp.Generators.AST Class decl; return field.Type.Desugar().TryGetClass(out decl) && decl.IsValueType; } + + public static bool IsDelegate(this ASTRecord record) + { + var typedef = record.Object as TypedefDecl; + return typedef != null && typedef.Type.GetPointee() is FunctionType; + } } public class RecordCollector : AstVisitor @@ -182,9 +188,7 @@ namespace CppSharp.Generators.AST public override bool VisitType(Type type, TypeQualifiers quals) { - type = type.Desugar(); - - if(recordStack.Contains(type)) + if (recordStack.Contains(type)) return true; recordStack.Push(type); diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs index 1bd72493..1cb82890 100644 --- a/src/Generator/Driver.cs +++ b/src/Generator/Driver.cs @@ -253,6 +253,7 @@ namespace CppSharp TranslationUnitPasses.AddPass(new CheckStaticClass()); TranslationUnitPasses.AddPass(new MoveOperatorToClassPass()); TranslationUnitPasses.AddPass(new MoveFunctionToClassPass()); + TranslationUnitPasses.AddPass(new GenerateAnonymousDelegatesPass()); if (Options.GenerateConversionOperators) TranslationUnitPasses.AddPass(new ConstructorToConversionOperatorPass()); diff --git a/src/Generator/Generators/CLI/CLIMarshal.cs b/src/Generator/Generators/CLI/CLIMarshal.cs index 554625f6..eb007378 100644 --- a/src/Generator/Generators/CLI/CLIMarshal.cs +++ b/src/Generator/Generators/CLI/CLIMarshal.cs @@ -586,7 +586,17 @@ namespace CppSharp.Generators.CLI FunctionType func; if (decl.Type.IsPointerTo(out func)) { - VisitDelegateType(func, "::" + typedef.Declaration.QualifiedOriginalName); + // Use the original typedef name if available, otherwise just use the function pointer type + string cppTypeName; + if (!decl.IsSynthetized) + cppTypeName = "::" + typedef.Declaration.QualifiedOriginalName; + else + { + var cppTypePrinter = new CppTypePrinter(Context.Driver.TypeDatabase); + cppTypeName = decl.Type.Visit(cppTypePrinter, quals); + } + + VisitDelegateType(func, cppTypeName); return true; } diff --git a/src/Generator/Generators/CLI/CLITypeReferences.cs b/src/Generator/Generators/CLI/CLITypeReferences.cs index a3f8de49..815c862d 100644 --- a/src/Generator/Generators/CLI/CLITypeReferences.cs +++ b/src/Generator/Generators/CLI/CLITypeReferences.cs @@ -172,7 +172,7 @@ namespace CppSharp.Generators.CLI if (TranslationUnit == record.Value.Namespace.TranslationUnit) return false; - return record.IsBaseClass() || record.IsFieldValueType(); + return record.IsBaseClass() || record.IsFieldValueType() || record.IsDelegate(); } public override bool VisitDeclaration(Declaration decl) diff --git a/src/Generator/Passes/GenerateAnonymousDelegatesPass.cs b/src/Generator/Passes/GenerateAnonymousDelegatesPass.cs new file mode 100644 index 00000000..b0eaa90f --- /dev/null +++ b/src/Generator/Passes/GenerateAnonymousDelegatesPass.cs @@ -0,0 +1,126 @@ +using System.Collections.Generic; +using System.Linq; +using CppSharp.AST; +using CppSharp.AST.Extensions; + +namespace CppSharp.Passes +{ + /// + /// C++ function pointers are converted to delegates. If the function pointer is typedef'd then a delegate is + /// generated with that name, otherwise the generic Action or Func delegates are used. The delegates are marhsalled + /// using the GetDelegateForFunctionPointer and GetFunctionPointerForDelegate methods; unfortunately, these methods + /// don't support generic types, so marshalling fails when function pointers are used without typedefs. This pass + /// generates explicit delegates for these function pointers when used as function arguments or return types. + /// + public class GenerateAnonymousDelegatesPass : TranslationUnitPass + { + private struct Typedef + { + public DeclarationContext Context; + public TypedefDecl Declaration; + } + + /// + /// The generated typedefs keyed by the qualified declaration context name. The tree can't be modified while + /// iterating over it, so we collect all the typedefs and add them at the end. + /// + private readonly Dictionary> allTypedefs = new Dictionary>(); + + public override bool VisitLibrary(ASTContext context) + { + bool result = base.VisitLibrary(context); + + foreach (var typedef in allTypedefs) + { + foreach (var foo in typedef.Value) + { + foo.Context.Declarations.Add(foo.Declaration); + } + } + allTypedefs.Clear(); + + return result; + } + + public override bool VisitFunctionDecl(Function function) + { + if (!base.VisitFunctionDecl(function)) + return false; + + function.ReturnType = CheckType(function.Namespace, function.ReturnType); + foreach (var parameter in function.Parameters) + { + parameter.QualifiedType = CheckType(function.Namespace, parameter.QualifiedType); + } + return true; + } + + /// + /// Generates a new typedef for the given type if necessary and returns the new type. + /// + /// The namespace the typedef will be added to. + /// The type to check. + /// The new type. + private QualifiedType CheckType(DeclarationContext @namespace, QualifiedType type) + { + var pointerType = type.Type as PointerType; + if (pointerType == null) + return type; + + var functionType = pointerType.Pointee as FunctionType; + if (functionType == null) + return type; + + List typedefs; + if (!allTypedefs.TryGetValue(@namespace.QualifiedName, out typedefs)) + { + typedefs = new List(); + allTypedefs.Add(@namespace.QualifiedName, typedefs); + } + + var typedef = FindMatchingTypedef(typedefs, functionType); + if (typedef == null) + { + for (int i = 0; i < functionType.Parameters.Count; i++) + { + functionType.Parameters[i].Name = string.Format("_{0}", i); + } + + typedef = new TypedefDecl + { + Access = AccessSpecifier.Public, + Name = string.Format("__AnonymousDelegate{0}", typedefs.Count), + Namespace = @namespace, + QualifiedType = type, + IsSynthetized = true + }; + typedefs.Add(new Typedef + { + Context = @namespace, + Declaration = typedef + }); + } + + var typedefType = new TypedefType + { + Declaration = typedef + }; + return new QualifiedType(typedefType); + } + + /// + /// Finds a typedef with the same return type and parameter types. + /// + /// The typedef list to search. + /// The function to match. + /// The matching typedef, or null if not found. + private TypedefDecl FindMatchingTypedef(List typedefs, FunctionType functionType) + { + return (from typedef in typedefs + let type = (FunctionType)typedef.Declaration.Type.GetPointee() + where type.ReturnType == functionType.ReturnType && + type.Parameters.SequenceEqual(functionType.Parameters, new ParameterTypeComparer()) + select typedef.Declaration).SingleOrDefault(); + } + } +} diff --git a/tests/Basic/AnotherUnit.cpp b/tests/Basic/AnotherUnit.cpp new file mode 100644 index 00000000..0681a0e4 --- /dev/null +++ b/tests/Basic/AnotherUnit.cpp @@ -0,0 +1,9 @@ +#include "AnotherUnit.h" + +void DelegateNamespace::Nested::f3(void (*)()) +{ +} + +void DelegateNamespace::f4(void (*)()) +{ +} diff --git a/tests/Basic/AnotherUnit.h b/tests/Basic/AnotherUnit.h new file mode 100644 index 00000000..5b5da5ad --- /dev/null +++ b/tests/Basic/AnotherUnit.h @@ -0,0 +1,15 @@ +#include "../Tests.h" + +// Verifies the header is included when the delegate is defined in a different file +typedef void (*DelegateInAnotherUnit)(); + +// Tests automatic generation of anonymous delegates in different translation units +namespace DelegateNamespace +{ + namespace Nested + { + void DLL_API f3(void (*)()); + } + + void DLL_API f4(void (*)()); +} diff --git a/tests/Basic/Basic.Tests.cs b/tests/Basic/Basic.Tests.cs index 8a3b1186..f0ec8a49 100644 --- a/tests/Basic/Basic.Tests.cs +++ b/tests/Basic/Basic.Tests.cs @@ -1,8 +1,8 @@ using System; using CppSharp.Utils; using NUnit.Framework; -using Basic; -using Enum = Basic.Enum; +using BasicTest; +using Enum = BasicTest.Enum; public class BasicTests : GeneratorTestFixture { @@ -349,7 +349,7 @@ public class BasicTests : GeneratorTestFixture [Test] public void TestFunctions() { - var ret = Basic.basic.Function(); + var ret = BasicTest.basic.Function(); Assert.That(ret, Is.EqualTo(5)); } @@ -443,6 +443,23 @@ public class BasicTests : GeneratorTestFixture new TestDelegates().MarshalUnattributedDelegate(i => i); } + [Test] + public void TestPassAnonymousDelegate() + { + var testDelegates = new TestDelegates(); + int value = testDelegates.MarshalAnonymousDelegate(i => i * 2); + Assert.AreEqual(2, value); + } + + [Test] + public void TestGetAnonymousDelegate() + { + var testDelegates = new TestDelegates(); + var @delegate = testDelegates.MarshalAnonymousDelegate4(); + int value = @delegate.Invoke(1); + Assert.AreEqual(2, value); + } + [Test] public void TestFixedArrays() { diff --git a/tests/Basic/Basic.cpp b/tests/Basic/Basic.cpp index 4e2e8e12..25c99f22 100644 --- a/tests/Basic/Basic.cpp +++ b/tests/Basic/Basic.cpp @@ -323,6 +323,41 @@ void TestDelegates::MarshalUnattributedDelegate(DelegateInGlobalNamespace del) { } +int TestDelegates::MarshalAnonymousDelegate(int (*del)(int n)) +{ + return del(1); +} + +void TestDelegates::MarshalAnonymousDelegate2(int (*del)(int n)) +{ +} + +void TestDelegates::MarshalAnonymousDelegate3(float (*del)(float n)) +{ +} + +int f(int n) +{ + return n * 2; +} + +int (*TestDelegates::MarshalAnonymousDelegate4())(int n) +{ + return f; +} + +void DelegateNamespace::Nested::f1(void (*)()) +{ +} + +void TestDelegates::MarshalDelegateInAnotherUnit(DelegateInAnotherUnit del) +{ +} + +void DelegateNamespace::f2(void (*)()) +{ +} + std::string HasStdString::testStdString(std::string s) { return s + "_test"; diff --git a/tests/Basic/Basic.cs b/tests/Basic/Basic.cs index f1d9b0af..d52d8290 100644 --- a/tests/Basic/Basic.cs +++ b/tests/Basic/Basic.cs @@ -13,6 +13,13 @@ namespace CppSharp.Tests } + public override void Setup(Driver driver) + { + base.Setup(driver); + + driver.Options.OutputNamespace = "BasicTest"; + } + public override void SetupPasses(Driver driver) { if (driver.Options.IsCSharpGenerator) diff --git a/tests/Basic/Basic.h b/tests/Basic/Basic.h index 2318d3b0..488dd2c5 100644 --- a/tests/Basic/Basic.h +++ b/tests/Basic/Basic.h @@ -1,4 +1,5 @@ #include "../Tests.h" +#include "AnotherUnit.h" #ifdef _WIN32 #include @@ -308,6 +309,13 @@ struct DLL_API TestDelegates int CDecl(DelegateCDecl del) { return del(1); } void MarshalUnattributedDelegate(DelegateInGlobalNamespace del); + int MarshalAnonymousDelegate(int (*del)(int n)); + void MarshalAnonymousDelegate2(int (*del)(int n)); + void MarshalAnonymousDelegate3(float (*del)(float n)); + int (*MarshalAnonymousDelegate4())(int n); + + void MarshalDelegateInAnotherUnit(DelegateInAnotherUnit del); + DelegateInClass A; DelegateInGlobalNamespace B; // As long as we can't marshal them make sure they're ignored @@ -318,6 +326,16 @@ TestDelegates::TestDelegates() : A(Double), B(Double), C(&TestDelegates::Triple) { } +namespace DelegateNamespace +{ + namespace Nested + { + void DLL_API f1(void (*)()); + } + + void DLL_API f2(void (*)()); +} + // Tests memory leaks in constructors // C#: Marshal.FreeHGlobal(arg0); struct DLL_API TestMemoryLeaks diff --git a/tests/Basic/premake4.lua b/tests/Basic/premake4.lua index 6ddb9e42..31a11fd6 100644 --- a/tests/Basic/premake4.lua +++ b/tests/Basic/premake4.lua @@ -1,2 +1,2 @@ group "Tests/Basic" - SetupTestProject("Basic") \ No newline at end of file + SetupTestProject("Basic", { "AnotherUnit" }) \ No newline at end of file