C/C++教程

c++跨平台开发技术总结

本文主要是介绍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;
}
这篇关于c++跨平台开发技术总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!