diff --git a/AdvanceDLSupport.sln.DotSettings b/AdvanceDLSupport.sln.DotSettings
index 599257f2..d322541f 100644
--- a/AdvanceDLSupport.sln.DotSettings
+++ b/AdvanceDLSupport.sln.DotSettings
@@ -31,4 +31,5 @@
True
True
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/AdvancedDLSupport.Tests/Data/Interfaces/IGenericDelegateLibrary.cs b/AdvancedDLSupport.Tests/Data/Interfaces/IGenericDelegateLibrary.cs
new file mode 100644
index 00000000..d6a4c676
--- /dev/null
+++ b/AdvancedDLSupport.Tests/Data/Interfaces/IGenericDelegateLibrary.cs
@@ -0,0 +1,52 @@
+//
+// IGenericDelegateLibrary.cs
+//
+// Copyright (c) 2018 Firwood Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+using System;
+
+#pragma warning disable SA1600, CS1591
+
+namespace AdvancedDLSupport.Tests.Data
+{
+ public interface IGenericDelegateLibrary
+ {
+ void ExecuteAction(Action action);
+
+ void ExecuteActionT1(Action action);
+
+ void ExecuteActionT1Nested(Action> action);
+
+ int ExecuteFuncT1(Func func);
+
+ int ExecuteFuncT1T2(Func func);
+
+ int ExecuteFuncT1T2Nested(Func, int> func);
+
+ Action GetNativeAction();
+
+ Action GetNativeActionT1();
+
+ Action> GetNativeActionT1Nested();
+
+ Func GetNativeFuncT1();
+
+ Func GetNativeFuncT1T2();
+
+ Func, int> GetNativeFuncT1T2Nested();
+ }
+}
diff --git a/AdvancedDLSupport.Tests/Tests/Integration/GenericDelegateTests.cs b/AdvancedDLSupport.Tests/Tests/Integration/GenericDelegateTests.cs
new file mode 100644
index 00000000..8de01ba9
--- /dev/null
+++ b/AdvancedDLSupport.Tests/Tests/Integration/GenericDelegateTests.cs
@@ -0,0 +1,170 @@
+//
+// GenericDelegateTests.cs
+//
+// Copyright (c) 2018 Firwood Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+using System;
+using AdvancedDLSupport.Tests.Data;
+using AdvancedDLSupport.Tests.TestBases;
+using Xunit;
+
+#pragma warning disable SA1600, CS1591
+
+namespace AdvancedDLSupport.Tests.Integration
+{
+ public class GenericDelegateTests
+ {
+ private const string LibraryName = "GenericDelegateTests";
+
+ public class FromManagedToNative : LibraryTestBase
+ {
+ public FromManagedToNative()
+ : base(LibraryName)
+ {
+ }
+
+ [Fact]
+ public void NativeCanCallAction()
+ {
+ bool ranAction = false;
+ Library.ExecuteAction(() => ranAction = true);
+
+ Assert.True(ranAction);
+ }
+
+ [Fact]
+ public void NativeCanCallActionWithParameter()
+ {
+ bool ranAction = false;
+ int result = 0;
+ Library.ExecuteActionT1(x =>
+ {
+ ranAction = true;
+ result = x;
+ });
+
+ Assert.True(ranAction);
+ Assert.Equal(5, result);
+ }
+
+ [Fact]
+ public void NativeCanCallFunc()
+ {
+ var result = Library.ExecuteFuncT1(() => 5);
+
+ Assert.Equal(5, result);
+ }
+
+ [Fact]
+ public void NativeCanCallFuncWithParameter()
+ {
+ var result = Library.ExecuteFuncT1T2(x => 5 * x);
+
+ Assert.Equal(25, result);
+ }
+
+ [Fact(Skip = "Not working due to CLR limitations.")]
+ public void NativeCanCallNestedAction()
+ {
+ bool ranAction = false;
+ Library.ExecuteActionT1Nested
+ (
+ action =>
+ {
+ ranAction = true;
+ action(5);
+ }
+ );
+
+ Assert.True(ranAction);
+ }
+
+ [Fact(Skip = "Not working due to CLR limitations.")]
+ public void NativeCanCallNestedFunc()
+ {
+ var result = Library.ExecuteFuncT1T2Nested
+ (
+ func => func(5)
+ );
+
+ Assert.Equal(25, result);
+ }
+ }
+
+ public class FromNativeToManaged : LibraryTestBase
+ {
+ public FromNativeToManaged()
+ : base(LibraryName)
+ {
+ }
+
+ [Fact]
+ public void ManagedCanCallAction()
+ {
+ var action = Library.GetNativeAction();
+ action();
+ }
+
+ [Fact]
+ public void ManagedCanCallActionWithParameter()
+ {
+ var action = Library.GetNativeActionT1();
+ action(5);
+ }
+
+ [Fact]
+ public void ManagedCanCallFunc()
+ {
+ var func = Library.GetNativeFuncT1();
+ var result = func();
+
+ Assert.Equal(5, result);
+ }
+
+ [Fact]
+ public void ManagedCanCallFuncWithParameter()
+ {
+ var func = Library.GetNativeFuncT1T2();
+ var result = func(5);
+
+ Assert.Equal(25, result);
+ }
+
+ [Fact(Skip = "Not working due to CLR limitations.")]
+ public void ManagedCanCallNestedAction()
+ {
+ var action = Library.GetNativeActionT1Nested();
+
+ int result = 0;
+ action(i => result = i);
+
+ Assert.Equal(5, result);
+ }
+
+ [Fact(Skip = "Not working due to CLR limitations.")]
+ public void ManagedCanCallNestedFunc()
+ {
+ var func = Library.GetNativeFuncT1T2Nested();
+
+ int result = 0;
+ func(i => result = i * 5);
+
+ Assert.Equal(25, result);
+ }
+ }
+ }
+}
diff --git a/AdvancedDLSupport.Tests/c/CMake/install-functions.cmake b/AdvancedDLSupport.Tests/c/CMake/install-functions.cmake
index f27f9c4d..8f08fb27 100644
--- a/AdvancedDLSupport.Tests/c/CMake/install-functions.cmake
+++ b/AdvancedDLSupport.Tests/c/CMake/install-functions.cmake
@@ -16,6 +16,7 @@ function(install_for_frameworks FRAMEWORKS)
IndirectCallTests
NameManglingTests
SymbolTransformationTests
+ GenericDelegateTests
COMPONENT
standard
DESTINATION
diff --git a/AdvancedDLSupport.Tests/c/CMakeLists.txt b/AdvancedDLSupport.Tests/c/CMakeLists.txt
index 18a42b56..f802f352 100644
--- a/AdvancedDLSupport.Tests/c/CMakeLists.txt
+++ b/AdvancedDLSupport.Tests/c/CMakeLists.txt
@@ -46,6 +46,7 @@ add_library(NullableTests SHARED src/NullableTests.c ${SHARED_HEADERS})
add_library(IndirectCallTests SHARED src/IndirectCallTests.c ${SHARED_HEADERS})
add_library(NameManglingTests SHARED src/NameManglingTests.c ${SHARED_HEADERS})
add_library(SymbolTransformationTests SHARED src/SymbolTransformationTests.c ${SHARED_HEADERS} src/SymbolTransformationTests.c)
+add_library(GenericDelegateTests SHARED src/GenericDelegateTests.c ${SHARED_HEADERS} src/GenericDelegateTests.c)
if (IS_UNIX_COMPILER)
# Any CPU is assumed to be 64-bit
@@ -64,6 +65,7 @@ if (IS_UNIX_COMPILER)
set_target_properties(IndirectCallTests PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
set_target_properties(NameManglingTests PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
set_target_properties(SymbolTransformationTests PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
+ set_target_properties(GenericDelegateTests PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
elseif (BUILD_PLATFORM STREQUAL "x86")
set_target_properties(BaseTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
set_target_properties(DisposeTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
@@ -79,6 +81,7 @@ if (IS_UNIX_COMPILER)
set_target_properties(IndirectCallTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
set_target_properties(NameManglingTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
set_target_properties(SymbolTransformationTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
+ set_target_properties(GenericDelegateTests PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32")
endif()
set_target_properties(BaseTests-x64 PROPERTIES COMPILE_FLAGS "-m64" LINK_FLAGS "-m64")
diff --git a/AdvancedDLSupport.Tests/c/src/GenericDelegateTests.c b/AdvancedDLSupport.Tests/c/src/GenericDelegateTests.c
new file mode 100644
index 00000000..98905098
--- /dev/null
+++ b/AdvancedDLSupport.Tests/c/src/GenericDelegateTests.c
@@ -0,0 +1,118 @@
+//
+// GenericDelegateTests.c
+//
+// Author:
+// Jarl Gullberg
+//
+// Copyright (c) 2018 Jarl Gullberg
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+#include
+#include
+#include "comp.h"
+
+typedef void (*Action)();
+typedef void (*ActionT1)(int t1);
+typedef void (*ActionT1Nested)(ActionT1 action);
+
+typedef int (*FuncT1)();
+typedef int (*FuncT1T2)(int t2);
+typedef int (*FuncT1T2Nested)(FuncT1T2 func);
+
+void NativeActionT1(int t1);
+
+int NativeFuncT1T2(int t2);
+
+__declspec(dllexport) void ExecuteAction(Action action)
+{
+ action();
+}
+
+__declspec(dllexport) void ExecuteActionT1(ActionT1 action)
+{
+ action(5);
+}
+
+__declspec(dllexport) void ExecuteActionT1Nested(ActionT1Nested action)
+{
+ fprintf(stdout, "In nested, seeing function pointer as %x", (unsigned int)&NativeActionT1);
+ action(&NativeActionT1);
+}
+
+__declspec(dllexport) int ExecuteFuncT1(FuncT1 func)
+{
+ return func();
+}
+
+__declspec(dllexport) int ExecuteFuncT1T2(FuncT1T2 func)
+{
+ return func(5);
+}
+
+__declspec(dllexport) int ExecuteFuncT1T2Nested(FuncT1T2Nested func)
+{
+ return func(&NativeFuncT1T2);
+}
+
+__declspec(dllexport) void NativeAction()
+{
+ fprintf(stdout, "Living in native land!");
+}
+
+__declspec(dllexport) void NativeActionT1(int t1)
+{
+ fprintf(stdout, "Living in native land, seeing parameter as %d!", t1);
+}
+
+__declspec(dllexport) Action GetNativeAction()
+{
+ return &NativeAction;
+}
+
+__declspec(dllexport) ActionT1 GetNativeActionT1()
+{
+ return &NativeActionT1;
+}
+
+__declspec(dllexport) ActionT1Nested GetNativeActionT1Nested()
+{
+ return &ExecuteActionT1;
+}
+
+__declspec(dllexport) int NativeFuncT1()
+{
+ return 5;
+}
+
+__declspec(dllexport) FuncT1 GetNativeFuncT1()
+{
+ return &NativeFuncT1;
+}
+
+__declspec(dllexport) int NativeFuncT1T2(int t2)
+{
+ return t2 * 5;
+}
+
+__declspec(dllexport) FuncT1T2 GetNativeFuncT1T2()
+{
+ return &NativeFuncT1T2;
+}
+
+__declspec(dllexport) FuncT1T2Nested GetNativeFuncT1T2Nested()
+{
+ return &ExecuteFuncT1T2;
+}
\ No newline at end of file
diff --git a/AdvancedDLSupport/AdvancedDLSupport.ExternalAnnotations.xml b/AdvancedDLSupport/AdvancedDLSupport.ExternalAnnotations.xml
index f8646572..3b0fb1af 100644
--- a/AdvancedDLSupport/AdvancedDLSupport.ExternalAnnotations.xml
+++ b/AdvancedDLSupport/AdvancedDLSupport.ExternalAnnotations.xml
@@ -36,6 +36,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -84,12 +111,23 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AdvancedDLSupport/AdvancedDLSupport.csproj b/AdvancedDLSupport/AdvancedDLSupport.csproj
index e2053c79..25e1ba08 100644
--- a/AdvancedDLSupport/AdvancedDLSupport.csproj
+++ b/AdvancedDLSupport/AdvancedDLSupport.csproj
@@ -12,7 +12,7 @@
AdvancedDLSupport
TheBlackCentipede;Jax
- 2.0.0
+ 2.1.0
An alternative approach to your typical P/Invoke.
Copyright Firwood Software 2017
@@ -20,7 +20,7 @@
AdvancedDLSupport
https://www.gnu.org/licenses/lgpl-3.0.txt
true
- Add support for AOT compilation of native binding types.
+ Implement generic delegate marshalling support.
p/invoke;cross-platform;mono;netcore;netstandard;native;interop
../nuget
True
@@ -63,4 +63,4 @@
-
\ No newline at end of file
+
diff --git a/AdvancedDLSupport/Extensions/ModuleBuilderExtensions.cs b/AdvancedDLSupport/Extensions/ModuleBuilderExtensions.cs
new file mode 100644
index 00000000..b746e9ad
--- /dev/null
+++ b/AdvancedDLSupport/Extensions/ModuleBuilderExtensions.cs
@@ -0,0 +1,208 @@
+//
+// ModuleBuilderExtensions.cs
+//
+// Copyright (c) 2018 Firwood Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using System.Security;
+using AdvancedDLSupport.Reflection;
+using JetBrains.Annotations;
+
+// ReSharper disable BitwiseOperatorOnEnumWithoutFlags
+namespace AdvancedDLSupport.Extensions
+{
+ ///
+ /// Extensions methods for the class.
+ ///
+ public static class ModuleBuilderExtensions
+ {
+ ///
+ /// Defines a delegate type in the given module with the given name and parameters.
+ ///
+ /// The module to define the delegate in.
+ /// The name of the delegate type.
+ /// The base member to take parameter types from.
+ /// Whether or not code security should be suppressed on the delegate.
+ /// The delegate type.
+ [NotNull]
+ public static TypeBuilder DefineDelegate
+ (
+ [NotNull] this ModuleBuilder module,
+ [NotNull] string name,
+ [NotNull] IntrospectiveMethodInfo baseMember,
+ bool suppressSecurity = false
+ )
+ {
+ var metadataAttribute = baseMember.GetCustomAttribute() ??
+ new NativeSymbolAttribute(baseMember.Name);
+
+ var delegateBuilder = DefineDelegateType
+ (
+ module,
+ name,
+ metadataAttribute.CallingConvention,
+ suppressSecurity
+ );
+
+ foreach (var attribute in baseMember.CustomAttributes)
+ {
+ delegateBuilder.SetCustomAttribute(attribute.GetAttributeBuilder());
+ }
+
+ var delegateInvocationBuilder = DefineDelegateInvocationMethod
+ (delegateBuilder, baseMember.ReturnType, baseMember.ParameterTypes.ToArray());
+
+ delegateInvocationBuilder.ApplyCustomAttributesFrom(baseMember);
+
+ return delegateBuilder;
+ }
+
+ ///
+ /// Defines a delegate type in the given module with the given name and parameters.
+ ///
+ /// The module to define the delegate in.
+ /// The name of the delegate type.
+ /// The unmanaged calling convention to use.
+ /// The return type of the delegate.
+ /// The parameter types of the delegate.
+ /// Whether or not code security should be suppressed on the delegate.
+ /// The delegate type.
+ [NotNull]
+ public static TypeBuilder DefineDelegate
+ (
+ [NotNull] this ModuleBuilder module,
+ [NotNull] string name,
+ CallingConvention callingConvention,
+ [NotNull] Type returnType,
+ [NotNull] Type[] parameterTypes,
+ bool suppressSecurity = false
+ )
+ {
+ var delegateBuilder = DefineDelegateType
+ (
+ module,
+ name,
+ callingConvention,
+ suppressSecurity
+ );
+
+ DefineDelegateInvocationMethod(delegateBuilder, returnType, parameterTypes);
+
+ return delegateBuilder;
+ }
+
+ ///
+ /// Defines a delegate type in the given module with the given name and parameters.
+ ///
+ /// The module to define the delegate in.
+ /// The name of the delegate type.
+ /// The unmanaged calling convention to use.
+ /// Whether or not code security should be suppressed on the delegate.
+ /// The delegate type.
+ [NotNull]
+ private static TypeBuilder DefineDelegateType
+ (
+ [NotNull] ModuleBuilder module,
+ [NotNull] string name,
+ CallingConvention callingConvention,
+ bool suppressSecurity = false)
+ {
+ var delegateBuilder = module.DefineType
+ (
+ name,
+ TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.AutoClass,
+ typeof(MulticastDelegate)
+ );
+
+ var unmanagedPtrAttributeConstructor = typeof(UnmanagedFunctionPointerAttribute).GetConstructors().First
+ (
+ c =>
+ c.GetParameters().Any() &&
+ c.GetParameters().Length == 1 &&
+ c.GetParameters().First().ParameterType == typeof(CallingConvention)
+ );
+
+ var setLastErrorField = typeof(UnmanagedFunctionPointerAttribute)
+ .GetField(nameof(UnmanagedFunctionPointerAttribute.SetLastError));
+
+ var functionPointerAttributeBuilder = new CustomAttributeBuilder
+ (
+ unmanagedPtrAttributeConstructor,
+ new object[] { callingConvention },
+ new[] { setLastErrorField },
+ new object[] { true }
+ );
+
+ delegateBuilder.SetCustomAttribute(functionPointerAttributeBuilder);
+
+ if (suppressSecurity)
+ {
+ var suppressSecurityConstructor = typeof(SuppressUnmanagedCodeSecurityAttribute).GetConstructors().First();
+
+ var suppressSecurityAttributeBuilder = new CustomAttributeBuilder
+ (
+ suppressSecurityConstructor,
+ new object[] { }
+ );
+
+ delegateBuilder.SetCustomAttribute(suppressSecurityAttributeBuilder);
+ }
+
+ var delegateCtorBuilder = delegateBuilder.DefineConstructor
+ (
+ MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public,
+ CallingConventions.Standard,
+ new[] { typeof(object), typeof(IntPtr) }
+ );
+
+ delegateCtorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
+
+ return delegateBuilder;
+ }
+
+ ///
+ /// Defines a delegate invocation method on a delegate type.
+ ///
+ /// The delegate type builder.
+ /// The return type of the method.
+ /// The parameter types of the method.
+ /// The delegate invocation method.
+ [NotNull]
+ private static MethodBuilder DefineDelegateInvocationMethod
+ (
+ [NotNull] TypeBuilder delegateBuilder,
+ [NotNull] Type returnType,
+ [NotNull] Type[] parameterTypes
+ )
+ {
+ var delegateMethodBuilder = delegateBuilder.DefineMethod
+ (
+ "Invoke",
+ MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual,
+ returnType,
+ parameterTypes
+ );
+
+ delegateMethodBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
+ return delegateMethodBuilder;
+ }
+ }
+}
diff --git a/AdvancedDLSupport/Extensions/TypeExtensions.cs b/AdvancedDLSupport/Extensions/TypeExtensions.cs
index 166ca2e0..d965aaf6 100644
--- a/AdvancedDLSupport/Extensions/TypeExtensions.cs
+++ b/AdvancedDLSupport/Extensions/TypeExtensions.cs
@@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using AdvancedDLSupport.Reflection;
using JetBrains.Annotations;
@@ -30,6 +31,77 @@ namespace AdvancedDLSupport.Extensions
///
internal static class TypeExtensions
{
+ ///
+ /// Determines whether the given type is a generic delegate type - that is, a or
+ /// .
+ ///
+ /// The type.
+ /// true if the type is a generic delegate type; Otherwise, false.
+ public static bool IsGenericDelegate([NotNull] this Type @this)
+ {
+ // The parameterless action is technically not a generic type, so it'll get caught here
+ if (!@this.IsGenericType)
+ {
+ return false;
+ }
+
+ var genericType = @this.GetGenericTypeDefinition();
+ if (genericType.FullName is null)
+ {
+ throw new InvalidOperationException("Couldn't get the full name of the given type.");
+ }
+
+ if (genericType.IsGenericFuncDelegate())
+ {
+ return true;
+ }
+
+ if (genericType.IsGenericActionDelegate())
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Determines whether or not the given type is a generic delegate.
+ ///
+ /// The type.
+ /// true if the type is an action delegate; otherwise, false.
+ public static bool IsGenericActionDelegate([NotNull] this Type @this)
+ {
+ // ReSharper disable once PossibleNullReferenceException
+ var genericBaseName = @this.FullName.Split('`').First();
+
+ if (genericBaseName == typeof(Action).FullName)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Determines whether or not the given type is a generic delegate.
+ ///
+ /// The type.
+ /// true if the type is a func delegate; otherwise, false.
+ public static bool IsGenericFuncDelegate([NotNull] this Type @this)
+ {
+ // ReSharper disable once PossibleNullReferenceException
+ var genericBaseName = @this.FullName.Split('`').First();
+
+ // ReSharper disable once PossibleNullReferenceException
+ var funcBaseName = typeof(Func<>).FullName.Split('`').First();
+ if (genericBaseName == funcBaseName)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Gets the methods defined in the given type as wrapped introspective methods.
///
diff --git a/AdvancedDLSupport/ImplementationGenerators/Complexity/GeneratorComplexity.cs b/AdvancedDLSupport/ImplementationGenerators/Complexity/GeneratorComplexity.cs
index bea4f5ea..7ad2f279 100644
--- a/AdvancedDLSupport/ImplementationGenerators/Complexity/GeneratorComplexity.cs
+++ b/AdvancedDLSupport/ImplementationGenerators/Complexity/GeneratorComplexity.cs
@@ -18,13 +18,14 @@
//
using System;
+using JetBrains.Annotations;
namespace AdvancedDLSupport.ImplementationGenerators
{
///
/// Represents levels of complexity in a generator.
///
- [Flags]
+ [PublicAPI, Flags]
public enum GeneratorComplexity
{
///
@@ -53,6 +54,11 @@ public enum GeneratorComplexity
/// The generator is a terminating generator, and will not produce any output definitions. These generators are
/// sorted apart from the normal generators, and are always executed last.
///
- Terminating = 1 << 3
+ Terminating = 1 << 3,
+
+ ///
+ /// The generator will create additional types in the assembly.
+ ///
+ CreatesTypes = 1 << 4
}
}
diff --git a/AdvancedDLSupport/ImplementationGenerators/Terminating/DelegateMethodImplementationGenerator.cs b/AdvancedDLSupport/ImplementationGenerators/Terminating/DelegateMethodImplementationGenerator.cs
index 8c371133..9bea72cc 100644
--- a/AdvancedDLSupport/ImplementationGenerators/Terminating/DelegateMethodImplementationGenerator.cs
+++ b/AdvancedDLSupport/ImplementationGenerators/Terminating/DelegateMethodImplementationGenerator.cs
@@ -38,7 +38,6 @@
using static System.Reflection.MethodAttributes;
using static System.Reflection.MethodImplAttributes;
-// ReSharper disable BitwiseOperatorOnEnumWithoutFlags
namespace AdvancedDLSupport.ImplementationGenerators
{
///
@@ -78,10 +77,7 @@ public override IEnumerable> GenerateI
{
var definition = workUnit.Definition;
- var metadataAttribute = definition.GetCustomAttribute() ??
- new NativeSymbolAttribute(definition.Name);
-
- var delegateBuilder = GenerateDelegateType(workUnit, metadataAttribute.CallingConvention);
+ var delegateBuilder = GenerateDelegateType(workUnit);
// Create a delegate field
var backingFieldType = delegateBuilder.CreateTypeInfo();
@@ -178,81 +174,23 @@ [NotNull] FieldInfo delegateField
/// Generates a delegate type for the given method.
///
/// The method to generate a delegate type for.
- /// The unmanaged calling convention of the delegate.
/// A delegate type.
[NotNull]
private TypeBuilder GenerateDelegateType
(
- [NotNull] PipelineWorkUnit workUnit,
- CallingConvention callingConvention
+ [NotNull] PipelineWorkUnit workUnit
)
{
var definition = workUnit.Definition;
// Declare a delegate type
- var delegateBuilder = TargetModule.DefineType
+ var delegateBuilder = TargetModule.DefineDelegate
(
$"{workUnit.GetUniqueBaseMemberName()}_delegate",
- TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AnsiClass | TypeAttributes.AutoClass,
- typeof(MulticastDelegate)
- );
-
- var unmanagedPtrAttributeConstructor = typeof(UnmanagedFunctionPointerAttribute).GetConstructors().First
- (
- c =>
- c.GetParameters().Any() &&
- c.GetParameters().Length == 1 &&
- c.GetParameters().First().ParameterType == typeof(CallingConvention)
- );
-
- var functionPointerAttributeBuilder = new CustomAttributeBuilder
- (
- unmanagedPtrAttributeConstructor,
- new object[] { callingConvention },
- new[] { typeof(UnmanagedFunctionPointerAttribute).GetField(nameof(UnmanagedFunctionPointerAttribute.SetLastError)) },
- new object[] { true }
- );
-
- delegateBuilder.SetCustomAttribute(functionPointerAttributeBuilder);
-
- if (Options.HasFlagFast(SuppressSecurity))
- {
- var suppressSecurityConstructor = typeof(SuppressUnmanagedCodeSecurityAttribute).GetConstructors().First();
-
- var suppressSecurityAttributeBuilder = new CustomAttributeBuilder
- (
- suppressSecurityConstructor,
- new object[] { }
- );
-
- delegateBuilder.SetCustomAttribute(suppressSecurityAttributeBuilder);
- }
-
- foreach (var attribute in definition.CustomAttributes)
- {
- delegateBuilder.SetCustomAttribute(attribute.GetAttributeBuilder());
- }
-
- var delegateCtorBuilder = delegateBuilder.DefineConstructor
- (
- RTSpecialName | HideBySig | Public,
- Standard,
- new[] { typeof(object), typeof(IntPtr) }
- );
-
- delegateCtorBuilder.SetImplementationFlags(Runtime | Managed);
-
- var delegateMethodBuilder = delegateBuilder.DefineMethod
- (
- "Invoke",
- Public | HideBySig | NewSlot | Virtual,
- definition.ReturnType,
- definition.ParameterTypes.ToArray()
+ definition,
+ Options.HasFlagFast(SuppressSecurity)
);
- delegateMethodBuilder.ApplyCustomAttributesFrom(definition);
-
- delegateMethodBuilder.SetImplementationFlags(Runtime | Managed);
return delegateBuilder;
}
}
diff --git a/AdvancedDLSupport/ImplementationGenerators/Wrappers/CallWrapperBase.cs b/AdvancedDLSupport/ImplementationGenerators/Wrappers/CallWrapperBase.cs
index 15c43e80..ec20a256 100644
--- a/AdvancedDLSupport/ImplementationGenerators/Wrappers/CallWrapperBase.cs
+++ b/AdvancedDLSupport/ImplementationGenerators/Wrappers/CallWrapperBase.cs
@@ -56,6 +56,19 @@ ImplementationOptions options
{
}
+ ///
+ /// Emits any additional types that a work unit requires. By default, this does nothing.
+ ///
+ /// The module to emit the types in.
+ /// The unit to generate the types from.
+ public virtual void EmitAdditionalTypes
+ (
+ [NotNull] ModuleBuilder module,
+ [NotNull] PipelineWorkUnit workUnit
+ )
+ {
+ }
+
///
public sealed override IEnumerable> GenerateImplementation
(
@@ -69,6 +82,7 @@ PipelineWorkUnit workUnit
throw new ArgumentNullException(nameof(workUnit), "Could not unwrap introspective method to method builder.");
}
+ EmitAdditionalTypes(TargetModule, workUnit);
var passthroughMethod = GeneratePassthroughDefinition(workUnit);
var il = builder.GetILGenerator();
diff --git a/AdvancedDLSupport/ImplementationGenerators/Wrappers/GenericDelegateWrapper.cs b/AdvancedDLSupport/ImplementationGenerators/Wrappers/GenericDelegateWrapper.cs
new file mode 100644
index 00000000..fa1d4342
--- /dev/null
+++ b/AdvancedDLSupport/ImplementationGenerators/Wrappers/GenericDelegateWrapper.cs
@@ -0,0 +1,351 @@
+//
+// GenericDelegateWrapper.cs
+//
+// Copyright (c) 2018 Firwood Software
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see .
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+using System.Text;
+using AdvancedDLSupport.Extensions;
+using AdvancedDLSupport.Pipeline;
+using AdvancedDLSupport.Reflection;
+using JetBrains.Annotations;
+using Mono.DllMap.Extensions;
+using StrictEmit;
+using static AdvancedDLSupport.ImplementationGenerators.GeneratorComplexity;
+
+namespace AdvancedDLSupport.ImplementationGenerators
+{
+ ///
+ /// Generates wrapper instructions for marshalling generic delegate types (,
+ /// and their variants.)
+ ///
+ internal sealed class GenericDelegateWrapper : CallWrapperBase
+ {
+ ///
+ public override GeneratorComplexity Complexity => MemberDependent | TransformsParameters | CreatesTypes;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The module where the implementation should be generated.
+ /// The type in which the implementation should be generated.
+ /// The IL generator for the target type's constructor.
+ /// The configuration object to use.
+ public GenericDelegateWrapper
+ (
+ [NotNull] ModuleBuilder targetModule,
+ [NotNull] TypeBuilder targetType,
+ [NotNull] ILGenerator targetTypeConstructorIL,
+ ImplementationOptions options
+ )
+ : base
+ (
+ targetModule,
+ targetType,
+ targetTypeConstructorIL,
+ options
+ )
+ {
+ }
+
+ ///
+ public override bool IsApplicable(IntrospectiveMethodInfo member)
+ {
+ if (member.ReturnType.IsGenericDelegate())
+ {
+ return true;
+ }
+
+ return member.ParameterTypes.Any(t => t.IsGenericDelegate());
+ }
+
+ ///
+ public override void EmitAdditionalTypes
+ (
+ ModuleBuilder module,
+ PipelineWorkUnit workUnit
+ )
+ {
+ var definition = workUnit.Definition;
+
+ foreach (var parameterType in definition.ParameterTypes.Concat(new[] { definition.ReturnType }))
+ {
+ if (!parameterType.IsGenericDelegate())
+ {
+ continue;
+ }
+
+ EmitExplicitDelegateDefinition(module, parameterType);
+ }
+ }
+
+ ///
+ /// Generates an explicit delegate definition based on a generic delegate type.
+ ///
+ /// The module to emit the type in.
+ /// The generic delegate type.
+ private TypeInfo EmitExplicitDelegateDefinition([NotNull] ModuleBuilder module, [NotNull] Type genericDelegateType)
+ {
+ var existingDelegate = GetCreatedExplicitDelegateType(genericDelegateType);
+ if (!(existingDelegate is null))
+ {
+ return existingDelegate.GetTypeInfo();
+ }
+
+ var signature = GetSignatureTypesFromGenericDelegate(genericDelegateType);
+
+ var delegateReturnType = signature.ReturnType;
+ if (delegateReturnType.IsGenericDelegate())
+ {
+ // This is a nested delegate, so we'll need to generate one for this one
+ delegateReturnType = EmitExplicitDelegateDefinition(module, signature.ReturnType);
+ }
+
+ var delegateParameters = new List();
+ foreach (var delegateParameter in signature.ParameterTypes)
+ {
+ if (!delegateParameter.IsGenericDelegate())
+ {
+ delegateParameters.Add(delegateParameter.GetTypeInfo());
+ continue;
+ }
+
+ // Also a nested delegate, so we'll need to generate one for this one too
+ var nestedDelegate = EmitExplicitDelegateDefinition(module, delegateParameter);
+ delegateParameters.Add(nestedDelegate);
+ }
+
+ if (delegateParameters.Any(p => p.IsGenericDelegate()))
+ {
+ // break
+ }
+
+ var delegateName = GetDelegateTypeName(delegateReturnType, delegateParameters);
+ var delegateDefinition = module.DefineDelegate
+ (
+ delegateName,
+ CallingConvention.Cdecl,
+ delegateReturnType,
+ delegateParameters.Cast().ToArray(),
+ Options.HasFlagFast(ImplementationOptions.SuppressSecurity)
+ );
+
+ return delegateDefinition.CreateTypeInfo();
+ }
+
+ ///
+ public override void EmitPrologue(ILGenerator il, PipelineWorkUnit workUnit)
+ {
+ var definition = workUnit.Definition;
+
+ // Load the "this" reference
+ il.EmitLoadArgument(0);
+
+ for (short i = 1; i <= definition.ParameterTypes.Count; ++i)
+ {
+ il.EmitLoadArgument(i);
+
+ var parameterType = definition.ParameterTypes[i - 1];
+ if (!parameterType.IsGenericDelegate())
+ {
+ continue;
+ }
+
+ // Convert the input generic delegate to an explicit delegate
+ var explicitDelegateType = GetCreatedExplicitDelegateType(parameterType);
+
+ if (explicitDelegateType is null)
+ {
+ throw new InvalidOperationException("No delegate type has been created for the given type.");
+ }
+
+ var explicitDelegateConstructor = explicitDelegateType.GetConstructors().First();
+ var invokeMethod = parameterType.GetMethod("Invoke");
+
+ il.EmitLoadFunctionPointer(invokeMethod);
+ il.EmitNewObject(explicitDelegateConstructor);
+ }
+ }
+
+ ///
+ public override void EmitEpilogue(ILGenerator il, PipelineWorkUnit workUnit)
+ {
+ // If the return type is a delegate, convert it back into its generic representation
+ var definition = workUnit.Definition;
+ var returnType = definition.ReturnType;
+
+ if (!returnType.IsGenericDelegate())
+ {
+ return;
+ }
+
+ // Convert the output explicit delegate to a generic delegate
+ var explicitDelegateType = GetCreatedExplicitDelegateType(returnType);
+
+ if (explicitDelegateType is null)
+ {
+ throw new InvalidOperationException("No delegate type has been created for the given type.");
+ }
+
+ var genericDelegateConstructor = returnType.GetConstructors().First();
+ var invokeMethod = explicitDelegateType.GetMethod("Invoke");
+
+ il.EmitLoadFunctionPointer(invokeMethod);
+ il.EmitNewObject(genericDelegateConstructor);
+ }
+
+ ///
+ public override IntrospectiveMethodInfo GeneratePassthroughDefinition
+ (
+ PipelineWorkUnit workUnit
+ )
+ {
+ var definition = workUnit.Definition;
+
+ var newReturnType = GetParameterPassthroughType(definition.ReturnType);
+ var newParameterTypes = definition.ParameterTypes.Select(GetParameterPassthroughType).ToArray();
+
+ var passthroughMethod = TargetType.DefineMethod
+ (
+ $"{workUnit.GetUniqueBaseMemberName()}_wrapped",
+ MethodAttributes.Private | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ CallingConventions.Standard,
+ newReturnType,
+ newParameterTypes
+ );
+
+ passthroughMethod.ApplyCustomAttributesFrom(definition, newReturnType, newParameterTypes);
+
+ return new IntrospectiveMethodInfo(passthroughMethod, newReturnType, newParameterTypes, definition);
+ }
+
+ ///
+ /// Gets the type that the parameter type should be passed through as.
+ ///
+ /// The original type.
+ /// The passed-through type.
+ [NotNull]
+ private Type GetParameterPassthroughType([NotNull] Type originalType)
+ {
+ if (originalType.IsGenericDelegate())
+ {
+ var explicitDelegateType = GetCreatedExplicitDelegateType(originalType);
+ if (explicitDelegateType is null)
+ {
+ throw new InvalidOperationException
+ (
+ "Could not find the generated delegate type."
+ );
+ }
+
+ return explicitDelegateType;
+ }
+
+ return originalType;
+ }
+
+ ///
+ /// Gets an already created explicit delegate type, based on the original generic delegate type.
+ ///
+ /// The generic type.
+ /// The explicitly implemented type.
+ [CanBeNull]
+ private Type GetCreatedExplicitDelegateType([NotNull] Type originalType)
+ {
+ var signature = GetSignatureTypesFromGenericDelegate(originalType);
+ var delegateName = GetDelegateTypeName(signature.ReturnType, signature.ParameterTypes);
+
+ return TargetModule.GetType(delegateName);
+ }
+
+ ///
+ /// Gets a method signature from the given generic delegate, consisting of a return type and parameter types.
+ ///
+ /// The type to inspect.
+ /// The types.
+ /// Thrown if no types could be extracted.
+ private (Type ReturnType, IReadOnlyList ParameterTypes) GetSignatureTypesFromGenericDelegate
+ (
+ [NotNull] Type delegateType
+ )
+ {
+ var typeParameters = delegateType.GenericTypeArguments;
+
+ if (delegateType.IsGenericFuncDelegate())
+ {
+ if (typeParameters.Length == 1)
+ {
+ return (typeParameters[0], new List());
+ }
+
+ return (typeParameters.Last(), typeParameters.Take(typeParameters.Length - 1).ToList());
+ }
+
+ if (delegateType.IsGenericActionDelegate())
+ {
+ return (typeof(void), typeParameters);
+ }
+
+ throw new InvalidOperationException("Couldn't extract a method signature from the type.");
+ }
+
+ ///
+ /// Gets the generated name for an explicit delegate implementation that returns the given type and takes the
+ /// given parameters. The name is guaranteed to be identical given the same input types in the same order.
+ ///
+ /// The return type of the delegate.
+ /// The parameter types of the delegate.
+ /// The generated name of the delegate.
+ [NotNull]
+ private string GetDelegateTypeName([NotNull] Type returnType, [NotNull] IReadOnlyCollection parameterTypes)
+ {
+ var sb = new StringBuilder();
+
+ sb.Append("generic_delegate_implementation_");
+ var returnTypeName = returnType.Name;
+ if (returnType.IsGenericDelegate())
+ {
+ var signature = GetSignatureTypesFromGenericDelegate(returnType);
+ returnTypeName = GetDelegateTypeName(signature.ReturnType, signature.ParameterTypes);
+ }
+
+ sb.Append($"r{returnTypeName}_");
+
+ var parameterNames = new List();
+ foreach (var parameterType in parameterTypes)
+ {
+ var parameterName = parameterType.Name;
+ if (parameterType.IsGenericDelegate())
+ {
+ var signature = GetSignatureTypesFromGenericDelegate(parameterType);
+ parameterName = GetDelegateTypeName(signature.ReturnType, signature.ParameterTypes);
+ }
+
+ parameterNames.Add(parameterName);
+ }
+
+ sb.Append($"p{string.Join("_p", parameterNames)}");
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/AdvancedDLSupport/Pipeline/ImplementationPipeline.cs b/AdvancedDLSupport/Pipeline/ImplementationPipeline.cs
index 6db986c2..0b5a74f9 100644
--- a/AdvancedDLSupport/Pipeline/ImplementationPipeline.cs
+++ b/AdvancedDLSupport/Pipeline/ImplementationPipeline.cs
@@ -148,6 +148,14 @@ private IEnumerable> GetBaseli
_constructorIL,
_options
);
+
+ yield return new GenericDelegateWrapper
+ (
+ _targetModule,
+ _targetType,
+ _constructorIL,
+ _options
+ );
}
///
diff --git a/docs/supported_constructs.md b/docs/supported_constructs.md
index 210f12f0..e29b2204 100644
--- a/docs/supported_constructs.md
+++ b/docs/supported_constructs.md
@@ -1,7 +1,7 @@
Supported Constructs
====================
-AdvancedDLSupport supports a number of interop constructs between C and the equivalent C# interface. This page lists
+AdvancedDLSupport supports a number of interop constructs between C and the equivalent C# interface. This page lists
them, and gives some usage examples.
### Functions to Methods
@@ -86,3 +86,106 @@ public unsafe interface IMyLibrary
int* GlobalVariableA { get; set; }
}
```
+
+### Marshalling of Generic Delegates
+`Action` and `Func` have been available in .NET since
+version 3.5 and are the preferred option to declaring your own delegate,
+but P/Invoke has to date lacked any capability for handling generics.
+
+ADL, on the other hand, allows you to seamlessly use generic delegates
+in native bindings. The following interface is completely valid, and
+works without any additional user code.
+
+The typical restrictions and gotchas related to explicit delegates in
+normal P/Invoke still apply.
+
+`C`
+```c
+typedef void (*Action)();
+typedef void (*ActionT1)(int t1);
+
+typedef int (*FuncT1)();
+typedef int (*FuncT1T2)(int t2);
+
+void ExecuteAction(Action action)
+{
+ action();
+}
+
+void ExecuteActionT1(ActionT1 action)
+{
+ action(5);
+}
+
+int ExecuteFuncT1(FuncT1 func)
+{
+ return func();
+}
+
+int ExecuteFuncT1T2(FuncT1T2 func)
+{
+ return func(5);
+}
+
+void NativeAction()
+{
+ fprintf(stdout, "Living in native land!");
+}
+
+Action GetNativeAction()
+{
+ return &NativeAction;
+}
+
+void NativeActionT1(int t1)
+{
+ fprintf(stdout, "Living in native land, with the parameter %d!", t1);
+}
+
+ActionT1 GetNativeActionT1()
+{
+ return &NativeActionT1;
+}
+
+int NativeFuncT1()
+{
+ return 5;
+}
+
+FuncT1 GetNativeFuncT1()
+{
+ return &NativeFuncT1;
+}
+
+int NativeFuncT1T2(int t2)
+{
+ return t2 * 5;
+}
+
+FuncT1T2 GetNativeFuncT1T2()
+{
+ return &NativeFuncT1T2;
+}
+```
+
+`C#`
+```c#
+public interface IMyLibrary
+{
+ void ExecuteAction(Action action);
+
+ void ExecuteActionT1(Action action);
+
+ int ExecuteFuncT1(Func func);
+
+ int ExecuteFuncT1T2(Func func);
+
+ Action GetNativeAction();
+
+ Action GetNativeActionT1();
+
+ Func GetNativeFuncT1();
+
+ Func GetNativeFuncT1T2();
+}
+```