c++跨平台開發技術總結


一、前言

博主初入c++開發,對技術的了解深度不足,如果編寫內容有出錯的,歡迎指出。

二、跨平台簡介

這里的跨平台主要指windows、Android和iOS上的開發。PC用dll加載,安卓用 .so,ios用.a。

如果我們開發一個通用版本的sdk,在windows上開發之后運行沒問題,但是當復制到Android stuido或者xcode發現各種報錯。原因就是因為c++在不同平台上存在差異。

安卓和ios上的c++代碼主要參考linux的語法,所以下面文章在總結c++語法的時候通常用linux來表述。

本文主要就是介紹在這三個平台開發上的區別和問題總結。

三、跨平台開發基礎

在了解差異之前,我們先要了解一些知識,用來區分或者打通開發流程。

術語介紹

  • android studio,安卓開發工具,后續簡稱as
  • xcode,蘋果開發工具
  • visual studio,博主用的c++開發的ide,后續簡稱vs

平台宏定義

用於區分 安卓,ios,windows
注意ifdef和if的區別

#ifdef _WIN32
   //表示windows系統,32位或64位
   #ifdef _WIN64
      //64位windows系統
   #endif
#elif __APPLE__
    #if TARGET_IPHONE_SIMULATOR
         // iOS模擬器
    #elif TARGET_OS_IPHONE
        // iOS設備
    #elif TARGET_OS_MAC
        // 其他mac系統
    #else
        // 未支持的其他系統
    #endif
#elif __ANDROID__
	//安卓系統
#elif __linux
    // linux
#elif __unix
    // Unix
#elif __posix
    // POSIX
#endif

unicode開發

為了避免不同平台編碼不同導致的亂碼問題,通常會轉成unicode編碼來做

首先需要切換解決方案的字符集:
配置屬性 -> 常規 -> 字符集 -> 使用 Unicode 字符集

cmake指令是:add_definitions(-DUNICODE -D_UNICODE)

類型:wstring(字符串), wchar_t(字符)

// 常量字符初始化
wstring a = L"啊啊啊啊啊";
wchar_t b[] = {L'哈', L'呵'};

unicode操作集合見附件,windows可以用通用,但是移動端就不行了,需要用UNICODE那一列的方法。

對外接口設計盡可能使用基礎類型

比如如果要返回string類型的值,暴露的接口需要改成const char*/char*類型

因為string是c++語言獨有的類型,其他語言解析需要經過額外處理,所以轉成最基本的類型更好

其他的如vector等,同理

ps:如果確實有返回整個類/結構的需求,有以下幾種設計方法:
1、每個值,單獨設置一個接口
2、對外暴露模板類,然后在其他語言的膠水層處理類的數據
3、json大法好

四、跨平台編程上的差異

主要介紹linux(安卓+ios)和windows中c++的語法差異

prgram once指令在linux下不起作用

在windows開發時,為了防止一個頭文件include,從而導致沖突,我們可能會在.h文件上加 #prgram once 預編譯指令。

但是這個用法在linux不起作用(在as和xcode上起作用,目前博主未出現過模塊導入沖突的情況),需要改成 #ifndef + #define 的形式來處理
樣例:

#ifndef _MAIN_H_
#define _MAIN_H_
#endif

需要注意宏定義不要和其他的頭文件沖突,不然第二個同文件導入失敗不太好查。

編碼區別

linuxs上是utf8編碼,windows上是gbk,因此在處理中文字符串的時候,需要注意區別處理。
方法:轉寬字節(unicode),再轉成另外一個編碼

#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
using namespace std;

#ifdef _WIN32
#include <windows.h>

string GbkToUtf8(const char *src_str)
{
	int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len);
	len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
	string strTemp = str;
	if (wstr) delete[] wstr;
	if (str) delete[] str;
	return strTemp;
}

string Utf8ToGbk(const char *src_str)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
	wchar_t* wszGBK = new wchar_t[len + 1];
	memset(wszGBK, 0, len * 2 + 2);
	MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
	len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
	char* szGBK = new char[len + 1];
	memset(szGBK, 0, len + 1);
	WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
	string strTemp(szGBK);
	if (wszGBK) delete[] wszGBK;
	if (szGBK) delete[] szGBK;
	return strTemp;
}
#endif

long類型內存差異

long類型的變量在32位和64位Windows上都是4個字節,而在64位Linux系統上占8字節

文件/文件夾操作上的差異

文件內容操作

因為windows和linux都有fstream,所以都可以用open來對文件內容進行操作,操作平台互通

文件路徑操作

路徑操作主要分為三個方法:access(判斷路徑存在),mkdir(創建文件夾),rmdir(刪除文件夾)

windows可以使用前綴帶_的函數(比如_access),但是linux不行

還有就是頭文件有差異,導入的時候注意區分

