一、前言
博主初入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;
}
