UWP中重用C/C++代码时踩过的一些坑
标题中提到的UWP,主要是指用C#来写UWP的主工程,开发过程中可能需要调用C/C++实现的库。
为什么需要调用C/C++的库呢,举个例子,开源库OpenSSL实现了许多加密算法,稳定快速,我们想在应用中调用;再比如,应用已经在iOS/Android平台上线并稳定运行了,我想把它们的库拿来给UWP版本用。
经过一些项目的实践,我总结了下图所示的几种集成方式:
图中红叉不代表此路不通,只是我们今天不讨论P/Invoke方式。我们今天主要通过WinRT来打通C#和C/C++之间的调用,因此,我们的选择应是前两种路线。前两种路线的区别在于,一个直接用原有代码创建了一个WinRT,另一个是使用WinRT将原DLL的接口进行包装,方便C#调用。
坑1:代码移植
下载需要的开源库,按照默认编译选项,很轻松得到了dll文件,然后新建一个WinRT,将dll包装一下,提供给C#使用,简单几步就将开源库集成到UWP中了,但是,在发布到商店时,可耻的失败了。
由于我们的UWP应用最终是要发布到商店中的,所以必须要能通过商店的认证才可以。开发环境本地就可以检测x86架构的安装包是否符合商店要求,使用的工具是Windows App Cert Kit,后面我们简称它为WACK,该工具随Win10 SDK一起安装。你可以通过小娜快速启动这个工具,直接在小娜中输入Windows App Cert Kit或Cert就可以。
现成的dll文件很可能是无法通过商店认证的,快速检测方法是可以将dll文件作为内容添加到工程中,打包并用WACK验证,大部分情况下会在支持的API那里验证不过。 https://msdn.microsoft.com/zh-cn/library/windows/apps/jj606124.aspx 这里列出了部分UWP中不能使用的API。所以,我们需要修改工程或代码,将这些不能使用的API都替换掉。这是一个非常痛苦的事情,我要一行一行代码来找哪些API不能用吗?我要自己修改编译选项,加各种宏吗?太可怕了!
一种简单的做法是这样的,新建一个Windows通用工程,如下图所示,这个工程默认是配置好编译选项和宏的,编出来的库可以符合商店的认证规则。然后将原代码添加到工程中,编译,输出窗口中会打印出哪些函数是不能用的,我们可以快速定位并进行相应替换或代码更改。
坑2:引用
如果是直接引用的工程文件,此处可能不会出坑,因为VS会很贴心地将所有必需的文件复制到该去的位置;如果是引用的编译出来的winmd文件,那么就要注意一些事情了。
我们来看一下编译输出的文件,如果是第一种路线,输出一个winmd和一个dll,将它们放在一起,只需要引用winmd文件就可以了,VS知道编译的时候带上dll文件;如果是第二种路线,输出一个winmd和两个dll,多出来的dll是动态库的工程生成的,此时除了引用winmd文件外,还需要将额外的dll文件直接添加到主工程中,同时将文件属性中的"生成操作"设置为"内容"。
完成上面的引用后,我们还需要在主工程中添加 Visual C++ 2015 Runtime for Universal Windows Platform Apps 和 Microsoft Universal CRT Debug Runtime ,具体添加方式是在工程上右键- > 添加 -> 引用 -> Universal Windows -> 扩展。如果不添加,有可能在运行时会收到莫名其妙的找不到文件的崩溃。
最后,提一下如何设置X86/ARM分别引用不同的文件,在添加完对winmd文件的引用后,用记事本打开工程文件,找到刚才引用的winmd文件这里,在路径中使用$(PlatformTarget)来代替X86/X64/ARM,使用$(Configuration)来代替Debug/Release,这样,VS在编译的时候就可以为不同的目标使用不同的库文件了。
坑3:命名空间和文件名
运行时组件创建完成时,根命名空间和工程的名称是一样的,直接使用没有问题。但有的同学希望修改命名空间的名称以符合公司的命名规范,此时,有以下几点需要注意:
一个是如何修改命名空间,不仅仅要将代码中命名空间修改,还要修改工程的根命名空间,方法是打开属性窗口,单击工程,就可以看到根命名空间。这两处要保持致,否则引用的工程无法编译通过。
再一个是修改完命名空间后,编译产生的dll和winmd文件名称不同,在按文件添加完引用后,主工程可以编译通过,但是会有APPX1707的警告,并且运行时会发生崩溃。分析后发现,原来是VS默认引用的时候会用winmd文件的名称去找dll文件,而现在两个文件名称不一样,所以会找不到实现。解决方案有两种,一种是修改运行时组件工程选项,使得输出的文件名保持一致;另一种是在主工程添加完winmd文件的引用后,手动编辑一下主工程的工程文件,在引用处加入如下图所示的一行,强制指定对应的实现在哪个dll文件中。
坑4:字符串转换
C/C++实现的库中,有相当一部分还是用的std::string,没有使用宽字符,为了保证这种情况下的中文可以正确的传递不出乱码,需要对字符串进行转换。闲话不多说,直接上代码。
// UTF编码的多字节转为宽字符
std::wstring TUtf8ToUnicode(const char * pszUtf8Str, unsigned len = -)
{
std::wstring ret;
do
{
if (!pszUtf8Str) break;
// get UTF8 string length
if (- == len)
{
len = strlen(pszUtf8Str);
}
if (len <= ) break; // get UTF16 string length
int wLen = MultiByteToWideChar(CP_UTF8, , pszUtf8Str, len, , );
if ( == wLen || 0xFFFD == wLen) break; // convert string
wchar_t * pwszStr = new(std::nothrow) wchar_t[wLen + ];
if (!pwszStr) break;
pwszStr[wLen] = ;
MultiByteToWideChar(CP_UTF8, , pszUtf8Str, len, pwszStr, wLen + );
ret = pwszStr;
delete[] pwszStr;
} while ();
return ret;
} // std::string => Platform::String
Platform::String ^ Ts2ps(std::string str)
{
return ref new Platform::String(TUtf8ToUnicode(str.c_str()).c_str());
} // 宽字符转为UTF8编码的多字节
std::string TUnicodeToUtf8(const wchar_t* pwszStr)
{
std::string ret;
do
{
if (!pwszStr) break;
size_t len = wcslen(pwszStr);
if (len <= ) break; size_t convertedChars = ;
char * pszUtf8Str = new(std::nothrow) char[len * + ];
if (!pszUtf8Str) break;
WideCharToMultiByte(CP_UTF8, , pwszStr, len + , pszUtf8Str, len * + , , );
ret = pszUtf8Str;
delete[] pszUtf8Str;
} while (); return ret;
} // Platform::String => std::string
std::string Tps2s(Platform::String ^ pstr)
{
if (pstr == nullptr)
return "";
return TUnicodeToUtf8(pstr->Data());
}
坑5:数组在异步中怎么传
如何在接口中使用数组,可以参考 https://msdn.microsoft.com/zh-cn/library/hh700131.aspx 这篇文章。里面提到不同场景下分别用什么样的数组,讲的非常详细,这里就不再重复了,重点扒一下遇到的坑。
数组作为WinRT接口的返回值时,如果该接口是同步的,一切正常;当接口是异步的时候,编译器就会抛出
error C3952: 'Platform::Array<int,1> ': WinRT does not support 'in/out' arrays. Use 'const Array<T>^' for 'in' and 'WriteOnlyArray<T>' or 'Array<T>^*' for 'out' on public APIs
按照该提示进行修改,怎么都不好使,后经过多方求解,才知道这种情况下需要进行装箱操作才可以。示例代码出下:
IAsyncOperation<Platform::Object^>^ GetArrayAsync()
{
return create_async([]() -> Platform::Object^
{
Array<int>^ arr1 = ref new Array<int>();
for (int i = ; i < arr1->Length; i++)
{
arr1[i] = i + ;
} Platform::Object^ a = arr1;
return a;
});
}
以上是我们在开发过程中遇到的一些坑和解决方案,如有理解不当之处,请小伙伴指出来,另外小伙伴们也可以在评论中积极分享下你们遇到过哪些坑,是如何解决的,大家共同进步。
UWP中重用C/C++代码时踩过的一些坑的更多相关文章
- 使用Ajax中get请求发送Token时踩的那些坑
在使用惯了各种牛X的插件以后,在使用原生组件写一些小东西的时候总是有踩不完的坑! 今天就来说一说我使用原生ajax请求时踩得坑: 下面是我的代码: var xmlhttp; if (window.XM ...
- iOS中重用UITableView单元格时,千万别忘了这个
不多说,看截图
- 当年用httpclient时踩过的那些坑
一.前言 httpclient是java开发中最常用的工具之一,通常大家会使用其中比较基础的api去调用远程.长期开发爬虫,会接触httpclient不常用的api,同时会遇到各式各样的坑,本文将总结 ...
- 在配置tensorflow时踩的无数个坑
在下午尝试配置tensorflow环境时,遇到了许多天坑,讲真的心态炸了好几次,特此写下这篇记录,希望能给看到朋友一点帮助. 先说一下这抓狂的一天的起因,比赛项目想用SVM进行一下数据分析,除了常规的 ...
- (转)libhybris及EGL Platform-在Glibc生态中重用Android的驱动
原文地址:http://blog.csdn.net/jinzhuojun/article/details/41412587 libhybris主要作用是为了解决libc库的兼容问题,目的是为了在基于G ...
- 在word中显示漂亮的代码
在word中粘贴或写代码时,通常得不到想要的格式,可用‘Notepad++’工具实现. 步骤: (1)安装Notepad++软件,把代码粘贴进去,选择菜单栏中的语言,然后选择相应代码语言,如P-> ...
- 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)
之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指 ...
- 在使用<script>嵌入JavaScript代码时,不要在代码中的任何地方出现"</script>"字符串
在使用<script>嵌入JavaScript代码时,记住不要在代码中的任何地方出现"</script>"字符串.例如浏览器执行下面代码会报错: <s ...
- Javascript中的Trait与代码重用
Javascript中的Trait与代码重用 来源 http://www.ituring.com.cn/article/64103 我们知道,OOP中最普遍的代码重用方式是通过继承,但是,继承有一些缺 ...
随机推荐
- 移动端webapp自适应实践(css雪碧图制作小工具实践)图文并茂
为什么要写这个 以前写过关于webapp自适应屏幕的文章(链接),不过写的大多数群众看不懂,所以来个图文并茂的版本.虽然只是一个简单的页面,不过在做的过程中也遇到了一些问题,也算是好事吧! 该示例gi ...
- eclipse 创建maven web项目
参考:http://www.cnblogs.com/hongwz/p/5456616.html eclipse 创建maven web项目
- Mac下golang开发环境配置
go语言在开发效率和运行效率中的优势让很多人青睐,所以有倾向打算转向go语言的开发. 下面介绍在Mac OS X中golang的开发环境配置. 1.安装brew brew是一个mac下的由ruby开发 ...
- 1.Android 视图及View绘制分析笔记之setContentView
自从1983年第一台图形用户界面的个人电脑问世以来,几乎所有的PC操作系统都支持可视化操作,Android也不例外.对于所有Android Developer来说,我们接触最多的控件就是View.通常 ...
- Centos7中安装Mysql及配置
CentOS 7 安装 MySQL 首先检查 MySQL 是否已安装 yum list installed | grep mysql 如果有的话 就全部卸载 yum -y remove +数据库名称 ...
- 关于在django框架里取已登录用户名字的问题
在 views里取值是这样的 request.user.username而在模板页面取值是这样的:{{request.user}} 判断是否通过验证是这样的 {% if request.user.is ...
- 安卓智能POS终端手持机PDA应用仓库出入库,移库,盘点,销售开单系统
随着移动互联网的兴起,目前仓储管理所面临的的问题可以迎刃而解,WMS仓库系统解决方案通过智能终端扫描条码技术应用解决了工作量大导致工作效率不高,以及数据实时传输等问题,该方案主要提供仓库出入库,移库, ...
- Jedis 使用范例
public class RedisUtil { Logger logger = LoggerFactory.getLogger(RedisUtil.class); private JedisPool ...
- C++-Qt【4】-CheckBox on QListView
引用:http://www.qtcentre.org/threads/47119-checkbox-on-QListView QListWidgetItem *item = new QListWidg ...
- PO,VO,BO,DTO,POJO(POCO),DAO的区别(转载)
PO:persistant object持久对象 最形象的理解就是一个PO就是数据库中的一条记录.好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象. BO:business object业 ...