詳細差異可以看這個:https://blog.csdn.net/NCEPUautomation/article/details/108304619

文件夾遍歷

頭文件和操作接口都不一樣,詳細見附錄

linux沒itoa()函數

linux有atoi,但是沒itoa,就很nb……

可以用snprintf代替實現

#include <stdlib.h>
#include <stdio.h>

int number = 429496729;
char str[25];
snprintf(str, 25, "%d", number);
printf("integer = %d string = %s\n", number, str);

五、跨平台開發過程中遇到的疑難雜症

主要總結在安卓和ios開發過程中,遇到的非語法問題。

代碼長度過長,as出現容量不足的報錯

clang++.exe: error: clang frontend command failed due to signal (use -v to see invocation)
Android clang version 5.0.300080 (base on LLVM 5.0.300080)
Target: aarch 64-none-linux-android

ndk版本過小,注意升級

博主之前使用的是16版本的ndk,后面升級到21版本之后,就沒這個問題了

不同平台橋接層代碼生成方案

swig

SWIG (Simplified Wrapper and Interface Generator) ,即簡化包以及接口生成器

目前博主用的就是這個,生成橋接代碼包裝原生代碼,用以支持其他語言如python、java等語言調用

ps:具體使用的博客待補充

Djinni

Djinni是Dropbox開發的工具,可幫助利用C ++內置的代碼發布到iOS和Android平台

目前博主沒用到,后續准備調研下

ps:具體調研或使用的博客待補充

總結

文獻參考

unicode字符定義和函數對照表: https://blog.csdn.net/venom_snake/article/details/88066475
不同平台文件遍歷操作: https://blog.csdn.net/wh445306/article/details/106685269

附錄

unicode字符定義和函數對照表

ANSI UNICODE 通用 說明
數據類型
(char.h) (wchar.h) (tchar.h)
char wchar_t TCHAR
char * wchar_t * TCHAR*
LPSTR LPWSTR LPTSTR
LPCSTR LPCWSTR LPCTSTR
字符串轉換
atoi _wtoi _ttoi 把字符串轉換成整數(int)
atol _wtol _ttol 把字符串轉換成長整型數(long)
atof _wtof _tstof 把字符串轉換成浮點數(double)
itoa _itow _itot 將任意類型的數字轉換為字符串
字符串操作
strlen wcslen _tcslen 獲得字符串的數目
strcpy wcscpy _tcscpy 拷貝字符串
strncpy wcsncpy _tcsncpy 類似於strcpy/wcscpy,同時指定拷貝的數目
strcmp wcscmp _tcscmp 比較兩個字符串
strncmp wcsncmp _tcsncmp 類似於strcmp/wcscmp,同時指定比較字符字符串的數目
strcat wcscat _tcscat 把一個字符串接到另一個字符串的尾部
strncat wcsncat _tcsnccat 類似於strcat/wcscat,而且指定粘接字符串的粘接長度.
strchr wcschr _tcschr 查找子字符串的第一個位置
strrchr wcsrchr _tcsrchr 從尾部開始查找子字符串出現的第一個位置
strpbrk wcspbrk _tcspbrk 從一字符字符串中查找另一字符串中任何一個字符第一次出現的位置
strstr wcsstr/wcswcs _tcsstr 在一字符串中查找另一字符串第一次出現的位置
strcspn wcscspn _tcscspn 返回不包含第二個字符串的的初始數目
strspn wcsspn _tcsspn 返回包含第二個字符串的初始數目
strtok wcstok _tcstok 根據標示符把字符串分解成一系列字符串
wcswidth 獲得寬字符串的寬度
wcwidth 獲得寬字符的寬度
字符串測試
isascii iswascii _istascii 測試字符是否為ASCII 碼字符, 也就是判斷c 的范圍是否在0 到127 之間
isalnum iswalnum _istalnum 測試字符是否為數字或字母
isalpha iswalpha _istalpha 測試字符是否是字母
iscntrl iswcntrl _istcntrl 測試字符是否是控制符
isdigit iswdigit _istdigit 測試字符是否為數字
isgraph iswgraph _istgraph 測試字符是否是可見字符
islower iswlower _istlower 測試字符是否是小寫字符
isprint iswprint _istprint 測試字符是否是可打印字符
ispunct iswpunct _istpunct 測試字符是否是標點符號
isspace iswspace _istspace 測試字符是否是空白符號
isupper iswupper _istupper 測試字符是否是大寫字符
isxdigit iswxdigit _istxdigit 測試字符是否是十六進制的數字
大小寫轉換
tolower towlower _totlower 把字符轉換為小寫
toupper towupper _totupper 把字符轉換為大寫
字符比較
strcoll wcscoll _tcscoll 比較字符串
日期和時間轉換
strftime wcsftime _tcsftime 根據指定的字符串格式和locale設置格式化日期和時間
strptime 根據指定格式把字符串轉換為時間值, 是strftime的反過程
打印和掃描字符串
printf wprintf _tprintf 使用vararg參量的格式化輸出到標准輸出
fprintf fwprintf _ftprintf 使用vararg參量的格式化輸出
scanf wscanf _tscanf 從標准輸入的格式化讀入
fscanf fwscanf _ftscanf 格式化讀入
sprintf swprintf _stprintf 根據vararg參量表格式化成字符串
sscanf swscanf _stscanf 以字符串作格式化讀入
vfprintf vfwprintf _vftprintf 使用stdarg參量表格式化輸出到文件
vprintf 使用stdarg參量表格式化輸出到標准輸出
vsprintf vswprintf _vstprintf 格式化stdarg參量表並寫到字符串
sprintf_s swprintf_s _stprintf_s 格式化字符串
數字轉換
strtod wcstod _tcstod 把字符串的初始部分轉換為雙精度浮點數
strtol wcstol _tcstol 把字符串的初始部分轉換為長整數
strtoul wcstoul _tcstoul 把字符串的初始部分轉換為無符號長整數
_strtoi64 _wcstoi64 _tcstoi64
輸入和輸出
fgetc fgetwc _fgettc 從流中讀入一個字符並轉換為寬字符
fgets fgetws _fgetts 從流中讀入一個字符串並轉換為寬字符串
fputc fputwc _fputtc 把寬字符轉換為多字節字符並且輸出到標准輸出
fputs fputws _fputts 把寬字符串轉換為多字節字符並且輸出到標准輸出串
getc getwc _gettc 從標准輸入中讀取字符, 並且轉換為寬字符
getchar getwchar _gettchar 從標准輸入中讀取字符
putc putwc _puttc 標准輸出
putchar putwchar _puttchar 標准輸出
ungetc ungetwc _ungettc 把一個字符放回到輸入流中

