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(); +} +```