CPF/CPF.Mac/Mac/ObjCRuntime/Class.cs
2023-11-21 23:05:03 +08:00

591 lines
22 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CPF.Mac.Foundation;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace CPF.Mac.ObjCRuntime
{
public class Class : INativeObject
{
[MonoNativeFunctionWrapper]
private delegate int getFrameLengthDelegate(IntPtr @this, IntPtr sel);
[MonoNativeFunctionWrapper]
private delegate IntPtr addPropertyDelegate(IntPtr cls, string name, objc_attribute_prop[] attributes, int count);
private struct objc_attribute_prop
{
[MarshalAs(UnmanagedType.LPStr)]
internal string name;
[MarshalAs(UnmanagedType.LPStr)]
internal string value;
}
public static bool ThrowOnInitFailure = true;
private static Dictionary<IntPtr, Type> type_map = new Dictionary<IntPtr, Type>();
private static Dictionary<Type, Type> custom_types = new Dictionary<Type, Type>();
private static List<Delegate> method_wrappers = new List<Delegate>();
private static object lock_obj = new object();
internal IntPtr handle;
private static IntPtr memory;
private static int size_left;
private static getFrameLengthDelegate getFrameLength = Selector.GetFrameLength;
private static IntPtr getFrameLengthPtr = Marshal.GetFunctionPointerForDelegate(getFrameLength);
private static addPropertyDelegate addProperty;
private static bool addPropertyInitialized;
public IntPtr Handle => handle;
public IntPtr SuperClass => class_getSuperclass(handle);
public string Name => Messaging.StringFromNativeUtf8(class_getName(handle));
public Class(string name)
{
handle = objc_getClass(name);
if (handle == IntPtr.Zero)
{
throw new ArgumentException($"name {name} is an unknown class", "name");
}
}
public Class(Type type)
{
handle = Register(type);
}
public Class(IntPtr handle)
{
this.handle = handle;
}
internal static Class Construct(IntPtr handle)
{
return new Class(handle);
}
internal static string GetName(IntPtr @class)
{
return Messaging.StringFromNativeUtf8(class_getName(@class));
}
public static IntPtr GetHandle(string name)
{
return objc_getClass(name);
}
public static IntPtr GetHandle(Type type)
{
RegisterAttribute registerAttribute = (RegisterAttribute)Attribute.GetCustomAttribute(type, typeof(RegisterAttribute), inherit: false);
string name = (registerAttribute == null) ? type.FullName : (registerAttribute.Name ?? type.FullName);
bool is_wrapper = registerAttribute?.IsWrapper ?? false;
IntPtr intPtr = objc_getClass(name);
if (intPtr == IntPtr.Zero)
{
intPtr = Register(type, name, is_wrapper);
}
return intPtr;
}
public static bool IsCustomType(Type type)
{
lock (lock_obj)
{
return custom_types.ContainsKey(type);
}
}
internal static Type Lookup(IntPtr klass)
{
return Lookup(klass, throw_on_error: true);
}
internal static Type Lookup(IntPtr klass, bool throw_on_error)
{
lock (lock_obj)
{
if (type_map.TryGetValue(klass, out Type value))
{
return value;
}
IntPtr key = klass;
while (true)
{
IntPtr intPtr = class_getSuperclass(klass);
if (type_map.TryGetValue(intPtr, out value))
{
type_map[key] = value;
return value;
}
if (intPtr == IntPtr.Zero)
{
break;
}
klass = intPtr;
}
if (throw_on_error)
{
throw new ArgumentException("Could not find a valid superclass for type " + new Class(key).Name + ". Did you forget to register the bindings at " + typeof(Class).FullName + ".Register() or call NSApplication.Init()?");
}
return null;
}
}
internal static IntPtr Register(Type type)
{
RegisterAttribute registerAttribute = (RegisterAttribute)Attribute.GetCustomAttribute(type, typeof(RegisterAttribute), inherit: false);
string name = (registerAttribute == null) ? type.FullName : (registerAttribute.Name ?? type.FullName);
bool is_wrapper = registerAttribute?.IsWrapper ?? false;
return Register(type, name, is_wrapper);
}
private static IntPtr Register(Type type, string name, bool is_wrapper)
{
IntPtr zero = IntPtr.Zero;
IntPtr zero2 = IntPtr.Zero;
zero2 = objc_getClass(name);
lock (lock_obj)
{
if (zero2 != IntPtr.Zero)
{
if (!type_map.ContainsKey(zero2))
{
type_map[zero2] = type;
}
return zero2;
}
if (objc_getProtocol(name) != IntPtr.Zero)
{
throw new ArgumentException("Attempting to register a class named: " + name + " which is a valid protocol");
}
if (is_wrapper)
{
return IntPtr.Zero;
}
Type baseType = type.BaseType;
string text = null;
while (Attribute.IsDefined(baseType, typeof(ModelAttribute), inherit: false))
{
baseType = baseType.BaseType;
}
RegisterAttribute registerAttribute = (RegisterAttribute)Attribute.GetCustomAttribute(baseType, typeof(RegisterAttribute), inherit: false);
text = ((registerAttribute == null) ? baseType.FullName : (registerAttribute.Name ?? baseType.FullName));
zero = objc_getClass(text);
if (zero == IntPtr.Zero && (baseType.Assembly != NSObject.MonoMacAssembly || typeof(NotMonoMac).IsAssignableFrom(baseType)))
{
bool is_wrapper2 = registerAttribute?.IsWrapper ?? false;
Register(baseType, text, is_wrapper2);
zero = objc_getClass(text);
}
if (zero == IntPtr.Zero)
{
zero = objc_getClass("NSObject");
}
zero2 = objc_allocateClassPair(zero, name, IntPtr.Zero);
PropertyInfo[] properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (PropertyInfo propertyInfo in properties)
{
ConnectAttribute connectAttribute = (ConnectAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ConnectAttribute));
if (connectAttribute != null)
{
string name2 = connectAttribute.Name ?? propertyInfo.Name;
class_addIvar(zero2, name2, (IntPtr)Marshal.SizeOf(typeof(IntPtr)), (ushort)Math.Log(Marshal.SizeOf(typeof(IntPtr)), 2.0), "@");
}
RegisterProperty(propertyInfo, type, zero2);
}
NSObject.OverrideRetainAndRelease(zero2);
MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
for (int i = 0; i < methods.Length; i++)
{
RegisterMethod(methods[i], type, zero2);
}
ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor != null)
{
NativeConstructorBuilder nativeConstructorBuilder = new NativeConstructorBuilder(constructor);
class_addMethod(zero2, nativeConstructorBuilder.Selector, nativeConstructorBuilder.Delegate, nativeConstructorBuilder.Signature);
method_wrappers.Add(nativeConstructorBuilder.Delegate);
}
ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (ConstructorInfo constructorInfo in constructors)
{
if ((ExportAttribute)Attribute.GetCustomAttribute(constructorInfo, typeof(ExportAttribute)) != null)
{
NativeConstructorBuilder nativeConstructorBuilder2 = new NativeConstructorBuilder(constructorInfo);
class_addMethod(zero2, nativeConstructorBuilder2.Selector, nativeConstructorBuilder2.Delegate, nativeConstructorBuilder2.Signature);
method_wrappers.Add(nativeConstructorBuilder2.Delegate);
}
}
var inter = type.GetInterfaces();
if (inter != null)
{
foreach (var item in inter)
{
if (item.GetCustomAttribute<ProtocolAttribute>() != null)
{
var prot = objc_getProtocol(item.Name);
if (prot == null)
{
throw new Exception("未找到Protocol" + item.Name);
}
class_addProtocol(zero2, prot);
}
}
}
objc_registerClassPair(zero2);
type_map[zero2] = type;
custom_types.Add(type, type);
return zero2;
}
}
internal static void RegisterProperty(PropertyInfo prop, Type type, IntPtr handle)
{
ExportAttribute exportAttribute = (ExportAttribute)Attribute.GetCustomAttribute(prop, typeof(ExportAttribute));
if (exportAttribute != null)
{
if (prop.PropertyType.IsGenericType || prop.PropertyType.IsGenericTypeDefinition)
{
throw new ArgumentException($"Cannot export the property '{prop.DeclaringType.FullName}.{prop.Name}': it is generic.");
}
MethodInfo getMethod = prop.GetGetMethod(nonPublic: true);
if (getMethod != null)
{
RegisterMethod(getMethod, exportAttribute.ToGetter(prop), type, handle);
}
getMethod = prop.GetSetMethod(nonPublic: true);
if (getMethod != null)
{
RegisterMethod(getMethod, exportAttribute.ToSetter(prop), type, handle);
}
int count = 0;
objc_attribute_prop[] array = new objc_attribute_prop[3];
objc_attribute_prop objc_attribute_prop = array[count++] = new objc_attribute_prop
{
name = "T",
value = TypeConverter.ToNative(prop.PropertyType)
};
switch (exportAttribute.ArgumentSemantic)
{
case ArgumentSemantic.Copy:
{
int num2 = count++;
objc_attribute_prop = new objc_attribute_prop
{
name = "C",
value = ""
};
array[num2] = objc_attribute_prop;
break;
}
case ArgumentSemantic.Retain:
{
int num = count++;
objc_attribute_prop = new objc_attribute_prop
{
name = "&",
value = ""
};
array[num] = objc_attribute_prop;
break;
}
}
objc_attribute_prop = (array[count++] = new objc_attribute_prop
{
name = "V",
value = exportAttribute.Selector
});
class_addProperty(handle, exportAttribute.Selector, array, count);
}
}
internal static void RegisterMethod(MethodInfo minfo, Type type, IntPtr handle)
{
ExportAttribute exportAttribute = (ExportAttribute)Attribute.GetCustomAttribute(minfo.GetBaseDefinition(), typeof(ExportAttribute));
if (exportAttribute != null && (!minfo.IsVirtual || !(minfo.DeclaringType != type) || (minfo.DeclaringType.Assembly != NSObject.MonoMacAssembly || typeof(NotMonoMac).IsAssignableFrom(minfo.DeclaringType))))
{
RegisterMethod(minfo, exportAttribute, type, handle);
}
}
private static IntPtr AllocExecMemory(int size)
{
if (size_left < size)
{
size_left = 4096;
memory = Marshal.AllocHGlobal(size_left);
if (memory == IntPtr.Zero)
{
throw new Exception($"Could not allocate memory for specialized x86 floating point stret delegate thunk: {Marshal.GetLastWin32Error()}");
}
if (mprotect(memory, size_left, 7) != 0)
{
throw new Exception($"Could not make allocated memory for specialized x86 floating point stret delegate thunk code executable: {Marshal.GetLastWin32Error()}");
}
}
IntPtr result = memory;
size_left -= size;
memory = new IntPtr(memory.ToInt32() + size);
return result;
}
private static bool TypeRequiresFloatingPointTrampoline(Type t)
{
if (IntPtr.Size != 4)
{
return false;
}
if (typeof(float) == t || typeof(double) == t)
{
return false;
}
if (!t.IsValueType || t.IsEnum)
{
return false;
}
if (Marshal.SizeOf(t) <= 8)
{
return false;
}
return TypeContainsFloatingPoint(t);
}
private static bool TypeContainsFloatingPoint(Type t)
{
if (!t.IsValueType || t.IsEnum || t.IsPrimitive)
{
return false;
}
FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo fieldInfo in fields)
{
if (fieldInfo.FieldType == typeof(double) || fieldInfo.FieldType == typeof(float))
{
return true;
}
if (!(fieldInfo.FieldType == t) && TypeContainsFloatingPoint(fieldInfo.FieldType))
{
return true;
}
}
return false;
}
private static IntPtr GetFunctionPointer(MethodInfo minfo, Delegate @delegate)
{
IntPtr functionPointerForDelegate = Marshal.GetFunctionPointerForDelegate(@delegate);
if (!TypeRequiresFloatingPointTrampoline(minfo.ReturnType))
{
return functionPointerForDelegate;
}
IntPtr intPtr = AllocExecMemory(83);
IntPtr intPtr2 = new IntPtr(functionPointerForDelegate.ToInt32() - intPtr.ToInt32() - 70);
IntPtr intPtr3 = new IntPtr(getFrameLengthPtr.ToInt32() - intPtr.ToInt32() - 27);
byte[] bytes = BitConverter.GetBytes(intPtr2.ToInt32());
byte[] bytes2 = BitConverter.GetBytes(intPtr3.ToInt32());
byte[] obj = new byte[83]
{
85,
137,
229,
86,
87,
83,
131,
236,
60,
139,
69,
16,
137,
68,
36,
4,
139,
69,
12,
137,
4,
36,
232,
0,
0,
0,
0,
137,
69,
240,
131,
192,
15,
193,
232,
4,
193,
224,
4,
41,
196,
139,
77,
240,
141,
117,
8,
137,
231,
131,
249,
0,
116,
11,
131,
233,
4,
139,
4,
14,
137,
4,
15,
235,
240,
232,
0,
0,
0,
0,
139,
93,
244,
139,
125,
248,
139,
117,
252,
201,
194,
4,
0
};
obj[23] = bytes2[0];
obj[24] = bytes2[1];
obj[25] = bytes2[2];
obj[26] = bytes2[3];
obj[66] = bytes[0];
obj[67] = bytes[1];
obj[68] = bytes[2];
obj[69] = bytes[3];
byte[] array = obj;
Marshal.Copy(array, 0, intPtr, array.Length);
return intPtr;
}
internal static void RegisterMethod(MethodInfo minfo, ExportAttribute ea, Type type, IntPtr handle)
{
NativeMethodBuilder nativeMethodBuilder = new NativeMethodBuilder(minfo, type, ea);
class_addMethod(minfo.IsStatic ? object_getClass(handle) : handle, nativeMethodBuilder.Selector, GetFunctionPointer(minfo, nativeMethodBuilder.Delegate), nativeMethodBuilder.Signature);
lock (lock_obj)
{
method_wrappers.Add(nativeMethodBuilder.Delegate);
}
}
[DllImport("libc", SetLastError = true)]
private static extern int mprotect(IntPtr addr, int len, int prot);
[DllImport("libc", SetLastError = true)]
private static extern IntPtr mmap(IntPtr start, ulong length, int prot, int flags, int fd, long offset);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr objc_allocateClassPair(IntPtr superclass, string name, IntPtr extraBytes);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr objc_getClass(string name);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr objc_getProtocol(string name);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern bool class_addProtocol(IntPtr cls, IntPtr protocol);
[DllImport("/usr/lib/libobjc.dylib")]
private static extern void objc_registerClassPair(IntPtr cls);
[DllImport("/usr/lib/libobjc.dylib")]
private static extern bool class_addIvar(IntPtr cls, string name, IntPtr size, ushort alignment, string types);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern bool class_addMethod(IntPtr cls, IntPtr name, Delegate imp, string types);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern bool class_addMethod(IntPtr cls, IntPtr name, IntPtr imp, string types);
[DllImport("/usr/lib/libobjc.dylib")]
private static extern IntPtr class_getName(IntPtr cls);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr class_getSuperclass(IntPtr cls);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr object_getClass(IntPtr obj);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr class_getMethodImplementation(IntPtr cls, IntPtr sel);
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr class_getInstanceVariable(IntPtr cls, string name);
private static IntPtr class_addProperty(IntPtr cls, string name, objc_attribute_prop[] attributes, int count)
{
if (!addPropertyInitialized)
{
IntPtr intPtr = Dlfcn.dlopen("/usr/lib/libobjc.dylib", 0);
try
{
IntPtr intPtr2 = Dlfcn.dlsym(intPtr, "class_addProperty");
if (intPtr2 != IntPtr.Zero)
{
addProperty = (addPropertyDelegate)Marshal.GetDelegateForFunctionPointer(intPtr2, typeof(addPropertyDelegate));
}
}
finally
{
Dlfcn.dlclose(intPtr);
}
addPropertyInitialized = true;
}
if (addProperty == null)
{
return IntPtr.Zero;
}
return addProperty(cls, name, attributes, count);
}
[DllImport("/usr/lib/libobjc.dylib")]
internal static extern IntPtr objc_setAssociatedObject(IntPtr obj, IntPtr name, IntPtr value, long policy);
internal enum objc_AssociationPolicy
{
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
}
}
}