博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
UWP中重用C/C++代码时踩过的一些坑
阅读量:6807 次
发布时间:2019-06-26

本文共 4502 字,大约阅读时间需要 15 分钟。

标题中提到的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 KitCert就可以。   

现成的dll文件很可能是无法通过商店认证的,快速检测方法是可以将dll文件作为内容添加到工程中,打包并用WACK验证,大部分情况下会在支持的API那里验证不过。 这里列出了部分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:命名空间和文件名 

运行时组件创建完成时,根命名空间和工程的名称是一样的,直接使用没有问题。但有的同学希望修改命名空间的名称以符合公司的命名规范,此时,有以下几点需要注意:   

一个是如何修改命名空间,不仅仅要将代码中命名空间修改,还要修改工程的根命名空间,方法是打开属性窗口,单击工程,就可以看到根命名空间。这两处要保持致,否则引用的工程无法编译通过。   

再一个是修改完命名空间后,编译产生的dllwinmd文件名称不同,在按文件添加完引用后,主工程可以编译通过,但是会有APPX1707的警告,并且运行时会发生崩溃。分析后发现,原来是VS默认引用的时候会用winmd文件的名称去找dll文件,而现在两个文件名称不一样,所以会找不到实现。解决方案有两种,一种是修改运行时组件工程选项,使得输出的文件名保持一致;另一种是在主工程添加完winmd文件的引用后,手动编辑一下主工程的工程文件,在引用处加入如下图所示的一行,强制指定对应的实现在哪个dll文件中。

    

坑4:字符串转换

C/C++实现的库中,有相当一部分还是用的std::string,没有使用宽字符,为了保证这种情况下的中文可以正确的传递不出乱码,需要对字符串进行转换。闲话不多说,直接上代码。   

// UTF编码的多字节转为宽字符std::wstring TUtf8ToUnicode(const char * pszUtf8Str, unsigned len = -1){    std::wstring ret;    do    {        if (!pszUtf8Str) break;        // get UTF8 string length        if (-1 == len)        {            len = strlen(pszUtf8Str);        }        if (len <= 0) break;        // get UTF16 string length        int wLen = MultiByteToWideChar(CP_UTF8, 0, pszUtf8Str, len, 0, 0);        if (0 == wLen || 0xFFFD == wLen) break;        // convert string          wchar_t * pwszStr = new(std::nothrow) wchar_t[wLen + 1];        if (!pwszStr) break;        pwszStr[wLen] = 0;        MultiByteToWideChar(CP_UTF8, 0, pszUtf8Str, len, pwszStr, wLen + 1);        ret = pwszStr;        delete[] pwszStr;    } while (0);    return ret;}// std::string => Platform::StringPlatform::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 <= 0) break;        size_t convertedChars = 0;        char * pszUtf8Str = new(std::nothrow) char[len * 3 + 1];        if (!pszUtf8Str) break;        WideCharToMultiByte(CP_UTF8, 0, pwszStr, len + 1, pszUtf8Str, len * 3 + 1, 0, 0);        ret = pszUtf8Str;        delete[] pszUtf8Str;    } while (0);    return ret;}// Platform::String => std::stringstd::string Tps2s(Platform::String ^ pstr){    if (pstr == nullptr)        return "";    return TUnicodeToUtf8(pstr->Data());}

   

坑5:数组在异步中怎么传 

如何在接口中使用数组,可以参考 这篇文章。里面提到不同场景下分别用什么样的数组,讲的非常详细,这里就不再重复了,重点扒一下遇到的坑。   

数组作为WinRT接口的返回值时,如果该接口是同步的,一切正常;当接口是异步的时候,编译器就会抛出 

error C3952: 'Platform::Array
': WinRT does not support 'in/out' arrays. Use 'const Array
^' for 'in' and 'WriteOnlyArray
' or 'Array
^*' for 'out' on public APIs

按照该提示进行修改,怎么都不好使,后经过多方求解,才知道这种情况下需要进行装箱操作才可以。示例代码出下: 

IAsyncOperation
^ GetArrayAsync(){ return create_async([]() -> Platform::Object^ { Array
^ arr1 = ref new Array
(10); for (int i = 0; i < arr1->Length; i++) { arr1[i] = i + 1; } Platform::Object^ a = arr1; return a; });}

 

以上是我们在开发过程中遇到的一些坑和解决方案,如有理解不当之处,请小伙伴指出来,另外小伙伴们也可以在评论中积极分享下你们遇到过哪些坑,是如何解决的,大家共同进步。

转载地址:http://uotwl.baihongyu.com/

你可能感兴趣的文章
ARM 和 RISC-V 公然开撕,GNOME 之父指责 ARM
查看>>
日本将推出“隐形列车”,你要去体验一番吗?
查看>>
15 篇最新 AI 论文来袭!NLP、CV...人人有份 | 本周值得读
查看>>
Maven查看依赖树
查看>>
装饰器
查看>>
第13章 用序列化保存模型
查看>>
从SQL Server到MySQL,近百亿数据量迁移实战
查看>>
RDS PG如何安装DTS需要的增量迁移插件?
查看>>
阿里的Atlas组件化框架
查看>>
《物联网框架ServerSuperIO教程》-19.设备驱动和OPC Client支持mysql、oracle、sqlite、sqlserver的持久化。v3.6.4版本发布...
查看>>
Forrester告诫CIO们:谨慎应用区块链网络
查看>>
使用Eclipse连接SAP云平台上的HANA数据库实例
查看>>
java.io.IOException: Connection reset by peer
查看>>
MySQL · myrocks · 事务锁分析
查看>>
Mysql中,int(10)和int(11)的区别
查看>>
观点对立,无关虚拟现实技术
查看>>
VC++动态链接库(DLL)编程深入浅出(四)(转)
查看>>
Cortex-M3启动深度解析
查看>>
PKI/CA 技术的介绍
查看>>
只知道人工智能远远不够 下一件大事将是边缘计算!
查看>>