一、数据类型
尽量使用Qt提供的数据类型,比如qint32,quint64等。
typedef signed char qint8; /* 8 bit signed */
typedef unsigned char quint8; /* 8 bit unsigned */
typedef short qint16; /* 16 bit signed */
typedef unsigned short quint16; /* 16 bit unsigned */
typedef int qint32; /* 32 bit signed */
typedef unsigned int quint32; /* 32 bit unsigned */
#if defined(Q_OS_WIN) && !defined(Q_CC_GNU)
# define Q_INT64_C(c) c ## i64 /* signed 64 bit constant */
# define Q_UINT64_C(c) c ## ui64 /* unsigned 64 bit constant */
typedef __int64 qint64; /* 64 bit signed */
typedef unsigned __int64 quint64; /* 64 bit unsigned */
在不同的平台上,基本的C++类型如short,char,int,long,long long会有不同的字长。
最好使用Qt提供的类型qint8,quint8,qint16,quint16,qint32,quint32,qint64,quint64,这些类型能确保字长是不随平台改变的。
二、跨平台适配
当我们在开发一个功能时,由于在win/linux下各自实现方式不同,故我们需要针对平台分别实现。
比如,我们需要编写一个模块获取当前系统的名称。一般来讲,有如下几种方式:
1、多平台代码放在一个类中
将win和linux各自的实现代码放在同一个类中,通过使用如下条件编译,来隔离不同平台的代码。
void Test1::doSomething()
{
#ifdef WIN32
// ...
QString str = "on Windows 10, doSomething!";
#else
// ...
QString str = "on Ubuntu 16.04, doSomething!";
#endif
qDebug() << str;
}
此种方式只适合用于逻辑简单、代码较少的代码段,不适合用于大段代码跨平台,或者一个类中分布多个跨平台函数调用的情况,这样在整个类中,多个平台的代码缠绕在一起,会很混乱,难以维护。
大段代码,以及跨平台逻辑复杂,还是建议写在不同的文件中,以文件为界,来对不同平台代码进行隔离。
2、使用接口进行统一
先定义需要获取的功能接口,然后分别派生出win、linux下的子类,并在子类中完成各自平台的逻辑代码。如下:
定义ISysInfo接口(ISysInfo.h)
class ISysInfo
{
public:
virtual QString getSystemName() = 0;
};
定义Linux下获取系统名称实现类(SysInfoLinux.h)
#ifndef SYSINFOLINUX_H
#define SYSINFOLINUX_H
#ifdef __linux
#include "ISysInfo.h"
class SysInfoLinux : public ISysInfo
{
public:
virtual QString getSystemName() override
{
return "Ubuntu 16.04";
}
};
#endif
#endif // SYSINFOLINUX_H
定义Win下获取系统名称实现类(SysInfoWin.h)
#ifndef SYSINFOWIN_H
#define SYSINFOWIN_H
#ifdef WIN32
#include "ISysInfo.h"
class SysInfoWin : public ISysInfo
{
public:
virtual QString getSystemName() override
{
return "Windows 10";
}
};
#endif
#endif // SYSINFOWIN_H
定义Test2类,Test2.h
#include "ISysInfo.h"
class Test2
{
public:
Test2();
~Test2();
void doSomething();
private:
ISysInfo* sysInfo;
};
Test2.cpp
#include "Test2.h"
#include <QDebug>
#ifdef WIN32
#include "SysInfoWin.h"
#else
#include "SysInfoLinux.h"
#endif
Test2::Test2()
{
#ifdef WIN32
sysInfo = new SysInfoWin();
#else
sysInfo = new SysInfoLinux();
#endif
}
Test2::~Test2()
{
delete sysInfo;
sysInfo = nullptr;
}
void Test2::doSomething()
{
QString str = "on " + sysInfo->getSystemName() + ", doSomething!";
qDebug() << str;
}
在main.cpp进行调用
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Test2 test2;
test2.doSomething();
return a.exec();
}
此种方式的好处在于,通过接口可以屏蔽各平台的实现细节,只要添加好条件编译#ifdef,则调用时不需要关心到底调用的哪个类。
缺点在于,需要在调用者,代码中添加条件编译#ifdef,并需要管理linux和win实现类对象的生命周期。另外,SysInfoWin类需要添加条件编译#ifdef WIN32,SysInfoLinux类需要添加条件编译#ifdef __linux,否则编译出错,较为麻烦。
3、使用目录进行隔离
我们分别建立win目录和linux目录,分别存放各自平台实现代码,并且源文件名称保持一致SysInfo.h。如下:
然后在pro文件中,添加如下内容,对win/linux进行跨平台条件编译。
win32 {
HEADERS += Test3/win/SysInfo.h
INCLUDEPATH += Test3/win
} else {
HEADERS += Test3/linux/SysInfo.h
INCLUDEPATH += Test3/linux
}
随后,实现linux目录下SysInfo.h内容,如下:
class SysInfo
{
public:
QString getSystemName()
{
return "Ubuntu 16.04";
}
};
实现win目录下SysInfo.h内容,如下:
class SysInfo
{
public:
QString getSystemName()
{
return "Windows 10";
}
};
Test3.cpp中
#include "Test3.h"
#include <QDebug>
#include "SysInfo.h"
void Test3::doSomething()
{
SysInfo info;
QString str = "on " + info.getSystemName() + ", doSomething!";
qDebug() << str;
}
在main.cpp中调用
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Test3 test3;
test3.doSomething();
return a.exec();
}
此种方式,通过在工程文件pro中,根据平台添加参与编译的源文件,实现跨平台编译。
使得调用者,无需关心到底#include "SysInfo.h"文件是哪个平台,由于pro中设置的关系,当include时会自动找到正确的平台头文件。在代码中不再使用#ifdef条件编译。
而且,与第2种对比,win和linux目录下SysInfo类不需要添加各自平台的条件编译宏Win32、__linux。所以应该是更方便的。
缺点,是必须手动人工,保证win和linux目录下SysInfo中,QString getSystemName()函数的一致性,否则在其他平台编译时,就可能出现报错。
三、总结
在实际操作中,我感觉还是推荐使用第3种使用目录进行隔离的方式,
虽然有缺点,但是只要在需要跨的平台上编译通过,基本就没有大问题,编译期,接口一致性还是容易解决。
另外在小段代码需要跨平台处理时,推荐使用第1种方式。
当然在跨平台编程中,还有许多的注意点,以后有机会再继续分享。
若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!
同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。
本文涉及工程代码,公众号回复:17CrossPlatform,即可下载。