diff --git a/.gitignore b/.gitignore index 6f4242a..f867683 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.meta +*.meta \ No newline at end of file diff --git a/src/DllManipulator.cs b/src/DllManipulator.cs index 842c569..5e2f1bc 100644 --- a/src/DllManipulator.cs +++ b/src/DllManipulator.cs @@ -16,8 +16,10 @@ public class DllManipulator : MonoBehaviour public const string DLL_PATH_PATTERN_NAME_MACRO = "{name}"; public const string DLL_PATH_PATTERN_ASSETS_MACRO = "{assets}"; public const string DLL_PATH_PATTERN_PROJECT_MACRO = "{proj}"; - private static readonly Type[] DELEGATE_CTOR_PARAMETERS = new[] { typeof(object), typeof(IntPtr) }; - private static readonly Type[] UNMANAGED_FUNCTION_POINTER_ATTRIBUTE_CTOR_PARAMETERS = new[] { typeof(CallingConvention) }; + public static readonly Type[] SUPPORTED_PARAMATER_ATTRIBUTES = { typeof(MarshalAsAttribute), typeof(InAttribute), typeof(OutAttribute) }; + private static readonly Type[] DELEGATE_CTOR_PARAMETERS = { typeof(object), typeof(IntPtr) }; + private static readonly Type[] UNMANAGED_FUNCTION_POINTER_ATTRIBUTE_CTOR_PARAMETERS = { typeof(CallingConvention) }; + private static readonly Type[] MARSHAL_AS_ATTRIBUTE_CTOR_PARAMETERS = { typeof(UnmanagedType) }; public DllManipulatorOptions Options = new DllManipulatorOptions() { @@ -262,10 +264,10 @@ private static DynamicMethod CreateNewNativeFunctionMock(MethodInfo nativeMethod var targetDelegateInvokeMethod = nativeFunction.delegateType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public); var mockedDynamicMethod = new DynamicMethod(dllName + ":::" + nativeFunctionSymbol, nativeMethod.ReturnType, parametersTypes, typeof(DllManipulator)); - mockedDynamicMethod.DefineParameter(0, nativeMethod.ReturnParameter.Attributes, nativeMethod.ReturnParameter.Name); + mockedDynamicMethod.DefineParameter(0, nativeMethod.ReturnParameter.Attributes, null); for (int i = 0; i < parameters.Length; i++) { - mockedDynamicMethod.DefineParameter(parameters[i].Position, parameters[i].Attributes, parameters[i].Name); + mockedDynamicMethod.DefineParameter(i + 1, parameters[i].Attributes, null); } if (_nativeFunctionsField == null) @@ -333,7 +335,7 @@ private static void AddNativeFunction(NativeFunction nativeFunction) _nativeFunctions[_nativeFunctionsCount++] = nativeFunction; } - private static Type CreateDelegateTypeForNativeFunctionSignature(NativeFunctionSignature funcionSignature) + private static Type CreateDelegateTypeForNativeFunctionSignature(NativeFunctionSignature functionSignature) { if (_customDelegateTypesModule == null) { @@ -347,31 +349,81 @@ private static Type CreateDelegateTypeForNativeFunctionSignature(NativeFunctionS //ufp = UnmanagedFunctionPointer var ufpAttrType = typeof(UnmanagedFunctionPointerAttribute); - var ufpAttrCtor = ufpAttrType.GetConstructor(UNMANAGED_FUNCTION_POINTER_ATTRIBUTE_CTOR_PARAMETERS); - var ufpAttrCtorArgValues = new object[] { funcionSignature.callingConvention }; - var ufpAttrNamedFields = new [] { + var ufpAttrCtor = ufpAttrType.GetConstructor(UNMANAGED_FUNCTION_POINTER_ATTRIBUTE_CTOR_PARAMETERS); + object[] ufpAttrCtorArgValues = { functionSignature.callingConvention }; + FieldInfo[] ufpAttrNamedFields = { ufpAttrType.GetField(nameof(UnmanagedFunctionPointerAttribute.BestFitMapping), BindingFlags.Public | BindingFlags.Instance), ufpAttrType.GetField(nameof(UnmanagedFunctionPointerAttribute.CharSet), BindingFlags.Public | BindingFlags.Instance), ufpAttrType.GetField(nameof(UnmanagedFunctionPointerAttribute.SetLastError), BindingFlags.Public | BindingFlags.Instance), ufpAttrType.GetField(nameof(UnmanagedFunctionPointerAttribute.ThrowOnUnmappableChar), BindingFlags.Public | BindingFlags.Instance), }; - var ufpAttrFieldValues = new object[] { funcionSignature.bestFitMapping, funcionSignature.charSet, funcionSignature.setLastError, funcionSignature.throwOnUnmappableChar }; + object[] ufpAttrFieldValues = { functionSignature.bestFitMapping, functionSignature.charSet, functionSignature.setLastError, functionSignature.throwOnUnmappableChar }; var ufpAttrBuilder = new CustomAttributeBuilder(ufpAttrCtor, ufpAttrCtorArgValues, ufpAttrNamedFields, ufpAttrFieldValues); delBuilder.SetCustomAttribute(ufpAttrBuilder); - var ctorBuilder = delBuilder.DefineConstructor(MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, CallingConventions.Standard, DELEGATE_CTOR_PARAMETERS); ctorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); var invokeBuilder = delBuilder.DefineMethod("Invoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, - CallingConventions.Standard | CallingConventions.HasThis, funcionSignature.returnParameterType, funcionSignature.parameterTypes); + CallingConventions.Standard | CallingConventions.HasThis, functionSignature.returnParameter.type, functionSignature.parameters.Select(p => p.type).ToArray()); invokeBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); + var invokeReturnParam = invokeBuilder.DefineParameter(0, functionSignature.returnParameter.parameterAttributes, null); + foreach (var attr in functionSignature.returnParameter.customAttributes) + { + invokeReturnParam.SetCustomAttribute(GetAttributeBuilderFromAttributeInstance(attr)); + } + for (int i = 0; i < functionSignature.parameters.Length; i++) + { + var param = functionSignature.parameters[i]; + var paramBuilder = invokeBuilder.DefineParameter(i + 1, param.parameterAttributes, null); + foreach(var attr in param.customAttributes) + { + paramBuilder.SetCustomAttribute(GetAttributeBuilderFromAttributeInstance(attr)); + } + } _createdDelegateTypes++; return delBuilder.CreateType(); } + private static CustomAttributeBuilder GetAttributeBuilderFromAttributeInstance(Attribute attribute) + { + var attrType = attribute.GetType(); + switch (attribute) + { + case MarshalAsAttribute marshalAsAttribute: + { + var ctor = attrType.GetConstructor(MARSHAL_AS_ATTRIBUTE_CTOR_PARAMETERS); + object[] ctorArgs = { marshalAsAttribute.Value }; + var fields = attrType.GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(f => f.FieldType.IsValueType).ToArray(); //XXX: Used to bypass Mono bug, see https://github.com/mono/mono/issues/12747 + var fieldArgumentValues = new object[fields.Length]; + for(int i = 0; i < fields.Length; i++) + { + fieldArgumentValues[i] = fields[i].GetValue(attribute); + } + + //MarshalAsAttribute has no properties other than Value, which is passed in constructor, hence empty properties array + return new CustomAttributeBuilder(ctor, ctorArgs, Array.Empty(), Array.Empty(), + fields, fieldArgumentValues); + } + case InAttribute _: + { + var ctor = attrType.GetConstructor(Type.EmptyTypes); + return new CustomAttributeBuilder(ctor, Array.Empty(), Array.Empty(), Array.Empty(), + Array.Empty(), Array.Empty()); + } + case OutAttribute _: + { + var ctor = attrType.GetConstructor(Type.EmptyTypes); + return new CustomAttributeBuilder(ctor, Array.Empty(), Array.Empty(), Array.Empty(), + Array.Empty(), Array.Empty()); + } + default: + throw new NotImplementedException($"Attribute {attrType} is not supported"); + } + } private static string GetDllPath(string dllName) { diff --git a/src/NativeFunction.cs b/src/NativeFunction.cs index eabe3b3..7cea16b 100644 --- a/src/NativeFunction.cs +++ b/src/NativeFunction.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; namespace DllManipulator.Internal { @@ -50,51 +46,4 @@ public NativeFunction(NativeFunctionIdentity identity, NativeDll containingDll) this.containingDll = containingDll; } } - - internal class NativeFunctionSignature - { - public readonly Type returnParameterType; - public readonly Type[] parameterTypes; - public readonly CallingConvention callingConvention; - public readonly bool bestFitMapping; - public readonly CharSet charSet; - public readonly bool setLastError; - public readonly bool throwOnUnmappableChar; - - public NativeFunctionSignature(MethodInfo methodInfo, CallingConvention callingConvention, bool bestFitMapping, CharSet charSet, bool setLastError, bool throwOnUnmappableChar) - { - this.returnParameterType = methodInfo.ReturnType; - this.parameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); - this.callingConvention = callingConvention; - this.bestFitMapping = bestFitMapping; - this.charSet = charSet; - this.setLastError = setLastError; - this.throwOnUnmappableChar = throwOnUnmappableChar; - } - - public override bool Equals(object obj) - { - return obj is NativeFunctionSignature other && - EqualityComparer.Default.Equals(returnParameterType, other.returnParameterType) && - EqualityComparer.Default.Equals(parameterTypes, other.parameterTypes) && - callingConvention == other.callingConvention && - bestFitMapping == other.bestFitMapping && - charSet == other.charSet && - setLastError == other.setLastError && - throwOnUnmappableChar == other.throwOnUnmappableChar; - } - - public override int GetHashCode() - { - var hashCode = 763644728; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(returnParameterType); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(parameterTypes); - hashCode = hashCode * -1521134295 + callingConvention.GetHashCode(); - hashCode = hashCode * -1521134295 + bestFitMapping.GetHashCode(); - hashCode = hashCode * -1521134295 + charSet.GetHashCode(); - hashCode = hashCode * -1521134295 + setLastError.GetHashCode(); - hashCode = hashCode * -1521134295 + throwOnUnmappableChar.GetHashCode(); - return hashCode; - } - } } diff --git a/src/NativeFunctionSignature.cs b/src/NativeFunctionSignature.cs new file mode 100644 index 0000000..467f298 --- /dev/null +++ b/src/NativeFunctionSignature.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace DllManipulator.Internal +{ + internal class NativeFunctionSignature + { + public readonly NativeFunctionParameterSignature returnParameter; + public readonly NativeFunctionParameterSignature[] parameters; + public readonly CallingConvention callingConvention; + public readonly bool bestFitMapping; + public readonly CharSet charSet; + public readonly bool setLastError; + public readonly bool throwOnUnmappableChar; + + public NativeFunctionSignature(MethodInfo methodInfo, CallingConvention callingConvention, bool bestFitMapping, CharSet charSet, bool setLastError, bool throwOnUnmappableChar) + { + this.returnParameter = new NativeFunctionParameterSignature(methodInfo.ReturnParameter); + this.parameters = methodInfo.GetParameters().Select(p => new NativeFunctionParameterSignature(p)).ToArray(); + this.callingConvention = callingConvention; + this.bestFitMapping = bestFitMapping; + this.charSet = charSet; + this.setLastError = setLastError; + this.throwOnUnmappableChar = throwOnUnmappableChar; + } + + public override bool Equals(object obj) + { + var other = obj as NativeFunctionSignature; + if (other == null) + { + return false; + } + + if(!returnParameter.Equals(other.returnParameter)) + { + return false; + } + + if (!parameters.SequenceEqual(other.parameters)) + { + return false; + } + + if (callingConvention != other.callingConvention) + { + return false; + } + + if (bestFitMapping != other.bestFitMapping) + { + return false; + } + + if (charSet != other.charSet) + { + return false; + } + + if (setLastError != other.setLastError) + { + return false; + } + + if (throwOnUnmappableChar != other.throwOnUnmappableChar) + { + return false; + } + + return true; + } + + public override int GetHashCode() + { + var hashCode = 316391695; + hashCode = hashCode * -1521134295 + returnParameter.GetHashCode(); + hashCode = hashCode * -1521134295 + callingConvention.GetHashCode(); + hashCode = hashCode * -1521134295 + bestFitMapping.GetHashCode(); + hashCode = hashCode * -1521134295 + charSet.GetHashCode(); + hashCode = hashCode * -1521134295 + setLastError.GetHashCode(); + hashCode = hashCode * -1521134295 + throwOnUnmappableChar.GetHashCode(); + return hashCode; + } + } + + internal class NativeFunctionParameterSignature + { + public readonly Type type; + public readonly ParameterAttributes parameterAttributes; + public readonly Attribute[] customAttributes; + + public NativeFunctionParameterSignature(ParameterInfo parameterInfo) + { + this.type = parameterInfo.ParameterType; + this.parameterAttributes = parameterInfo.Attributes; + var attrs = parameterInfo.GetCustomAttributes(false).OfType(); //XXX: This is required way of obtaining attributes, since both CustomAttributeExtensions.GetCustomAttributes() and Attribute.GetCustomAttributes() return at most 1 attribute (mono bug?) + this.customAttributes = attrs + .Where(a => DllManipulator.SUPPORTED_PARAMATER_ATTRIBUTES.Contains(a.GetType())) + .ToArray(); + } + + public NativeFunctionParameterSignature(Type type, ParameterAttributes parameterAttributes, Attribute[] customAttributes) + { + this.type = type; + this.parameterAttributes = parameterAttributes; + this.customAttributes = customAttributes; + } + + public override bool Equals(object obj) + { + var other = obj as NativeFunctionParameterSignature; + if(other == null) + { + return false; + } + + if (type != other.type) + { + return false; + } + + if (parameterAttributes != other.parameterAttributes) + { + return false; + } + + if (customAttributes.Except(other.customAttributes).Any()) //Check if arrays have the same elements + { + return false; + } + + return true; + } + + public override int GetHashCode() + { + var hashCode = 424392846; + hashCode = hashCode * -1521134295 + type.GetHashCode(); + hashCode = hashCode * -1521134295 + parameterAttributes.GetHashCode(); + return hashCode; + } + } +}