日本一位大佬写过一篇文章,很不错了。
[Unity] C#とObjective-Cの連携まとめ
略做翻译,记录一下。
基础
1 2 3 4 5 6 7 8 9 10
| #ifdef __cplusplus extern "C" { #endif void sampleMethod1() { NSLog(@"sampleMethod1 called."); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7
| using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern void sampleMethod1(); }
|
- 在 Objective-C 端,创建一个定义 C 函数的源代码。
- 如果你想使用一个Objective-C类,你可以通过C函数调用它。
- Objective-C 源文件位于 Assets/Plugins/iOS 下。
在 C# 端,定义了与定义的 C 函数同名的外部静态方法。 调用此静态方法时,将调用定义的 C 函数。
C#调用OC
基本数据类型(整数、浮点数、布尔值)、字符串
1 2 3 4 5 6 7 8 9 10
| #ifdef __cplusplus extern "C" { #endif void sampleMethod2(int val1, float val2, bool val3, const char *val4) { NSLog(@"sampleMethod2 called. val1=%d, val2=%f, val3=%d, val4=%s", val1, val2, val3, val4); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6
| using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern void sampleMethod2(int val1, float val2, bool val3, string val4); }
|
只需按原样传递原语即可。 字符串在 中接收。const char *
传递数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #ifdef __cplusplus extern "C" { #endif void sampleMethod3(int *intArr, int intArrSize, const char **strArr, int strArrSize) { for (int i = 0; i < intArrSize; i++) { NSLog(@"arr1 %d : %d", i, intArr[i]); } for (int i = 0; i < strArrSize; i++) { NSLog(@"arr2 %d : %s", i, strArr[i]); } } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6
| using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern void sampleMethod3(int[] intArr, int intArrSize, string[] strArr, int strArrSize); }
|
使用指针接收。 对于字符串,它是 的双指针。 我认为传递数组的大小也会很好。const char
静态方法
1 2 3 4 5 6 7 8 9 10 11 12
| #ifdef __cplusplus extern "C" { #endif typedef void (*cs_callback)(int);
void sampleMethod4(cs_callback callback) { callback(9); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using System.Runtime.InteropServices; using AOT;
public class Sample { delegate void callback_delegate(int val);
[DllImport("__Internal")] private static extern void sampleMethod4(callback_delegate callback);
[MonoPInvokeCallback(typeof(callback_delegate))] private static void cs_callback(int val) { UnityEngine.Debug.Log ("cs_callback : " + val); }
private static void sampleMethod4Invoker() { sampleMethod4 (cs_callback); } }
|
CSharp静态方法可以作为函数指针传递给Objective-C。 您可以传递回调函数,并在该过程完成后调用它。
但是,当从Objective-C调用 CSharp 回调时, 我认为使用[UnitySendMessage]更简单、更常见。
C# 中的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #ifdef __cplusplus extern "C" { #endif typedef void *_Mono_Sample_Instance;
typedef void (*cs_callback)(_Mono_Sample_Instance, int);
void sampleMethod5(_Mono_Sample_Instance instance) { NSLog(@"sampleMethod5 called."); cs_callback(instance, 999); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| using System.Runtime.InteropServices;
public class Sample { delegate void callback_delegate(IntPtr gameObjectPtr, int val);
[DllImport("__Internal")] private static extern void sampleMethod5(IntPtr gameObjectPtr, callback_delegate callback);
[MonoPInvokeCallback(typeof(callback_delegate))] private static void cs_callback(IntPtr gameObjectPtr, int val) { GCHandle handle = (GCHandle) gameObjectPtr; GameObject gameObject = handle.Target as GameObject; handle.Free();
UnityEngine.Debug.Log ("cs_callback : " + gameObject.name); }
private static void sampleMethod5Invoker() { GameObject gameObject = new GameObject ("Sample_GameObject"); IntPtr gameObjectPtr = (IntPtr)GCHandle.Alloc(gameObject); sampleMethod5 (gameObjectPtr, cs_callback); } }
|
- C# 的实例也可以在 IntPtr 中传递。
- 作为一种用法,我认为传递给Objective-C的C#实例将通过回调再次传递给C#
- 如果使用它,则可以在传递上述静态方法并调用回调时从那里调用实例方法。
返回值
基本类型(整数、浮点数、布尔值)、字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #ifdef __cplusplus extern "C" { #endif int sampleMethod6() { return 5; }
float sampleMethod7() { return 1.1f; }
bool sampleMethod8() { return true; }
const char* sampleMethod9() { const char *str = "test"; char* retStr = (char*)malloc(strlen(str) + 1); strcpy(retStr, str); retStr[strlen(str)] = '0'; return retStr; } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern int sampleMethod6();
[DllImport("__Internal")] private static extern float sampleMethod7();
[DllImport("__Internal")] private static extern bool sampleMethod8();
[DllImport("__Internal")] private static extern string sampleMethod9(); }
|
注意,字符串必须在 C 端为 malloc 并返回。
数组
1 2 3 4 5 6 7 8 9 10 11 12 13
| #ifdef __cplusplus extern "C" { #endif void sampleMethod10(int **arrPtr, int *size) { int arr[] = {1, 2, 3}; int *retArr = (int *)malloc(sizeof(arr)); memcpy(retArr, arr, sizeof(arr)); *arrPtr = retArr; *size = sizeof(arr) / sizeof(arr[0]); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| using System; using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern void sampleMethod10(out IntPtr arrPtr, out int size);
private static int[] sampleMethod10Invoker() { IntPtr arrPtr = IntPtr.Zero; int size = 0; sampleMethod10 (out arrPtr, out size); int[] arr = new int[size]; Marshal.Copy (arrPtr, arr, 0, size); return arr; } }
|
- 这很麻烦。 似乎不可能简单地返回它(大小也需要返回),所以我通过引用(out/ref)从C#传递 变量并接收它。 阵列受制于。
- 在 C 端,分配的数组的指针及其大小设置为通过引用传递的变量。
- 最后,将收到的数组复制到 C# 数组。
return IntPtr malloc IntPtr Marshal.Copy
Objective-C 的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @interface Sample : NSObject - (void)test; @end
@implementation Sample - (void)test { NSLog(@"test called."); } @end
#ifdef __cplusplus extern "C" { #endif Sample* sample_init() { Sample *sample = [[Sample alloc] init]; CFRetain((CFTypeRef)sample); return sample; }
void sample_test(Sample *sample) { [sample test]; }
void cfRelease(id *obj) { CFRelease((CFTypeRef)obj); } #ifdef __cplusplus } #endif
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using System; using System.Runtime.InteropServices;
public class Sample { [DllImport("__Internal")] private static extern IntPtr sample_init();
[DllImport("__Internal")] private static extern void sample_test(IntPtr samplePtr);
[DllImport("__Internal")] private static extern void cfRelease(IntPtr samplePtr);
private static void sampleInvoker() { IntPtr objcPtr = sample_init (); sample_test (objcPtr); cfRelease (objcPtr); } }
|
- Objective-C 的实例可以由 IntPtr 在 C# 端维护。
- 当您想在 C# 端管理 Objective-C 实例的生命周期时很有用。
- 请注意,除非对象在此之后返回到 C#,否则 Objective-C 端将被释放。
- 在 C# 构造函数中创建 Objective-C 的实例,在析构函数中 保留并释放它可能是一个好主意。
retain
从 Objective-C 调用 C# 中的方法
Unity发送消息
1
| UnitySendMessage("GameObjectName", "MethodName", "Message to send");
|
UnitySendMessage 调用指定游戏对象中包含的方法。 第三个参数是参数,但只能指定字符串。 此外,它变得异步,并发生一帧的延迟。
使用函数指针在 C# 中传递静态方法
上述方法。 实现起来很麻烦,所以如果没有问题,我觉得还是乖乖使用UnitySendMessage比较好。
构建时的各种支持
将标准 iOS 框架添加到链接
在 Unity 编辑器上选择一个原生资源(.h、.m.mm.a.framework 等),然后 从检查器中检查相应的框架以链接它。
将 iOS 标准库添加到链接(.dylib、.tbd)
由 PostProcessBuild 支持。 使用 Xcode Manipulation API。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class PostBuildProcess { [PostProcessBuild] public static void OnPostProcessBuild (BuildTarget buildTarget, string path) { string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");
PBXProject proj = new PBXProject (); proj.ReadFromString (File.ReadAllText (projPath));
string target = proj.TargetGuidByName ("Unity-iPhone"); proj.AddFileToBuild(target, proj.AddFile("usr/lib/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk)); proj.AddFileToBuild(target, proj.AddFile("usr/lib/libz.tbd", "Frameworks/libz.tbd", PBXSourceTree.Sdk));
File.WriteAllText (projPath, proj.WriteToString ()); } }
|
将第三方框架/库添加到链接
只需将其放在资产/插件/iOS下,它就会被链接。
配置添加到信息列表
由 PostProcessBuild 支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class PostBuildProcess { [PostProcessBuild] public static void OnPostProcessBuild (BuildTarget buildTarget, string path) { string plistPath = Path.Combine(path, "Info.plist"); PlistDocument plist = new PlistDocument(); plist.ReadFromFile(plistPath);
PlistElementDict rootDict = plist.root;
PlistElementArray urlTypesArray = rootDict.CreateArray ("CFBundleURLTypes"); PlistElementDict dict = urlTypesArray.AddDict (); dict.SetString ("CFBundleURLName", "xxxxx"); PlistElementArray schemesArray = dict.CreateArray ("CFBundleURLSchemes"); schemesArray.AddString ("xxxxx");
File.WriteAllText(plistPath, plist.WriteToString()); } }
|
此外,作为ATS对策,
1 2 3 4 5
| <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
|
如果您只想这样做,只需在iOS上打开播放器设置并打开“允许通过HTTP 下载”(默认打开)
在构建时添加的各种标志
在 PostProcessBuild 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class PostBuildProcess {
[PostProcessBuild] public static void OnPostProcessBuild (BuildTarget buildTarget, string path) { string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");
PBXProject proj = new PBXProject (); proj.ReadFromString (File.ReadAllText (projPath));
string target = proj.TargetGuidByName ("Unity-iPhone"); proj.SetBuildProperty (target, "CLANG_ENABLE_MODULES", "YES"); proj.SetBuildProperty (target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");
File.WriteAllText (projPath, proj.WriteToString ()); } }
|
- 如果您不知道要设置的键名称,请使用 . 我认为最快的方法是在xcodeproj中以文本形式打开project.pbxproj并查看内部。
- 您应该能够使用基本的PostProcessBuild编辑pbxproj和info.plist。 即使你不能用 PBXProject 类很好地设置它,你也可以在最坏的情况下替换读取的字符串。
- 如果要将“-fno-objc-arc”标志添加到源,请在 Unity 编辑器中选择 目标源,然后在检查器中将其设置为“编译标志”。