C#通过ObjCRuntime调用Objective-C函数获取结构体返回值问题
问题原因
代码拿不到正确结果是两个核心错误:
- 错误使用
IntPtr_objc_msgSend入口处理结构体返回值。Objective-C消息发送的ABI对结构体返回有专门的调用约定:x86_64架构下超过16字节的结构体返回需要调用objc_msgSend_stret而非普通objc_msgSend;arm64架构下虽然统一使用objc_msgSend,但blittable结构体是直接作为值类型返回,不会返回指向结构体的IntPtr指针。你用返回IntPtr的重载调用,栈结构不匹配,自然拿到无效的0x0结果。 - 错误使用
Dlfcn.GetCGRectAPI。这个方法是用来读取动态库导出的C语言全局CGRect变量的,第一个参数要求是dlopen拿到的动态库句柄,第二个参数是全局变量的符号名,完全不是用来处理objc_msgSend返回结果的,传空字符串加无效句柄不可能拿到正确值。
正确实现方法
优先使用ObjCRuntime内置的封装,不用手动处理架构差异和调用约定,避免踩ABI的坑:
方法1:使用内置的结构体消息发送封装(推荐)
微软iOS/macOS绑定库已经为CGRect这类常用blittable结构体内置了对应的消息发送实现,自动适配不同架构的调用规则:
// 替换成你实际要调用的selector,比如获取UIView的frame就传"frame" Selector targetSelector = new Selector("frame"); // 直接调用内置封装,返回值就是正确的CGRect CGRect result = Messaging.CGRect_objc_msgSend(CGRect.Empty, objectHandle, targetSelector.Handle);
如果调用的方法带参数,直接使用内置的对应参数重载即可,不需要额外处理调用逻辑。
方法2:自定义结构体场景的手动P/Invoke
如果你要返回的是自定义结构体,需要根据架构声明正确的P/Invoke签名:
// arm64架构下不需要objc_msgSend_stret,直接返回结构体即可 [DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")] static extern CGRect CGRect_objc_msgSend(IntPtr target, IntPtr selector); // x86_64架构下返回大于16字节结构体需要用stret入口 [DllImport(Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend_stret")] static extern void CGRect_objc_msgSend_stret(out CGRect result, IntPtr target, IntPtr selector); // 调用时根据架构选择对应方法 CGRect result; if (Runtime.Arch == Arch.DEVICE || Runtime.IsARM64CallingConvention) { result = CGRect_objc_msgSend(objectHandle, selectorHandle); } else { CGRect_objc_msgSend_stret(out result, objectHandle, selectorHandle); }
方法3:避免手动调用消息发送
如果只是获取已知类型对象的属性,不需要手动写msgSend逻辑,直接用运行时绑定即可:
// 将非托管句柄转为NSObject对象 NSObject targetObj = Runtime.GetNSObject(objectHandle); // 已知具体类型的场景直接强转访问属性即可,比如UIView直接取Frame属性 // var view = (UIView)targetObj; // CGRect frame = view.Frame; // 动态类型场景用KVC取值 NSValue rectValue = (NSValue)targetObj.ValueForKey(new NSString("frame")); CGRect result = rectValue.CGRectValue;
注意:所有手动调用objc_msgSend的场景,都要保证selector存在且参数/返回值类型匹配,否则会出现内存泄露、崩溃或者返回值错误的问题。
内容的提问来源于stack exchange,提问作者Aleksandr
相关产品推荐
相关产品推荐

