Unity里使用包含C#事件的Dll
2015-05-28 11:41
423 查看
原文地址:http://jacksondunstan.com/articles/2949
正文:
dll——像flash里的swc一样——是一种方便的把你的代码转换成可复用模块的方法。不幸的是,Unity因为自身的原因(不支持JIT编译),导致在IOS和其他一些平台上会崩溃。最大的问题就是你在dll里使用C#事件。本文探究了这个问题发生的原因并找出一个简单可行的解决方案。让我们开始学习如何安全的在Unity的自定义dll里使用C#事件吧!
让我们写一个C#事件的简单例子来说明这个问题:
当你在ios或者其他不支持JIT的平台上运行这个Unity程序的时候你会得到下面的报错:
为了找出为啥报错,让我们使用mono3.12来编译这个dll,mono3.12是本文写作时的当前版本。如果你使用MonoDevelop/Xamarin Studio,Mono正是编译你的dll所用的编译器。编译好dll后,我们使用.NET反编译工具查看这个dll,可以看到下面这些用来添加和移除事件回调的函数:
就像你看到的那样,编译器产生的代码包含了对
但是为什么在非dll的代码里不会发生这个问题呢?把相同的代码放到Unity的Asset目录下,反编译
我们看到Unity4.6生成的代码和mono3.12是完全不同的。这些生成的代码不包含
vs产生的代码和mono3.12类似,但是有一点不同。关键是它仍然调用了
鉴于编译器产生的代码都会触发JIT,让我们自己写一套代码添加和删除事件监听。这是我们的方案:
有两点需要注意的,第一也是最重要的一点,没有任何函数使用
生成的代码和Unity编译器生成的一样,也就是说解决了JIT的问题。
该方法由两点不足。首先要多写点代码,因为你定义了自己的
这个解决方案允许我们在Unity dll里使用C#事件,并且对性能影响很小,却保证了应用程序在非JIT平台上能正确运行。如果你知道其他解决方案,请在评论里留言给我:)
正文:
dll——像flash里的swc一样——是一种方便的把你的代码转换成可复用模块的方法。不幸的是,Unity因为自身的原因(不支持JIT编译),导致在IOS和其他一些平台上会崩溃。最大的问题就是你在dll里使用C#事件。本文探究了这个问题发生的原因并找出一个简单可行的解决方案。让我们开始学习如何安全的在Unity的自定义dll里使用C#事件吧!
让我们写一个C#事件的简单例子来说明这个问题:
public class Normal { //声明一个事件回调用的委托 public delegate void SomethingHandler(); // 声明一个事件,使用刚才声明的委托 public event SomethingHandler OnSomething = () => {}; private void DispatchTheEvent() { // 派发事件 OnSomething(); } }
当你在ios或者其他不支持JIT的平台上运行这个Unity程序的时候你会得到下面的报错:
ExecutionEngineException: Attempting to JIT compile method '(wrapper managed-to-native) System.Threading.Interlocked:CompareExchange |
public void add_OnSomething(SomethingHandler value) { SomethingHandler handler2; SomethingHandler onSomething = this.OnSomething; do { handler2 = onSomething; onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, (SomethingHandler) Delegate.Combine(handler2, value), onSomething); } while (onSomething != handler2); } public void remove_OnSomething(SomethingHandler value) { SomethingHandler handler2; SomethingHandler onSomething = this.OnSomething; do { handler2 = onSomething; onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, (SomethingHandler) Delegate.Remove(handler2, value), onSomething); } while (onSomething != handler2); }
就像你看到的那样,编译器产生的代码包含了对
System.Threading.Interlocked.CompareExchange的调用,他会尝试JIT编译。
但是为什么在非dll的代码里不会发生这个问题呢?把相同的代码放到Unity的Asset目录下,反编译
Library/ScriptAssemblies/Assembly-CSharp.dll,会得到下面的代码:
[MethodImpl(MethodImplOptions.Synchronized)] public void add_OnSomething(SomethingHandler value) { this.OnSomething = (SomethingHandler) Delegate.Combine(this.OnSomething, value); } [MethodImpl(MethodImplOptions.Synchronized)] public void remove_OnSomething(SomethingHandler value) { this.OnSomething = (SomethingHandler) Delegate.Remove(this.OnSomething, value); }
我们看到Unity4.6生成的代码和mono3.12是完全不同的。这些生成的代码不包含
CompareExchange的调用,因此也不会试图进行JIT编译,也就没有错误啦。不幸的是,为了解决这个问题,我们只能把一部分代码移出dll。但是如果我就是想把这部分代码放到dll里,那就试试另一个编译器:Microsoft Visual Studio 2013:
public void add_OnSomething(SomethingHandler value) { SomethingHandler handler2; SomethingHandler onSomething = this.OnSomething; do { handler2 = onSomething; SomethingHandler handler3 = (SomethingHandler) Delegate.Combine(handler2, value); onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, handler3, handler2); } while (onSomething != handler2); } public void remove_OnSomething(SomethingHandler value) { SomethingHandler handler2; SomethingHandler onSomething = this.OnSomething; do { handler2 = onSomething; SomethingHandler handler3 = (SomethingHandler) Delegate.Remove(handler2, value); onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, handler3, handler2); } while (onSomething != handler2); }
vs产生的代码和mono3.12类似,但是有一点不同。关键是它仍然调用了
CompareExchange,仍然会产生JIT的问题。
鉴于编译器产生的代码都会触发JIT,让我们自己写一套代码添加和删除事件监听。这是我们的方案:
public class Workaround { // 声明一个委托 public delegate void SomethingHandler(); // 创建一个委托的实例 private SomethingHandler somethingInvoker = () => {}; //创建事件,并自定义add和remove函数 public event SomethingHandler OnSomething { // 把监听监听器添加到委托 add { somethingInvoker += value; } // 把监听器从委托移除 remove { somethingInvoker -= value; } } private void DispatchTheEvent() { // 派发事件 somethingInvoker(); } }
add和
remove函数不太重要,但是定义了它们就会阻止编译器产生它们自己的实现。我们再把dll反编译,下面是mono3.12编译器产生的代码:
public void add_OnSomething(SomethingHandler value) { this.somethingInvoker = (SomethingHandler) Delegate.Combine(this.somethingInvoker, value); } public void remove_OnSomething(SomethingHandler value) { this.somethingInvoker = (SomethingHandler) Delegate.Remove(this.somethingInvoker, value); }
有两点需要注意的,第一也是最重要的一点,没有任何函数使用
CompareExchange。此外,第二点,这些代码和Unity4.6编译器生成的代码一样。这意味着我们有效的绕开了JIT的问题。下一步,让我们看看在Visual Studio 2013里这个方法好使么:
public void add_OnSomething(SomethingHandler value) { this.somethingInvoker = (SomethingHandler) Delegate.Combine(this.somethingInvoker, value); } public void remove_OnSomething(SomethingHandler value) { this.somethingInvoker = (SomethingHandler) Delegate.Remove(this.somethingInvoker, value); }
生成的代码和Unity编译器生成的一样,也就是说解决了JIT的问题。
该方法由两点不足。首先要多写点代码,因为你定义了自己的
add和
remove方法。下面是一个比较:
public class Normal { public delegate void SomethingHandler(); public event SomethingHandler OnSomething = () => {}; } public class Workaround { public delegate void SomethingHandler(); private SomethingHandler somethingInvoker = () => {}; public event SomethingHandler OnSomething { add { somethingInvoker += value; } remove { somethingInvoker -= value; } } }第二点不足是
Interlocked.CompareExchange函数更高效。但是仅在添加和移除事件时才会有效率损失,派发事件都比它们频繁,因此综上所述我们认为它应用程序的性能影响很小。
这个解决方案允许我们在Unity dll里使用C#事件,并且对性能影响很小,却保证了应用程序在非JIT平台上能正确运行。如果你知道其他解决方案,请在评论里留言给我:)
相关文章推荐
- C# 生成dll 导入unity中使用
- 在C#中使用C++的DLL,并且在DLL中包含有opencv的代码,通过“障眼法”操作
- unity 3d 使用C#的事件/委托机制
- 关于unity使用dll和c#扩展方法的小结
- Unity使用C#调用C++dll传递指针参数、接收指针返回值
- UnityC#注册C++Dll事件回调
- Tolua使用笔记六:在lua中操作C#的委托事件与在lua中对Unity的GameObject的操作
- Tolua使用笔记六:在lua中操作C#的委托事件与在lua中对Unity的GameObject的操作
- [Unity&C#&委托事件&未实例化]使用委托事件的时候事件对象未实例化
- C#连接Oracle数据库(直接引用dll使用)
- JAVA通过JNI调用C#dll方法说明(包含示例)
- c#调用java代码(jar转化成dll):ikvm 使用注意事项
- C#绑定Delphi的Dll多事件出错,ErrorCode=-2147220990
- Unity中使用委托/事件实现GameObject之间的通信
- 【转】C# 中使用 ThoughtWorks.QRCode.dll 生成指定尺寸和边框宽度的二维码
- SqlServer开启CLR使用(C#)DLL实现实时Socket通知
- Unity C# 使用反射,利用字符串作为泛型参数调用泛型方法。
- Unity&C#的委托事件总结
- Unity使用Socket与后台连接,包含Json的读写
- C++工程代码打包的dll在C#工程上使用注意点!变量对应关系与dll调用出错情况。