本文可能对谁有帮助

如果你正在做将现有的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动态库

我们需要做的包括以下几个方面:

  1. 各类工程到 UWP 的转换
  2. 处理编译问题
  3. 处理磁盘操作问题
  4. 数据类型间的转换
  5. 接口封装问题

开始

首先,请下载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工程的方法,如下:

  1. 打开 DLL 项目中的“项目属性”,并将“配置”设置为“所有配置”;
  2. 在“项目属性”中,在“C/C++”、“常规”选项卡上,将“使用 Windows 运行时扩展”设置为“是 (/ZW)”。这将启用组件扩展 (C++/CX);
  3. 在“解决方案资源管理器”中,选择项目节点,打开快捷菜单,然后选择“重定SDK版本目标”,“确定”;
  4. 在“解决方案资源管理器”中,选择项目节点,打开快捷菜单,然后选择“卸载项目”。然后,在卸载的项目节点上打开快捷菜单,然后选择编辑项目文件。找到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 不支持fopenCreateFile此类操作。用来替换的是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是无法直接使用任意字符串路径进行文件操作的。正确的方式应该是:

  1. 使用FolderPickerFilePicker获取一个StorageFolderStorageFile对象
  2. 将对象加入到权限列表中 AccessCache::StorageApplicationPermissions::FutureAccessList->Add(file);
  3. 如果多模块间传递的是String类型,此时可以从StorageFolderStorageFile对象的Path属性获取String类型路径字符串,之后可以使用该路径字符串转换(见数据类型间的转换)为StorageFolderStorageFile对象,此时权限仍旧有效。

如果需要在某目录下新建文件,则应该使用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>^ *这种写法倒是没有问题。intint *诸如此类,都是可以的,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相关推荐

  1. vs项目移植到linux运行,VS2008项目移植到Linux

    不少人都遇到过这种情况:在Windows下用Visual Studio工具开发的程序需要移植到Linux系统中,做成Linux版本的,但程序比较大,在Linux上又离不开Make,手动编写Makefi ...

  2. 摘录cocos2d-x 从环境搭建到win32项目移植android平台

    软件:cocos2d-x-2.2.3:android-ndk-r9d:adt-bundle-windows-x86_64-20131030:python-2.7.6: 1安装配置python 安装没什 ...

  3. cocos2d-x 菜鸟实习生学习篇(十) win32项目移植到安卓

    再两天就过年啦,这篇博客应该是今年的最后一篇吧.在此提前恭贺大家....元宵节快乐!!!相信很多大牛说的都是新年快乐之类的,但是咱不能跟他们一样啊,咱虽然博客不咋的,但是肯定要体现出自己的身份跟内涵. ...

  4. Win10 UWP开发系列:使用VS2015 Update2+ionic开发第一个Cordova App

    安装VS2015 Update2的过程是非常曲折的.还好经过不懈的努力,终于折腾成功了. 如果开发Cordova项目的话,推荐大家用一下ionic这个框架,效果还不错.对于Cordova.PhoneG ...

  5. Win10 UWP开发中的重复性静态UI绘制小技巧 1

    Win10 UWP开发中的重复性静态UI绘制小技巧 1 原文:Win10 UWP开发中的重复性静态UI绘制小技巧 1 介绍 在Windows 10 UWP界面实现的过程中,有时会遇到一些重复性的.静态 ...

  6. win10 uwp 让焦点在点击在页面空白处时回到textbox中

    原文:win10 uwp 让焦点在点击在页面空白处时回到textbox中 在网上 有一个大神问我这样的问题:在做UWP的项目,怎么能让焦点在点击在页面空白处时回到textbox中? 虽然我的小伙伴认为 ...

  7. win10 uwp 使用 msbuild 命令行编译 UWP 程序

    原文:win10 uwp 使用 msbuild 命令行编译 UWP 程序 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http:// ...

  8. win10 uwp 如何开始写 uwp 程序

    本文告诉大家如何创建一个 UWP 程序. 这是一系列的 uwp 入门博客,所以写的很简单 本文来告诉大家如何创建一个简单的程序 安装 VisualStudio 在开始写 UWP 需要安装 Visual ...

  9. win10 UWP Controls by function

    Windows的 XAML UI 框架提供了很多控件,支持用户界面开发库. 我现在做的一个中文版的,很多都是照着微软写,除了注释 我们先学微软做一个简单的frame,新建Page,里面放title和跳 ...

最新文章

  1. ieee期刊_IEEE期刊的双栏排版中的图片位置问题
  2. linux为什么要交换内存,Linux系统中交换内存是什么?
  3. 国内MySQL技术现状_1024不搬砖,谈谈自己2020剩余两月的学习计划
  4. shell 替换字符串的几种方法,变量替换${},sed,awk
  5. java socket oc_Java Socket编程(三) 服务器Sockets
  6. mybatis(数据库增删改查)
  7. django基础知识之验证码:
  8. fiddler抓不到PC端微信小程序的包
  9. eclipse导入html页面乱码,Eclipse导入项目乱码问题(中文乱码)
  10. 从why到how,双态IT的落地联想为何能走在最前列
  11. 关于形而上学与形而下学之区别及关系
  12. 解读Android12 CDD中针对隔离环境(TEE)的要求
  13. TDengine 入坑
  14. Android 仿soul首页星球旋转,可上下左右方向旋转
  15. 面试计算机应用技术自我介绍,计算机应用专业面试的自我介绍
  16. java-锁_自我理解
  17. Zrou株肉与造洋饭书及摩卡站联名发布“平衡新生”
  18. Java人员该如何站稳脚跟 需要具备哪些技能
  19. 【阴沟翻船】AVPlayer设置完毕马上调用play方法会导致播放没声音
  20. 制度通用模板-数据安全组织建设及人员管理办法

热门文章

  1. jquery.Inputmask 插件用法(中文API文档)
  2. 文件操作,fopen打不开文件
  3. 开源免费代码_01_基于Arduino的ESP-NOW,ESP32发送指令、ESP-01S接收指令,通过ESP-01S继电器实现LED灯开关控制_公羽兴
  4. 科普:iOS开发如何做外部测试
  5. OJ:L3-001 凑零钱 DFS
  6. Word发表blog格式模板
  7. OrmLite for android--Ormlite的大概介绍
  8. MySQL federated存储引擎--访问在远程数据库的表中的数据,而不是本地的表
  9. 华为的核心供应商名单凸显出国内企业对美国芯片的依赖
  10. Pr:导出设置之管理显示色域体积及内容光线级别