Win32 C++项目移植到 Win10 UWP
本文可能对谁有帮助
如果你正在做将现有的Win32 静态库
或 DLL
工程移植到Win10 UWP(通用 Windows)
环境,这篇文章可能会对你有帮助。
概述
在VS2015的 新建项目
-> 已安装
-> 模板
-> Visual C++
-> Windows
-> 通用
页面,包含几个我们需要关心的工程类型:空白应用(通用 Windows)
、DLL(通用 Windwos)
、静态库(通用 Windows)
、Windows 运行时组件(通用 Windows)
。
根据工程说明可以知道,DLL(通用 Windwos)
和静态库(通用 Windows)
可以被空白应用(通用 Windows)
和Windows 运行时组件(通用 Windows)
使用,并且是语言相关的,不能跨语言调用。
而Windows 运行时组件(通用 Windows)
可以被空白应用(通用 Windows)
使用,是语言无关的。也就是说,不管是C++
还是C#
开发的应用都可以调用Windows 运行时组件(通用 Windows)
。
知道了这一点,那么我们来看下问题,博主(C++程序员,未接触过C#及WinPhone相关开发)遇到的情况是这样的:将现有的 Win32 平台DLL
移植到 UWP
平台,供采用 C#
开发的Win Phone APP使用,而该 DLL
还依赖其它 C++静态LIB库
和 C动态库
。
我们需要做的包括以下几个方面:
- 各类工程到
UWP
的转换 - 处理编译问题
- 处理磁盘操作问题
- 数据类型间的转换
- 接口封装问题
开始
首先,请下载Universal Windows Platform (UWP) app samples,将会对你有莫大的帮助。
为方便描述,做如下约定:
- 被移植的DLL定义为a.dll
- a.dll依赖的C++静态LIB库定义为c++.lib
- a.dll依赖的C动态库定义为c.dll
通用 Windows
版组件加 _rt 后缀以示区别- Windows 运行时组件(通用 Windows)外壳定义为 shell_rt.dll
各类工程到UWP
的转换
我们整体的工程关系转换为:a.dll -> a_rt.libc++.lib -> c++_rt.libc.dll -> c_rt.lib旧的依赖关系:app 依赖a.dll,a.dll 链接c++.lib,a.dll 依赖c.dll;新的依赖关系:app 依赖shell_rt.dll,shell_rt.dll 链接a_rt.lib、c++_rt.lib、c_rt.lib,并且shell_rt.dll 负责重新封装a.dll的接口。app 可由 C++ 或 C# 开发。
注意: 创建
Windows 运行时组件(通用 Windows)
工程时,必须保证工程内的最外层命名空间名字和最终生成的dll名字(包括winmd文件)完全一致,这也是官方的要求。
通过阅读 官方文档 得知在不重新创建工程的情况下将现有工程转换为UWP工程的方法,如下:
- 打开 DLL 项目中的“
项目属性
”,并将“配置
”设置为“所有配置
”; - 在“
项目属性
”中,在“C/C++
”、“常规
”选项卡上,将“使用 Windows 运行时扩展
”设置为“是 (/ZW)
”。这将启用组件扩展 (C++/CX); - 在“
解决方案资源管理器
”中,选择项目节点,打开快捷菜单,然后选择“重定SDK版本目标
”,“确定
”; - 在“
解决方案资源管理器
”中,选择项目节点,打开快捷菜单,然后选择“卸载项目
”。然后,在卸载的项目节点上打开快捷菜单,然后选择编辑项目文件。找到WindowsTargetPlatformVersion
元素并将其替换为以下元素。然后关闭 .vcxproj 文件,再次打开快捷菜单,然后选择“重新加载项目
”。现在,解决方案资源管理器会将该项目标识为通用 Windows
项目。
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<WindowsTargetPlatformVersion>10.0.10156.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.10156.0</WindowsTargetPlatformMinVersion>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
其中, 第3步在官方文档中没有,但如果不做第3步,第4步中的WindowsTargetPlatformVersion
元素可能无法找到。以上涉及的SDK版本(10.0.10156.0)
,可以根据自己的环境需要进行调整。第2步中打开的“/ZW
”选项,只能用于 C++项目
,如果是 C语言
项目的话,需要将该选项设置会 否
;或者,如果C++项目
中包含C文件
,可以单独将 C文件
设置为 否
。
处理编译问题
工程转换完后开始处理编译问题。因为不喜欢stdafx.h
这个文件名中的 afx
三个字母,博主一直是把工程的预编译功能关闭,涉及此文件的问题这里不做讨论。
在编译zlib静态库的ARM版本时,遇到了如下编译问题:
fatal error C1189: #error: Compiling Desktop applications for the ARM platform is not supported.
双击后可看到以下代码(corect.h
),各种宏交错在一些:
// Verify that the ARM Desktop SDK is available when building an ARM Desktop app
#ifdef _M_ARM#if _CRT_BUILD_DESKTOP_APP && !_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE#error Compiling Desktop applications for the ARM platform is not supported.#endif
#endif
编译存在此类问题的代码文件头部增加如下定义,可解决:
#ifdef _M_ARM#define WINAPI_FAMILY WINAPI_FAMILY_PHONE_APP
#endif
由于编译问题各式各样,没有重点,只能哪里编译不过改哪里。总之,既然是C++ 程序员,相信你可以搞定。
处理磁盘操作问题
UWP
不支持fopen
,CreateFile
此类操作。用来替换的是CreateFile2
,用法和CreateFile
类似。但该API
只能处理特殊目录,例如程序安装目录
、图片
、文档
、视频
等。对于磁盘中任意的目录,都没有操作权限。因此,对于期望可操作任意目录文件的需求,只能放弃使用CreateFile2
,改用以下UWP
组件中的磁盘操作类:
Windows::Storage::StorageFile
Windows::Storage::StorageFolder
Windows::Storage::Streams::IRandomAccessStream
其它相关类请在类名上按F12
打开对象浏览器查看。
看过类里的函数之后可以发现大部分函数都有Asyn后缀,带Asyn后缀的函数均为异步函数,Windows不希望UI线程及其它某些线程因为同步调用导致响应迟钝。C++中异步函数的调用方式大致为:
[代码片段 A]
// 使用 task 和以下命名空间中的类时需开启 /ZW 选项,即开启 C++/CX 支持#include <collection.h>#include <ppltasks.h>using namespace concurrency;using namespace Platform;using namespace Windows::Storage;using namespace Windows::Storage::Streams;// 从一个文件对象获取其目录对象 void Test(StorageFile ^file){create_task(file->GetParentAsync()).then([this, file](StorageFolder ^parentFolder){if(parentFolder != nullptr){// do something}});}
线程A调用Test
函数,通过create_task
创建一个task
对象,并将一个lamda
函数(位于then()中,[this, file]中声明的变量可在函数中使用)作为委托传递给task
对象的then
方法,并继续向下执行并退出Test
函数。task
中的file->GetParentAsyn()
操作实际由线程B调用,待函数返回后,再将结果交由线程A执行委托函数[15-20]行
。
科普:
^
这个符号读作hat
,这里用来声明句柄对象。String ^str;
这里的str
就是一个String
的句柄类型,初始值或无对象指向时为nullptr
,释放时可以使用delete str
也可以让作用域控制自动释放。可以简单的理解为类似智能指针。
异步方法虽然可以避免对线程A的阻塞,但实际使用中并不方便。因为,大部分情况下,我们都会为耗时的网络或磁盘操作专门开启线程处理,而不是直接使用UI线程操作。因此如果都使用这种异步方式,在某些场景下,代码会写的很反人类,例如下面这个比较完整的文件读取操作:
[代码片段 B]
// 头文件、命名空间省略,变量判断、异常处理省略void ReadBytesFromFile(String ^strFilePath){// 根据文件路径获取文件对象; create_task(StorageFile::GetFileFromPathAsync(strFilePath)).then([](StorageFile ^file){if (file != nullptr){// 以读写的方式打开文件; create_task(file->OpenAsync(FileAccessMode::ReadWrite)).then([](IRandomAccessStream ^stream){if (stream != nullptr){auto buf = ref new Buffer(10); // 读取10个字节; create_task(stream->ReadAsync(buf, 10, InputStreamOptions::None)).then([buf](IBuffer ^buffer) {// buf 和 buffer 中包含读取到的数据; });}});}});}
昔日Win32
的一个CreateFile
操作,在这里变的无比繁琐。而且,上面传入一个String
路径打开文件的方式因为权限文件,并不可行。
在系统中,除几个个别目录(安装目录、图片目录、视频目录等)在app配置权限后可用于直接操作权限外,app是无法直接使用任意字符串路径进行文件操作的。正确的方式应该是:
- 使用
FolderPicker
或FilePicker
获取一个StorageFolder
或StorageFile
对象 - 将对象加入到权限列表中
AccessCache::StorageApplicationPermissions::FutureAccessList->Add(file);
- 如果多模块间传递的是String类型,此时可以从
StorageFolder
或StorageFile
对象的Path
属性获取String
类型路径字符串,之后可以使用该路径字符串转换(见数据类型间的转换)为StorageFolder
或StorageFile
对象,此时权限仍旧有效。
如果需要在某目录下新建文件,则应该使用FolderPicker
获取StorageFolder
对象,将对象加入权限列表,再使用该StorageFolder
对象创建文件。
考虑到在做代码移植时,调整某些线程的同异步模式将会导致原有框架结构变的混乱,因此,出现了下面的用法:
[代码片段 C]
// 将file文件中偏移5开始的10个字符写入到偏移2开始的位置;auto taskOpen = create_task(file->OpenAsync(FileAccessMode::ReadWrite));if(taskOpen.wait() == canceled)return false;IRandomAccessStream ^stream = taskOpen.get();stream->Seek(5);auto buffer = ref new Buffer(10);auto taskRead = create_task(stream->ReadAsync(buffer, 10, InputStreamOptions::None));if(taskRead.wait() == canceled)return false;auto data = ref new Array<byte>(buffer->Length);auto reader = DataReader::FromBuffer(buffer);reader->ReadBytes(data);stream->Seek(2);auto taskWrite = create_task(stream->WriteAsync(buffer));if(taskWrite.wait() == canceled)return false;auto taskFlush = create_task(stream->FlushAsync());if(taskFlush.wait() == canceled)return false;
[12-14]行
只是示范IBuffer
对象数据到字符数组数据的转换,更多的类型转换见数据类型间的转换。
注意: 这种
task.wait()
的调用方式并不能应用到所有线程。参见ppltask.h
文件的task_status _Wait();
函数及其中的_IsNonBlockingThread
函数内部实现。请自行调试实验各类线程调用wait()
中的_IsNonBlockingThread
函数时的返回情况。
(经过验证,以上这种写文件的写法效率较低,在频繁调用时尤为明显,包括前面列出的对GetFileFromPathAsync
的调用)
至此,有关磁盘操作的大致情况如上所述。
数据类型间的转换
Platform::Array<unsigned char> ^UnsignedChar2Array(unsigned char *pBuffer, unsigned int uSize)
{return ref new Platform::Array<unsigned char>(pBuffer, 10);
}std::wstring PlatformString2StdWstring(Platform::String ^str)
{return std::wstring(str->Data());
}std::string Unicode2Utf8(Platform::String ^str)
{std::wstring wstrTemp(str->Data());std::string strUtf8;int iUtf8Len = ::WideCharToMultiByte(CP_UTF8, 0, wstrTemp.c_str(), wstrTemp.length(), NULL, 0, NULL, NULL);if (0 == iUtf8Len)return "";char* pBuf = new char[iUtf8Len + 1];memset(pBuf, 0, iUtf8Len + 1);::WideCharToMultiByte(CP_UTF8, 0, wstrTemp.c_str(), wstrTemp.length(), pBuf, iUtf8Len, NULL, NULL);strUtf8 = pBuf;delete[] pBuf;return strUtf8;
}using namespace Windows::Storage::Streams;
IBuffer ^UnsignedChar2Buffer(unsigned char *pBuffer, unsigned int uSize)
{DataWriter writer;writer.WriteBytes(Platform::ArrayReference<uint8>(pBuffer, uSize));return writer.DetachBuffer();
}void Buffer2UnsignedChar(IBuffer ^buffer, unsigned char **pBuffer, unsigned int *uSize)
{DataReader ^reader = DataReader::FromBuffer(buffer);*uSize = buffer->Length;*pBuffer = new uint8[*uSize];reader->ReadBytes(Platform::ArrayReference<uint8>(*pBuffer, *uSize));
}
接口封装问题
UWP 组件的接口不同于Win32 DLL 的导出接口,UWP 的接口是一个winmd 文件,包含语言无关类型信息MetaData
(元数据)。使用组件时只需要 xxx.dll 和 xxx.winmd 两个文件,不需要头文件。
在导出接口时,首先需要最外层有一个和库文件名相同的命名空间名,导出的类需要声明成如下格式(需带public
ref
sealed
声明 ):
namespace test // 组件名为test.dll
{public ref class CInterface sealed{}
}
因为接口可能被跨语言使用,因此下面这种接口参数的写法就要避免:
void Func(Platform::String ^*pStr);
这种写法只能被C++使用,如果C#调用的话,会出现崩溃。不过,Platform::Array<unsigned char>^ *
这种写法倒是没有问题。int
、int *
诸如此类,都是可以的,int *
对于C#
的调用,使用out
进行修饰。
// C++方式的接口导出函数声明
void Func1(int *pParam);
void Func2(const Platform::Array<unsigned char>^ inArray);
void Func3(Platform::Array<unsigned char>^ *outArray);
// C#看到的接口声明
// void Func1(out int pParam);
// void Func2(byte[] inArray);
// void Func3(out byte[] outArray);// C#方式的接口调用
Int32 param = 0;
Func1(out param);byte[] inArray;
Func2(inArray);byte[] outArray;
Func3(out outArray);
如果接口需要传递回调函数,需要封装成类,可以从接口导出一个interface
修饰的类:
namespace test
{public interface ICallback{public:virtual void func() = 0;}public ref class CInterface sealed{void RegCallback(ICallback ^callback){// 对于callback我做了一层回调封装映射,由此处的ICallback ^ 类型与内部原有的C++ 回调形成映射关系(中间过渡)// 避免C++/CX 语法深入内部}}
}
// C# 使用时
class CCallback : test.ICallback
{public void func(){// do something}
}CCallback callback = new CCallback();
test.CInterface inter = new test.CInterface();
inter.RegCallback(callback);
Win32 C++项目移植到 Win10 UWP相关推荐
- vs项目移植到linux运行,VS2008项目移植到Linux
不少人都遇到过这种情况:在Windows下用Visual Studio工具开发的程序需要移植到Linux系统中,做成Linux版本的,但程序比较大,在Linux上又离不开Make,手动编写Makefi ...
- 摘录cocos2d-x 从环境搭建到win32项目移植android平台
软件:cocos2d-x-2.2.3:android-ndk-r9d:adt-bundle-windows-x86_64-20131030:python-2.7.6: 1安装配置python 安装没什 ...
- cocos2d-x 菜鸟实习生学习篇(十) win32项目移植到安卓
再两天就过年啦,这篇博客应该是今年的最后一篇吧.在此提前恭贺大家....元宵节快乐!!!相信很多大牛说的都是新年快乐之类的,但是咱不能跟他们一样啊,咱虽然博客不咋的,但是肯定要体现出自己的身份跟内涵. ...
- Win10 UWP开发系列:使用VS2015 Update2+ionic开发第一个Cordova App
安装VS2015 Update2的过程是非常曲折的.还好经过不懈的努力,终于折腾成功了. 如果开发Cordova项目的话,推荐大家用一下ionic这个框架,效果还不错.对于Cordova.PhoneG ...
- Win10 UWP开发中的重复性静态UI绘制小技巧 1
Win10 UWP开发中的重复性静态UI绘制小技巧 1 原文:Win10 UWP开发中的重复性静态UI绘制小技巧 1 介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态 ...
- win10 uwp 让焦点在点击在页面空白处时回到textbox中
原文:win10 uwp 让焦点在点击在页面空白处时回到textbox中 在网上 有一个大神问我这样的问题:在做UWP的项目,怎么能让焦点在点击在页面空白处时回到textbox中? 虽然我的小伙伴认为 ...
- win10 uwp 使用 msbuild 命令行编译 UWP 程序
原文:win10 uwp 使用 msbuild 命令行编译 UWP 程序 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http:// ...
- win10 uwp 如何开始写 uwp 程序
本文告诉大家如何创建一个 UWP 程序. 这是一系列的 uwp 入门博客,所以写的很简单 本文来告诉大家如何创建一个简单的程序 安装 VisualStudio 在开始写 UWP 需要安装 Visual ...
- win10 UWP Controls by function
Windows的 XAML UI 框架提供了很多控件,支持用户界面开发库. 我现在做的一个中文版的,很多都是照着微软写,除了注释 我们先学微软做一个简单的frame,新建Page,里面放title和跳 ...
最新文章
- ieee期刊_IEEE期刊的双栏排版中的图片位置问题
- linux为什么要交换内存,Linux系统中交换内存是什么?
- 国内MySQL技术现状_1024不搬砖,谈谈自己2020剩余两月的学习计划
- shell 替换字符串的几种方法,变量替换${},sed,awk
- java socket oc_Java Socket编程(三) 服务器Sockets
- mybatis(数据库增删改查)
- django基础知识之验证码:
- fiddler抓不到PC端微信小程序的包
- eclipse导入html页面乱码,Eclipse导入项目乱码问题(中文乱码)
- 从why到how,双态IT的落地联想为何能走在最前列
- 关于形而上学与形而下学之区别及关系
- 解读Android12 CDD中针对隔离环境(TEE)的要求
- TDengine 入坑
- Android 仿soul首页星球旋转,可上下左右方向旋转
- 面试计算机应用技术自我介绍,计算机应用专业面试的自我介绍
- java-锁_自我理解
- Zrou株肉与造洋饭书及摩卡站联名发布“平衡新生”
- Java人员该如何站稳脚跟 需要具备哪些技能
- 【阴沟翻船】AVPlayer设置完毕马上调用play方法会导致播放没声音
- 制度通用模板-数据安全组织建设及人员管理办法
热门文章
- jquery.Inputmask 插件用法(中文API文档)
- 文件操作,fopen打不开文件
- 开源免费代码_01_基于Arduino的ESP-NOW,ESP32发送指令、ESP-01S接收指令,通过ESP-01S继电器实现LED灯开关控制_公羽兴
- 科普:iOS开发如何做外部测试
- OJ:L3-001 凑零钱 DFS
- Word发表blog格式模板
- OrmLite for android--Ormlite的大概介绍
- MySQL federated存储引擎--访问在远程数据库的表中的数据,而不是本地的表
- 华为的核心供应商名单凸显出国内企业对美国芯片的依赖
- Pr:导出设置之管理显示色域体积及内容光线级别