不同平台文件夾遍歷操作

以下為刪除文件操作

windows

#include <io.h>


bool RmDir(const std::string & path)
{
	std::string strPath = path;
	struct _finddata_t fb;   //查找相同屬性文件的存儲結構體
	//制作用於正則化路徑
	if (strPath.at(strPath.length() - 1) != '\\' || strPath.at(strPath.length() - 1) != '/')
		strPath.append("\\");
	std::string findPath = strPath + "*";
	intptr_t handle;//用long類型會報錯
	handle = _findfirst(findPath.c_str(), &fb);
	//找到第一個匹配的文件
	if (handle != -1L)
	{
		std::string pathTemp;
		do//循環找到的文件 
		{
			//系統有個系統文件,名為“..”和“.”,對它不做處理  
			if (strcmp(fb.name, "..")!=0 && strcmp(fb.name, ".")!=0)//對系統隱藏文件的處理標記
			{
				//制作完整路徑
				pathTemp.clear();
				pathTemp = strPath + std::string(fb.name);
				//屬性值為16,則說明是文件夾,迭代  
				if (fb.attrib == _A_SUBDIR)//_A_SUBDIR=16
				{
					RmDir(pathTemp.c_str());
				}
				//非文件夾的文件,直接刪除。對文件屬性值的情況沒做詳細調查,可能還有其他情況。  
				else
				{
					//這里也可以用 DeleteFile(const char*)
					remove(pathTemp.c_str());
				}
			}
		} while (0 == _findnext(handle, &fb));//判斷放前面會失去第一個搜索的結果
		//關閉文件夾,只有關閉了才能刪除。找這個函數找了很久,標准c中用的是closedir  
		//經驗介紹:一般產生Handle的函數執行后,都要進行關閉的動作。  
		_findclose(handle);
	}
	//移除文件夾  
	return RMDIR(strPath.c_str())==0?true:false;
}

linux


#include <dirent.h>


bool RmDir(const std::string & path)
{
	if (strPath.at(strPath.length() - 1) != '\\' || strPath.at(strPath.length() - 1) != '/')
		strPath.append("/");
	DIR *d = opendir(strPath.c_str());//打開這個目錄
	if (d != NULL)
	{ 
		struct dirent *dt = NULL;
		while (dt = readdir(d))//逐個讀取目錄中的文件到dt
		{
			//系統有個系統文件,名為“..”和“.”,對它不做處理
			if (strcmp(dt->d_name, "..")!=0 && strcmp(dt->d_name, ".")!=0)//判斷是否為系統隱藏文件
			{
				struct stat st;//文件的信息
				std::string fileName;//文件夾中的文件名
				fileName = strPath + std::string(dt->d_name);
				stat(fileName.c_str(), &st);
				if (S_ISDIR(st.st_mode))
				{
					RmDir(fileName);
				}
				else
				{
					remove(fileName.c_str());
				}
			}
		}
		closedir(d);
	}
	return rmdir(strPath.c_str())==0?true:false;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM