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/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/Passes/GenerateAnonymousDelegatesPass.cs b/src/Generator/Passes/GenerateAnonymousDelegatesPass.cs new file mode 100644 index 00000000..9961bdb5 --- /dev/null +++ b/src/Generator/Passes/GenerateAnonymousDelegatesPass.cs @@ -0,0 +1,112 @@ +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 + { + /// + /// The generated typedefs. 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 VisitTranslationUnit(TranslationUnit unit) + { + bool result = base.VisitTranslationUnit(unit); + + foreach (var typedef in allTypedefs) + typedef.Key.Declarations.AddRange(typedef.Value); + 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, out typedefs)) + { + typedefs = new List(); + allTypedefs.Add(@namespace, 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(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.Type.GetPointee() + where type.ReturnType == functionType.ReturnType && + type.Parameters.SequenceEqual(functionType.Parameters, new ParameterTypeComparer()) + select typedef).SingleOrDefault(); + } + } +} diff --git a/tests/Basic/Basic.Tests.cs b/tests/Basic/Basic.Tests.cs index 8a3b1186..c2fa784d 100644 --- a/tests/Basic/Basic.Tests.cs +++ b/tests/Basic/Basic.Tests.cs @@ -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..a0fd0735 100644 --- a/tests/Basic/Basic.cpp +++ b/tests/Basic/Basic.cpp @@ -323,6 +323,29 @@ 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; +} + std::string HasStdString::testStdString(std::string s) { return s + "_test"; diff --git a/tests/Basic/Basic.h b/tests/Basic/Basic.h index 2318d3b0..0835a0d5 100644 --- a/tests/Basic/Basic.h +++ b/tests/Basic/Basic.h @@ -308,6 +308,11 @@ 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); + DelegateInClass A; DelegateInGlobalNamespace B; // As long as we can't marshal them make sure they're ignored