标题中提到的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,没有使用宽字符,为了保证这种情况下的中文可以正确的传递不出乱码,需要对字符串进行转换。闲话不多说,直接上代码。

  1. // UTF编码的多字节转为宽字符
  2. std::wstring TUtf8ToUnicode(const char * pszUtf8Str, unsigned len = -)
  3. {
  4. std::wstring ret;
  5. do
  6. {
  7. if (!pszUtf8Str) break;
  8. // get UTF8 string length
  9. if (- == len)
  10. {
  11. len = strlen(pszUtf8Str);
  12. }
  13. if (len <= ) break;
  14.  
  15. // get UTF16 string length
  16. int wLen = MultiByteToWideChar(CP_UTF8, , pszUtf8Str, len, , );
  17. if ( == wLen || 0xFFFD == wLen) break;
  18.  
  19. // convert string
  20. wchar_t * pwszStr = new(std::nothrow) wchar_t[wLen + ];
  21. if (!pwszStr) break;
  22. pwszStr[wLen] = ;
  23. MultiByteToWideChar(CP_UTF8, , pszUtf8Str, len, pwszStr, wLen + );
  24. ret = pwszStr;
  25. delete[] pwszStr;
  26. } while ();
  27. return ret;
  28. }
  29.  
  30. // std::string => Platform::String
  31. Platform::String ^ Ts2ps(std::string str)
  32. {
  33. return ref new Platform::String(TUtf8ToUnicode(str.c_str()).c_str());
  34. }
  35.  
  36. // 宽字符转为UTF8编码的多字节
  37. std::string TUnicodeToUtf8(const wchar_t* pwszStr)
  38. {
  39. std::string ret;
  40. do
  41. {
  42. if (!pwszStr) break;
  43. size_t len = wcslen(pwszStr);
  44. if (len <= ) break;
  45.  
  46. size_t convertedChars = ;
  47. char * pszUtf8Str = new(std::nothrow) char[len * + ];
  48. if (!pszUtf8Str) break;
  49. WideCharToMultiByte(CP_UTF8, , pwszStr, len + , pszUtf8Str, len * + , , );
  50. ret = pszUtf8Str;
  51. delete[] pszUtf8Str;
  52. } while ();
  53.  
  54. return ret;
  55. }
  56.  
  57. // Platform::String => std::string
  58. std::string Tps2s(Platform::String ^ pstr)
  59. {
  60. if (pstr == nullptr)
  61. return "";
  62. return TUnicodeToUtf8(pstr->Data());
  63. }

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

如何在接口中使用数组,可以参考 https://msdn.microsoft.com/zh-cn/library/hh700131.aspx 这篇文章。里面提到不同场景下分别用什么样的数组,讲的非常详细,这里就不再重复了,重点扒一下遇到的坑。

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

  1. 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

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

  1. IAsyncOperation<Platform::Object^>^ GetArrayAsync()
  2. {
  3. return create_async([]() -> Platform::Object^
  4. {
  5. Array<int>^ arr1 = ref new Array<int>();
  6. for (int i = ; i < arr1->Length; i++)
  7. {
  8. arr1[i] = i + ;
  9. }
  10.  
  11. Platform::Object^ a = arr1;
  12. return a;
  13. });
  14. }

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

UWP中重用C/C++代码时踩过的一些坑的更多相关文章

  1. 使用Ajax中get请求发送Token时踩的那些坑

    在使用惯了各种牛X的插件以后,在使用原生组件写一些小东西的时候总是有踩不完的坑! 今天就来说一说我使用原生ajax请求时踩得坑: 下面是我的代码: var xmlhttp; if (window.XM ...

  2. iOS中重用UITableView单元格时,千万别忘了这个

    不多说,看截图

  3. 当年用httpclient时踩过的那些坑

    一.前言 httpclient是java开发中最常用的工具之一,通常大家会使用其中比较基础的api去调用远程.长期开发爬虫,会接触httpclient不常用的api,同时会遇到各式各样的坑,本文将总结 ...

  4. 在配置tensorflow时踩的无数个坑

    在下午尝试配置tensorflow环境时,遇到了许多天坑,讲真的心态炸了好几次,特此写下这篇记录,希望能给看到朋友一点帮助. 先说一下这抓狂的一天的起因,比赛项目想用SVM进行一下数据分析,除了常规的 ...

  5. (转)libhybris及EGL Platform-在Glibc生态中重用Android的驱动

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/41412587 libhybris主要作用是为了解决libc库的兼容问题,目的是为了在基于G ...

  6. 在word中显示漂亮的代码

    在word中粘贴或写代码时,通常得不到想要的格式,可用‘Notepad++’工具实现. 步骤: (1)安装Notepad++软件,把代码粘贴进去,选择菜单栏中的语言,然后选择相应代码语言,如P-> ...

  7. 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)

    之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指 ...

  8. 在使用<script>嵌入JavaScript代码时,不要在代码中的任何地方出现"</script>"字符串

    在使用<script>嵌入JavaScript代码时,记住不要在代码中的任何地方出现"</script>"字符串.例如浏览器执行下面代码会报错: <s ...

  9. Javascript中的Trait与代码重用

    Javascript中的Trait与代码重用 来源 http://www.ituring.com.cn/article/64103 我们知道,OOP中最普遍的代码重用方式是通过继承,但是,继承有一些缺 ...

随机推荐

  1. C++结构体内存对齐跨平台测试

    测试1,不规则对齐数据. Code: #include <stdio.h> #pragma pack(push) #pragma pack(8) struct Test8 { char a ...

  2. 使用Javascript快速获取URL参数

    首先:原文在这   Quick Tip: Get URL Parameters with JavaScript function getAllUrlParams(url) { var queryStr ...

  3. MySLQ 为数据库远程授权的方法与问题的解决解决方法

    Mysql通过远程的连接工具连接,提示Can't connect to MySQL server (10060).  这个时候我们需要分析,看哪里设置不当而导致的该问题.   工具/原料 mysql数 ...

  4. Vue - 实例

    1.实例属性介绍如下图所示: 具体介绍如下: A.$parent:访问当前组件的父实例 B.$root:访问当前组件的根实例,要是没有的话,则是自己本身 C.$children:访问当前组件实例的直接 ...

  5. 全局变量 HInstance 到底是在什么时候赋值的?

    在学习 资源文件 和 钩子函数 时, 经常用到当前模块句柄(HInstance)这个全局变量. 今天特别想知道, 它到底是在什么时候给赋值的. 输入 HInstance; "Ctrl+鼠标& ...

  6. php js数组问题

    <script type="text/javascript"> var a = new Array(); a = "a"; a = "b& ...

  7. 词法分析程序(C)

    #include <stdio.h> #include <string.h> ],word[],ch; int type,p,i,n,sum; ]={"begin&q ...

  8. 2.1 -1.0 Xcode(发布时间、使用、快捷键、插件相关)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 1.0 Xcode 发布时间 版本 iOS 版本 手机 日期 特殊介绍 Xcode 3.1 ...

  9. ACM: Gym 101047E Escape from Ayutthaya - BFS

    Gym 101047E Escape from Ayutthaya Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%I6 ...

  10. Android开发 SQLite数据库应用笔记(一)

    注意: 1.public Cursor rawQuery(String sql, String[] selectionArgs) Cursor游标是查询后返回的结果集合,游标的意思是指向集合中的某行. ...