Win32 C++項目移植到 Win10 UWP
來源鏈接:https://blog.csdn.net/conan98/article/details/52095938
本文可能對誰有幫助
如果你正在做將現(xiàn)有的Win32 靜態(tài)庫 或 DLL 工程移植到Win10 UWP(通用 Windows) 環(huán)境,這篇文章可能會對你有幫助。
補充:本文雖然是針對WIN10的UWP平臺準備的移植解決方案,但其中的方法和思想依然可以用在WIN7、WIN8、WIN11的軟件開發(fā)上。
概述
在VS2015的 新建項目 -> 已安裝 -> 模板 -> Visual C++ -> Windows -> 通用 頁面,包含幾個我們需要關心的工程類型:空白應用(通用 Windows)、DLL(通用 Windwos)、靜態(tài)庫(通用 Windows)、Windows 運行時組件(通用 Windows)。
根據(jù)工程說明可以知道,DLL(通用 Windwos)和靜態(tài)庫(通用 Windows)可以被空白應用(通用 Windows)和Windows 運行時組件(通用 Windows)使用,并且是語言相關的,不能跨語言調用。
而Windows 運行時組件(通用 Windows)可以被空白應用(通用 Windows)使用,是語言無關的。也就是說,不管是C++還是C#開發(fā)的應用都可以調用Windows 運行時組件(通用 Windows)。
知道了這一點,那么我們來看下問題,博主(C++程序員,未接觸過C#及WinPhone相關開發(fā))遇到的情況是這樣的:將現(xiàn)有的 Win32 平臺DLL 移植到 UWP 平臺,供采用 C# 開發(fā)的Win Phone App使用,而該 DLL 還依賴其它 C++靜態(tài)LIB庫 和 C動態(tài)庫。
我們需要做的包括以下幾個方面:
1.各類工程到 UWP 的轉換
2.處理編譯問題
3.處理磁盤操作問題
4.數(shù)據(jù)類型間的轉換
5.接口封裝問題
開始
首先,請下載Universal Windows Platform (UWP) App samples,將會對你有莫大的幫助。
為方便描述,做如下約定:1.被移植的DLL定義為a.dll
2.a.dll依賴的C++靜態(tài)LIB庫定義為c++.lib
3.a.dll依賴的C動態(tài)庫定義為c.dll
4.通用 Windows版組件加 _rt 后綴以示區(qū)別
5.Windows 運行時組件(通用 Windows)外殼定義為 shell_rt.dll
各類工程到UWP的轉換
注意: 創(chuàng)建 Windows 運行時組件(通用 Windows) 工程時,必須保證工程內的最外層命名空間名字和最終生成的dll名字(包括winmd文件)完全一致,這也是官方的要求。
通過閱讀官方文檔(https://msdn.microsoft.com/zh-cn/library/mt186162.aspx)得知在不重新創(chuàng)建工程的情況下將現(xiàn)有工程轉換為UWP工程的方法,如下:
1.打開 DLL 項目中的“項目屬性”,并將“配置”設置為“所有配置”;
2.在“項目屬性”中,在“C/C++”、“常規(guī)”選項卡上,將“使用 Windows 運行時擴展”設置為“是 (/ZW)”。這將啟用組件擴展 (C++/CX);
3.在“解決方案資源管理器”中,選擇項目節(jié)點,打開快捷菜單,然后選擇“重定SDK版本目標”,“確定”;
4.在“解決方案資源管理器”中,選擇項目節(jié)點,打開快捷菜單,然后選擇“卸載項目”。然后,在卸載的項目節(jié)點上打開快捷菜單,然后選擇編輯項目文件。找到WindowsTargetPlatformVersion 元素并將其替換為以下元素。然后關閉 .vcxproj 文件,再次打開快捷菜單,然后選擇“重新加載項目”。現(xiàn)在,解決方案資源管理器會將該項目標識為 通用 Windows 項目。
其中, 第3步在官方文檔中沒有,但如果不做第3步,第4步中的WindowsTargetPlatformVersion 元素可能無法找到。以上涉及的SDK版本(10.0.10156.0),可以根據(jù)自己的環(huán)境需要進行調整。第2步中打開的“/ZW”選項,只能用于 C++項目,如果是 C語言項目的話,需要將該選項設置會 否;或者,如果C++項目中包含C文件,可以單獨將 C文件 設置為 否。
處理編譯問題
工程轉換完后開始處理編譯問題。因為不喜歡stdafx.h這個文件名中的 afx 三個字母,博主一直是把工程的預編譯功能關閉,涉及此文件的問題這里不做討論。
在編譯zlib靜態(tài)庫的ARM版本時,遇到了如下編譯問題:
雙擊后可看到以下代碼(corect.h),各種宏交錯在一起:
編譯存在此類問題的代碼文件頭部增加如下定義,可解決:
由于編譯問題各式各樣,沒有重點,只能哪里編譯不過改哪里??傊热皇荂++ 程序員,相信你可以搞定。
處理磁盤操作問題
UWP 不支持fopen,CreateFile此類操作。用來替換的是CreateFile2,用法和CreateFile類似。但該API只能處理特殊目錄,例如程序安裝目錄、圖片、文檔、視頻等。對于磁盤中任意的目錄,都沒有操作權限。因此,對于期望可操作任意目錄文件的需求,只能放棄使用CreateFile2,改用以下UWP組件中的磁盤操作類:
其它相關類請在類名上按F12打開對象瀏覽器查看。
看過類里的函數(shù)之后可以發(fā)現(xiàn)大部分函數(shù)都有Asyn后綴,帶Asyn后綴的函數(shù)均為異步函數(shù),Windows不希望UI線程及其它某些線程因為同步調用導致響應遲鈍。C++中異步函數(shù)的調用方式大致為:
[代碼片段 A]
線程A調用Test函數(shù),通過create_task創(chuàng)建一個task對象,并將一個lamda函數(shù)(位于then()中,[this, file]中聲明的變量可在函數(shù)中使用)作為委托傳遞給task對象的then方法,并繼續(xù)向下執(zhí)行并退出Test函數(shù)。task中的file->GetParentAsyn()操作實際由線程B調用,待函數(shù)返回后,再將結果交由線程A執(zhí)行委托函數(shù)[15-20]行。
科普: ^ 這個符號讀作hat,這里用來聲明句柄對象。String ^str;這里的str就是一個String的句柄類型,初始值或無對象指向時為nullptr,釋放時可以使用delete str 也可以讓作用域控制自動釋放??梢院唵蔚睦斫鉃轭愃浦悄苤羔?。
異步方法雖然可以避免對線程A的阻塞,但實際使用中并不方便。因為,大部分情況下,我們都會為耗時的網(wǎng)絡或磁盤操作專門開啟線程處理,而不是直接使用UI線程操作。因此如果都使用這種異步方式,在某些場景下,代碼會寫的很反人類,例如下面這個比較完整的文件讀取操作:
[代碼片段 B]
昔日Win32的一個CreateFile操作,在這里變的無比繁瑣。而且,上面?zhèn)魅胍粋€String路徑打開文件的方式因為權限文件,并不可行。
在系統(tǒng)中,除幾個個別目錄(安裝目錄、圖片目錄、視頻目錄等)在App配置權限后可用于直接操作權限外,App是無法直接使用任意字符串路徑進行文件操作的。正確的方式應該是:
1.使用FolderPicker或FilePicker獲取一個StorageFolder或StorageFile對象
2.將對象加入到權限列表中 AccessCache::StorageApplicationPermissions::FutureAccessList->Add(file);
3.如果多模塊間傳遞的是String類型,此時可以從StorageFolder或StorageFile對象的Path屬性獲取String類型路徑字符串,之后可以使用該路徑字符串轉換(見數(shù)據(jù)類型間的轉換)為StorageFolder或StorageFile對象,此時權限仍舊有效。
如果需要在某目錄下新建文件,則應該使用FolderPicker獲取StorageFolder對象,將對象加入權限列表,再使用該StorageFolder對象創(chuàng)建文件。
考慮到在做代碼移植時,調整某些線程的同異步模式將會導致原有框架結構變的混亂,因此,出現(xiàn)了下面的用法:
[代碼片段 C]
[12-14]行只是示范IBuffer對象數(shù)據(jù)到字符數(shù)組數(shù)據(jù)的轉換,更多的類型轉換見數(shù)據(jù)類型間的轉換。
注意: 這種 task.wait() 的調用方式并不能應用到所有線程。參見ppltask.h文件的task_status _Wait();函數(shù)及其中的_IsNonBlockingThread函數(shù)內部實現(xiàn)。請自行調試實驗各類線程調用wait()中的_IsNonBlockingThread函數(shù)時的返回情況。
(經(jīng)過驗證,以上這種寫文件的寫法效率較低,在頻繁調用時尤為明顯,包括前面列出的對GetFileFromPathAsync 的調用)
至此,有關磁盤操作的大致情況如上所述。
數(shù)據(jù)類型間的轉換
接口封裝問題
UWP 組件的接口不同于Win32 DLL 的導出接口,UWP 的接口是一個winmd 文件,包含語言無關類型信息MetaData(元數(shù)據(jù))。使用組件時只需要 xxx.dll 和 xxx.winmd 兩個文件,不需要頭文件。
在導出接口時,首先需要最外層有一個和庫文件名相同的命名空間名,導出的類需要聲明成如下格式(需帶public ref sealed聲明 ):
因為接口可能被跨語言使用,因此下面這種接口參數(shù)的寫法就要避免:
這種寫法只能被C++使用,如果C#調用的話,會出現(xiàn)崩潰。不過,Platform::Array<unsigned char>^ *這種寫法倒是沒有問題。int、int *諸如此類,都是可以的,int *對于C# 的調用,使用out進行修飾。
如果接口需要傳遞回調函數(shù),需要封裝成類,可以從接口導出一個interface 修飾的類: