Xamarin iOS绑定自定义结构体数组时泛型类型报错如何解决
Xamarin.iOS绑定生成器默认会将托管数组参数自动包装为NSArray传入原生方法,调用的NSArray.FromNSObjects泛型方法存在约束,要求元素必须是引用类型(NSObject子类)。
此前处理CGPoint数组时可通过BindAs修复,是因为Xamarin内置了CGPoint与NSValue的自动转换逻辑,但自定义结构体ColorHSV没有对应的内置转换规则,生成器直接将值类型的ColorHSV数组传入了带引用类型约束的泛型方法,触发编译错误。
同时原生头文件中colors参数声明为struct ColorHSV[],属于C风格连续结构体指针,并非NSArray类型,生成器默认按NSArray处理本身就不符合原生签名要求,进一步导致了转换逻辑错误。
核心思路是阻止生成器自动处理ColorHSV数组的封送逻辑,改为手动完成托管结构体数组到原生参数的转换。
步骤1:修改ApiDefinition.cs中的方法声明
将colors参数的类型从ColorHSV[]改为IntPtr,让生成器不再尝试自动包装该参数,修改后的代码如下:
namespace arFramework { [BaseType(typeof(NSObject))] interface ArPainting { [Export("processImage:::::::::")] UIImage ProcessImage( UIImage image, int resolution, int pointsSize, [BindAs(typeof(CGPoint[]))] NSValue[] points, IntPtr colors, float maxSliderValue, bool indoor, bool live, float transparency ); } }
步骤2:编写友好重载方法,手动封送结构体数组
直接暴露IntPtr参数对调用方不友好,在绑定项目中新增扩展方法文件(与Structs.cs同级即可),手动完成ColorHSV[]到原生指针的转换,同时处理非托管内存释放。
适配头文件声明的C风格结构体指针场景
如果原生方法确实按照头文件声明接收连续结构体指针,使用如下实现:
using System; using System.Runtime.InteropServices; using CoreGraphics; using UIKit; namespace arFramework { public static class ArPaintingExtensions { public static UIImage ProcessImage( this ArPainting painting, UIImage image, int resolution, int pointsSize, CGPoint[] points, ColorHSV[] colors, float maxSliderValue, bool indoor, bool live, float transparency) { if (painting == null) throw new ArgumentNullException(nameof(painting)); if (colors == null) throw new ArgumentNullException(nameof(colors)); IntPtr colorsPtr = IntPtr.Zero; int structSize = Marshal.SizeOf<ColorHSV>(); try { // 分配对应长度的非托管内存 colorsPtr = Marshal.AllocHGlobal(structSize * colors.Length); // 将托管结构体逐个拷贝到非托管内存 for (int i = 0; i < colors.Length; i++) { Marshal.StructureToPtr( colors[i], IntPtr.Add(colorsPtr, i * structSize), false ); } // 调用生成的原生绑定方法 return painting.ProcessImage( image, resolution, pointsSize, points, colorsPtr, maxSliderValue, indoor, live, transparency ); } finally { // 释放非托管内存,避免泄漏 if (colorsPtr != IntPtr.Zero) { for (int i = 0; i < colors.Length; i++) { Marshal.DestroyStructure<ColorHSV>(IntPtr.Add(colorsPtr, i * structSize)); } Marshal.FreeHGlobal(colorsPtr); } } } } }
适配NSArray包装NSValue场景
如果原生方法实际和points参数逻辑一致,要求传入包装了结构体的NSValue组成的NSArray,将上述封送逻辑替换为如下实现即可:
NSArray nsa_colors = null; try { NSValue[] colorValues = new NSValue[colors.Length]; for (int i = 0; i < colors.Length; i++) { GCHandle handle = GCHandle.Alloc(colors[i], GCHandleType.Pinned); try { // {ColorHSV=fff}为对应3个float字段的Objective-C类型编码,需和原生结构体定义匹配 colorValues[i] = NSValue.FromBytes(handle.AddrOfPinnedObject(), "{ColorHSV=fff}"); } finally { handle.Free(); } } nsa_colors = NSArray.FromNSObjects(colorValues); return painting.ProcessImage( image, resolution, pointsSize, points, nsa_colors.Handle, maxSliderValue, indoor, live, transparency ); } finally { nsa_colors?.Dispose(); }
步骤3:编译验证
重新编译绑定项目,之前自动生成代码中NSArray.FromNSObjects(colors)的错误逻辑会消失,业务侧调用时可以直接传入ColorHSV[]类型的参数,无需手动处理指针或内存释放逻辑。
内容的提问来源于stack exchange,提问作者Veeeksi

