System.Net.Http.dll在Unity3D(UWP)的IL2CPP模式使用的解决方案

前言

Unity3D升级到2017的版本后可以支持.Net 4.6的特性,所以引入.Net的基于任务的异步编程模型是十分具有吸引力的(更好的性能加上更优美的代码),为此我在新项目中使用了很多Async异步方法去实现网络与I/O,去替换Unity自带的协程机制的网络和I/O。其中System.Net.Http.dll这个托管库是微软封装的一层http请求接口,所以我准备在项目中使用该库进行网络请求(坑非常多,理想很丰满,现实很骨感),使用该库的项目基于UWP并且使用IL2CPP运行时,折腾两天多层层剥茧终于搞定,结论很简单,但是思考和调试的学习过程还是颇有收获,所以把个人的解决过程和思路记录一下。


好,下面进入正题~

坑一

首先去哪里获取System.Net.Http.dll这个库呢,既然是托管库,首当其冲便是NuGet,搜索System.Net.Http,轻松加愉快。
这里写图片描述
第一个坑,发现从NuGet中下载下来的包有很多和Unity不兼容,如果你将该库加入到工程中提示"The type 'System.IDisposable' is defined in an assembly that is not referenced. Consider adding a reference to assembly 'System.Runtime, Version=4.0.20.0, Culture=neutral...'"之类的错误,就说明出现了依赖问题,我认为这是Unity使用的Mono版本的和该库的依赖不匹配,即导入的该托管库依赖于更高版本的或Mono没有使用的库。笔者使用lib/net46下的库没有出现该问题,该问题在不同的Unity版本应该会略有不同,可以每个版本都试一下,实在找不出也有一条后路,在Unity的安装路径下,Editor\Data\MonoBleedingEdge\lib\mono\unityaot也有Mono实现的System.Net.Http.dll,二者差异不大。具体可对比下图:
这里写图片描述

坑二

导入了库,于是可以在Editor环境下欢快地使用,功能强大,代码简洁,感觉很爽呢。然而这才刚刚开始…使用IL2CPP打包成uwp工程后,又出现了第二个坑,使用该库进行http请求时,unity的Debug控制台报错DllNotFoundException:Unable to load DLL 'advapi32.dll':The specified module could not be found,看样子这个库使用了P/Invoke链接了win32 API,但是为什么无法加载advapi32.dll?这是UWP通用应用的沙盒环境的限制,果然微软大法厉害,不让应用程序使用沙盒环境(即安装目录)外的库,好吧好吧,你不让我用,我手动拷贝一份依赖dll到我的工程里总行了吧,拷了advapi32.dll到工程中,打包运行,又提示缺少IPHLPAPI.dll,好,我拷,继续添加IPHLPAPI.dll。哇可以了!一阵喜出望外之后,觉得事情并没有这么简单… 把该项目创建uwp安装包,emmmm,果然凉了,没有通过商店的认证审核,如下图:
这里写图片描述
也就是微软说了: 你这引入的库里面调用了我不允许你调用的win32api,因为不能让你利用这些api做坏事(破坏沙盒环境),如果想实现类似的功能,我这里有很多windows运行时组件把这些api封装了一下,可以替换其中一部分api。啊啊啊,走到这步大概思路已经清晰了,看样子这个System.Net.Http.dll里面使用了uwp禁止的win32api,查了些资料发现微软果然提供了替代品Windows.Web.Http可以实现相同的功能。看起来已经挖到尽头了,但是!到这个地步我仍然有不爽的或者有疑问的点,不搞清楚绝不罢休

  1. 为什么System.Net.Http.dll作为一个官方的托管库(而不是原生库)会使用P/Invoke,它不应当是完全依赖于.Net Framework中的实现吗,再由.Net运行时去打通和操作系统的通道
  2. 为什么UWP原生开发时可以使用System.Net.Http.dll而并没有什么异常,在Unity中使用就会报错呢

答案呼之欲出了,原因就是我一直忽视的IL2CPP运行时 ,没错,这个项目中使用了IL2CPP编译,将所有的托管代码转化为c++实现,就此我猜测正是该过程(将托管代码本地化)造成的这个问题。
打开UWP工程的IL2CPPOutput工程,发现Bulk_System.Net.Http_0.cpp赫然在列,正是该文件的某些函数使用了禁忌win32api,怎么去找到它呢,打开一看好家伙… 四万多行代码,猛然想到Debug调试时的报错信息,想必是Unity内置的一个异常表示来dll加载错误,如果我找到此函数在该位置打断点,命中后直接查看函数调用栈岂不是一切都明朗了。直接全局搜索unable to load dll,果然只有一处,在PlatformInvoke.cpp下,这应该就是处理平台调用的模块。
这里写图片描述
上面的注释第三行很扎心:表明诸如uwp的平台不支持运行时加载系统级动态链接库的… 好吧好吧,不支持就不支持,但是我还想知道到底哪里使用win32 api,我就想post/get没有想做坏事啊!打上断点看看调用栈:
这里写图片描述
由底向上分析函数调用,从UnityAction.Invoke开始触发自己的业务逻辑,LoginModule.CheckPhoneNumber(一个需要联网的业务),HttpRequestClient.PostAsync(自己封装一层抽象,内部使用HttpClient),再往上就是HttpClientHandler.CreateWebRequest(HttpClient内部使用的是较为底层的HttpWebRequest),到这为止都很正常都没超纲,之后重点来了:WebRequest.InternalDefaultWebProxy,应该对应的WebRequest.DefaultWebProxy属性,作用是获取或设置全局HTTP代理,继续向上,WebProxy.CreateDefaultProxy,。

大致查了一下:当新创建一个WebRequest实例时(通过Create方法),会自动初始化其Proxy属性,而它还有一个DefaultWebProxy属性,当用户没有手动设置Proxy属性时,则WebRequest会使用DefaultWebProxy作为其Proxy;而DefaultWebProxy是读取项目的app.config文件来进行初始化;当没有app.config文件,或者没有在app.config中配置Proxy时,DefaultWebProxy就会去读取Internet Explorer (IE)的代理设置。

果不其然,我根本就没考虑过代理之类的东西,也没有在代码中配置过,WebRequest去创建DefaultWebProxy时会读取本地的IE代理配置,看看后面的函数调用,InitializeRegistryGlobalProxy, Win32RegistryApi.OpenSubKey,直奔去取注册表键值去了。

既然如此,我不用代理设置还不行嘛,双击进入WebRequest_get_InternalDefaultWebProxy, 在WebRequest的构造函数里(在Bulk_System_4.cpp中),将这两行注释掉,不获取也不setProxy,因为用不到嘛
这里写图片描述
重新编译,运行!还是报错了,这次查看函数调用栈,用同样的思路,发现是HttpClientHandler_get_CookieContainer也是同样的问题,注释掉!在Bulk_System.Net.Http_0.cpp中注释掉如下两行,不去设置CookieContainer而已嘛
这里写图片描述
再次重新编译,运行,成功!

猜你喜欢

转载自blog.csdn.net/aawoe/article/details/79702659