Browse Source

Merge pull request #440 from spencercw/anonymous_delegates

Generate delegates for non-typedef'd function pointers to prevent runtime exceptions during marshalling
pull/441/head
João Matos 11 years ago
parent
commit
d0f4962c62
  1. 23
      build/Tests.lua
  2. 1
      src/AST/Declaration.cs
  3. 10
      src/Generator/AST/ASTRecord.cs
  4. 1
      src/Generator/Driver.cs
  5. 12
      src/Generator/Generators/CLI/CLIMarshal.cs
  6. 2
      src/Generator/Generators/CLI/CLITypeReferences.cs
  7. 126
      src/Generator/Passes/GenerateAnonymousDelegatesPass.cs
  8. 9
      tests/Basic/AnotherUnit.cpp
  9. 15
      tests/Basic/AnotherUnit.h
  10. 23
      tests/Basic/Basic.Tests.cs
  11. 35
      tests/Basic/Basic.cpp
  12. 7
      tests/Basic/Basic.cs
  13. 18
      tests/Basic/Basic.h
  14. 2
      tests/Basic/premake4.lua

23
build/Tests.lua

@ -12,11 +12,11 @@ function SetupExampleProject() @@ -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() @@ -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) @@ -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) @@ -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) @@ -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" }

1
src/AST/Declaration.cs

@ -360,6 +360,7 @@ namespace CppSharp.AST @@ -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<T>(IDeclVisitor<T> visitor)
{

10
src/Generator/AST/ASTRecord.cs

@ -149,6 +149,12 @@ namespace CppSharp.Generators.AST @@ -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 @@ -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);

1
src/Generator/Driver.cs

@ -253,6 +253,7 @@ namespace CppSharp @@ -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());

12
src/Generator/Generators/CLI/CLIMarshal.cs

@ -586,7 +586,17 @@ namespace CppSharp.Generators.CLI @@ -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;
}

2
src/Generator/Generators/CLI/CLITypeReferences.cs

@ -172,7 +172,7 @@ namespace CppSharp.Generators.CLI @@ -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)

126
src/Generator/Passes/GenerateAnonymousDelegatesPass.cs

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Linq;
using CppSharp.AST;
using CppSharp.AST.Extensions;
namespace CppSharp.Passes
{
/// <summary>
/// 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.
/// </summary>
public class GenerateAnonymousDelegatesPass : TranslationUnitPass
{
private struct Typedef
{
public DeclarationContext Context;
public TypedefDecl Declaration;
}
/// <summary>
/// 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.
/// </summary>
private readonly Dictionary<string, List<Typedef>> allTypedefs = new Dictionary<string, List<Typedef>>();
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;
}
/// <summary>
/// Generates a new typedef for the given type if necessary and returns the new type.
/// </summary>
/// <param name="namespace">The namespace the typedef will be added to.</param>
/// <param name="type">The type to check.</param>
/// <returns>The new type.</returns>
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<Typedef> typedefs;
if (!allTypedefs.TryGetValue(@namespace.QualifiedName, out typedefs))
{
typedefs = new List<Typedef>();
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);
}
/// <summary>
/// Finds a typedef with the same return type and parameter types.
/// </summary>
/// <param name="typedefs">The typedef list to search.</param>
/// <param name="functionType">The function to match.</param>
/// <returns>The matching typedef, or null if not found.</returns>
private TypedefDecl FindMatchingTypedef(List<Typedef> 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();
}
}
}

9
tests/Basic/AnotherUnit.cpp

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
#include "AnotherUnit.h"
void DelegateNamespace::Nested::f3(void (*)())
{
}
void DelegateNamespace::f4(void (*)())
{
}

15
tests/Basic/AnotherUnit.h

@ -0,0 +1,15 @@ @@ -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 (*)());
}

23
tests/Basic/Basic.Tests.cs

@ -1,8 +1,8 @@ @@ -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 @@ -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 @@ -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()
{

35
tests/Basic/Basic.cpp

@ -323,6 +323,41 @@ void TestDelegates::MarshalUnattributedDelegate(DelegateInGlobalNamespace del) @@ -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";

7
tests/Basic/Basic.cs

@ -13,6 +13,13 @@ namespace CppSharp.Tests @@ -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)

18
tests/Basic/Basic.h

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
#include "../Tests.h"
#include "AnotherUnit.h"
#ifdef _WIN32
#include <vadefs.h>
@ -308,6 +309,13 @@ struct DLL_API TestDelegates @@ -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) @@ -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

2
tests/Basic/premake4.lua

@ -1,2 +1,2 @@ @@ -1,2 +1,2 @@
group "Tests/Basic"
SetupTestProject("Basic")
SetupTestProject("Basic", { "AnotherUnit" })
Loading…
Cancel
Save