c++跨平台技术学习(一)--使用公共的代码
引言
能被不同平台共享的代码越多,跨平台的项目就越容易。所有平台上公用的功能应该被标识出来避免它们在平台相关的代码里重复出现。并且它们的编译、测试和部署应该贯穿在整个生命周期中。建议隐藏到一个统一的API抽象之中去。
在工厂模式下,可以编写一个希望暴露的功能接口,进而向每一个支持的平台提供这个功能的具体实现。在编译或运行阶段,可以根据平台的不同来具体实现,然后实例化,最后粘合到接口类中。当application调用接口类时,把平台相关的功能代理给这个具体实现。可移植功能单独在接口类中实现,不能重复。
假如实现一个列出在系统上所有运行中的进程名字和id的函数,在 mac、linux、和windows上运行
#include<iostream>
using namespace std;
class ProcessList {
public :
int Scan();
int GetCount();
const char * GetName(const int i);
int GetPID(const int i);
};
//使用上述类
int main() {
ProcessList processList;
processList.Scan();
for (int i = 0; i < processList.GetCount(); i++) {
cout << processList.GetPID(i);
cout << processList.GetName(i);
}
return 0;
}
每个平台都有它去的进程名/id的独特方式,这个被封装在Scan()中,获取数据后存在一个平台无关的数据结构中,就可以独立于平台风格实现。需要把ProcessList类改造如下
class ProcessList {
public :
#if defined(WIN32)
int ScanWindows();
#endif
#if defined(DARWIN)
int ScanMacOSX();
#endif
#if defined(LINUX)
int ScanLinux();
#endif
int GetCount();
const char * GetName(const int i);
int GetPID(const int i);
};
对应的main方法如下
int main()
{
ProcessList processList;
#if defined(WIN32)
processList.ScanWindows();
#endif
#if defined(DARWIN)
processList.ScanMacOSX();
#endif
#if defined(LINUX)
processList.ScanLinux();
#endif
for (int i = 0; i < processList.GetCount(); i++)
{
cout << processList.GetPID(i);
cout << processList.GetName(i);
}
return 0;
}
由于scan操作的实现没有提供任何抽象,开发人员必须不停地检查维护每一个平台的实现,来剔除公共的代码。所以把公共的部分提炼出来放到一个单独的Scan函数中。回到ProcessList的原始定义中
class ProcessList {
public :
int Scan();
int GetCount();
const char * GetName(const int i);
int GetPID(const int i);
};
这个接口是平台无关的,最终执行平台相关代码的是被调用的Scan,所以#ifdefs可以放到这一层,于是scan的实现如下
ProcessList::Scan()
{
#if defined(WIN32)
return ScanWindows();
#endif
#if defined(DARWIN)
return ScanMacOSX();
#endif
#if defined(LINUX)
return ScanLinux();
#endif
return -1;
}
最后的-1代表调用Scan()的平台没有相关的实现,目标达成!
如果要记录Scan()被调用这个事件,可以重写Scan()(假设Log是一个知道怎么正确做日志的宏,如添加时间戳)
ProcessList::Scan()
{
LOG("Scan was called!");
#if defined(WIN32)
return ScanWindows();
#endif
#if defined(DARWIN)
return ScanMacOSX();
#endif
#if defined(LINUX)
return ScanLinux();
#endif
return -1;
}
有一个需要注意的问题是为ProcessList类增加了一个新的平台相关的函数,例如增加一个改变进程优先级的函数。在UNIX下,你可以通过调用nice()函数做到这一点,nice()函数接受一个整形参数作为优先级。在windows下可以打开一个进程调用一个交SetPriorityClass()函数。类UNIX系统的返回值都有不同,Linux里返回0代表成功,-1代表失败。而Darwin系统上返回值就是进程的优先级,不能作为成功或失败的标志。
可以采用相同方法,给ProcessList加一个抽象API,叫做SetPriority(),然后在函数里用#ifdefs把平台相关的帮助函数隔离起来。随着功能的添加,#ifdef的数量就回随之增长。工厂模式虽然不能完全实习#ifdef是,但它会被限定在一个单独的地方,增加代码易读性。
工厂模式允许我们在Scan()函数里申请并获得一个可以提供平台相关的实现的对象。这个对象从一个定义了平台相关实习API的基础类上继承而来。ProcessList维护了一个指向这个类的指针,而工厂模式则是提供这个指针的对象。Scan结构改变如下
int ProcessList::Scan() {
if (m_processsImpl)
return m_processsImpl->Scan();
return -1;
}
这里的m_processImpl是一个指向工厂模式提供的对象的指针,可以在Scan()里向工厂模式申请这个对象,或者是从ProcessList的构造函数里获得
#include "processesfactory.h"
int ProcessList::ProcessList() : m_processesImpl(NULL)
ProcessesFactory * factory = ProcessesFactory::GetProcessFactory();
if (factory)
{
m_porcessesImpl = factory->MakeProcesses();
}
}
ProcessFactory是一个singleton对象,必须通过平台无关代码实现函数GetProcessFactory()获得,实现GetProcessFactory()的源码是#ifdefs出现的唯一地点:
#pragma once
class ProcessesFactory
{
#if defined(HAVE_WIN32)
#include "windows\windowsfactory.h"
#endif
#if defined(HAVE_MACOS)
#include "cocoa/cocafactory.h"
#endif
#if defined(HAVE_LINUX)
#include "linux/linuxfactory.h"
#endif
int ProcessesFactory::GetProcessFactory()
{
static ProcessesFactory *processesFactory = 0;
if (!processesFactory)
{
#if defined(HAVE_WIN32)
processesFactory = WindwosFactory::GetFactoryInstance();
#endif
#if defined(HAVE_MACOS)
processesFactory = CocoaFactory::GetFactoryInstance();
#endif
#if defined(HAVE_LINUX)
processesFactory = LinuxFactory::GetFactoryInstance();
#endif
}
return processesFactory;
}
};
上边代码是按条件编译的,build系统保证同一时间只设置其中一个开关(如HAVE_WIN32)。通常会在Makefile里做相关设定让它作为编译时一个编译参数。
工厂模式在不同平台的实现
每个平台都提供一个可以创建并返回平台相关实现指针的类(比如WindowsFactory)。这个类被编译到平台相关的库里并链接到应用程序。每个支持的平台都有一个这样的库。当平台无关的代码获得这个指针后,可以用它来创建提供平台相关功能的对象。
WindowsFactory
WindowsFactory实现了一个静态函数GetFactoryInstance(),它创建并返回一个平台相关实现的单体
#pragma once
#if ! defined(_WINDOWS_FACTORY_H_)
#define _WINDOWS_Factory_H_
#include "processesfactory.h"
class WindowsFactory :public ProcessesFactory {
public:
static WindowsFactory * GetFactoryInstance() {
static WindowsFactory * factory = 0;
{
if (!factory)
factory = new WindowsFactory;
return factory;
}
}
virtual ~WindowsFactory();
virtual ProcessesImpl * MakeProcesses();
private:
WindowsFactory();
};
#endif
需要注意的是。首先,静态函数GetFactoryInstance()允许我们创建并获得windows-factory的单体,另一个关键是工厂模式实现了一个MakeProcesses()的函数,返回了一个指向我们需要的ProcessesImpl对象的指针。一个工厂模式可以创建各种各样的对象,每个对象都提供针对平台的某个功能。假设我们写独立于平台的读写文件功能嗲吗,首先创建一个新的抽象工厂类FilesFactory,然后修改WindowsFactory如下
#pragma once
#if ! defined(_WINDOWS_FACTORY_H_)
#define _WINDOWS_Factory_H_
#include "processesfactory.h"
#include "filefactory.h"
#include "processesimpl.h"
#include "filesimpl.h"
class WindowsFactory :public ProcessesFactory, public FilesFactory
{
public:
static WindowsFactory * GetFactoryInstance() {
static WindowsFactory * factory = 0;
{
if (!factory)
factory = new WindowsFactory;
return factory;
}
}
virtual ~WindowsFactory();
virtual ProcessesImpl * MakeProcesses();
virtual FileImpl * MakeFiles();
private:
WindowsFactory();
};
#endif
类的实现
这是ProcessesImpl的定义
# pragma once
# if ! defined(__PROCESSES_IMPL_H__)
#define _ _PROCESSES_IMPL_H__
#include "processes.h"
#include<vector>
class ProcessesImpl {
public:
ProcessesImpl() {};
virtual ~ProcessesImpl() {};
virtual int Scan() = 0;
int GetCount();
const char * GetName(const int which);
protected:
std::vector <Process> m_processList;
};
#endif
这个类里的实现大多数代码都是可以用在所用的平台上。进程名和ID列表存放在STL移植性很高的vector里,而GetCount()这样的函数都只依赖于这个vector,所以也很自然的在基类里实现。
平台相关的ProcessesImpl类
另一方面,Scan()函数是平台相关的,所以必须在子类里实现。
#if !defined(_WINDOWSPROCESSESIMPL_H__)
#define _WINDOWSPROCESSESIMPL_H__
#include "../processesimpl.h"
#include <Windows.h>
#include<stdio.h>
#include<tchar.h>
#include "psapi.h"
class WindowsProcessesImpl :public ProcessesImpl {
public:
WindowsProcessesImpl();
virtual ~WindowsProcessesImpl();
virtual int Scan();
private:
void ScanProcesses();
void PrintProcessNameAndID(DWORD processID);
};
#endif
注意这里包含了Windwos相关的头文件,因为这段代码只在Windows上编译,所以包含它或其他平台相关的代码都ok。Scan()函数只用到了两个私有的帮助函数WindowsProcessImpl:ScanProcess()和PrintProcessNameAndID()。只要按基类的要求声明了Scan()函数,其他的都可自行添加。
Linux
Linux下实现使用了proc文件系统来获取进程信息。首先是头文件:
#pragma once
#if !defined(_LINUXPRROCESSIMPL_H__)
#define _LINUXPRROCESSIMPL_H__
#include"../processimpl.h"
#include<dirent.h>
class LinuxProcessesImpl :public ProcessesImpl {
public :
LinuxProcessesImpl();
virtual ~LinuxProcessesImpl();
virtual int Scan();
};
#endif
然后是具体实现
#include "linuxprocessesimpl.h"
#include <unistd.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#include<string.h>
int
LinuxProcesseSImpl::Scan()
{
DIR *dir;
m_prorcessList.clear();
dir = opendir("/proc");
if (dir == NULL)
return 0;
std::string name;
struct dirent *dirEnt;
struct stat statBuf;
while ((dirEnt == readdir(dir)))
{
name = "/proc/";
name += dirEnt->d_name;
if (!stat(name.c_str(), &statBuf)
{
if (statBuf.st_mode & S_IFDIR)
{
char *p;
p = dirEnt->d_name;
bool allDigit = true;
while (*p)
{
if (!isdigit(*p))
{
allDigits = false;
break;
}
p++;
}
if (allDigits)
{
Process proc;
proc.SetPID(atoi(dirEnt->d_name));
std::string path = name +
std::string("/cmdline");
int fd = open(path.c_str().O_RDONLY);
if (fd != -1)
{
char buf[1024];
memset(buf, '\0', sizeof(buf));
int n;
if ((n = read(fd, buf, sizeof(buf) - 1)) > 0)
{
proc.SetName(buf);
m_porcessList.push_back(proc);
}
else if (n == 0)
{
path = name + std::string("/status");
int fd2 = open(path.c_str(), O_RDONLY);
if (fd2 != -1)
{
memset(buf, '\0', sizeof(buf));
if (n = read(fd2, buf, sizeof(buf) - 1) > 0)
{
char *p = buf;
while (*p)
{
if (*p == '\n')
{
*p = '\0';
break;
}
p++;
}
if ((p = strstr(buf, "Name:")))
{
p += strlen("Name:");
while (*p && isspace(*p))
p++;
}
else
p = buf;
proc.SetName(p);
m_processList.push_back(proc);
close(fd2);
}
close(fd);
}
}
}
}
}
}
closedir(dir);
return m_processList.size();
}
Mac OS X
最后,我们来看看使用BSD奇特的sysctl()函数来获取进程信息的Darwin系统的实现方法。
#pragma once
#if ! defined(_COCOAPROCESSESIMPL_H__)
#define _COCOAPROCESSESIMPL_H__
#include"../processimpl.h"
#include<assert.h>
#include<errno.h>
#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/sysctl.h>
typedef struct kinfo_proc kinfo_proc;
class CocoaProcessesImpl :public ProcessesImpl {
public :
CocoaProcessesImpl();
virtual ~CocoaProcessesImpl();
virtual int Scan();
private:
int getBSDProcessList(kinfo_proc **procList,size_t * procCount);
};
#endif
实现可以参考(http://developer.apple.com)
创建实例层次
ProcessList构造函数获取一个指向从ProcessesImpl继承而来的类的指针。ProcessesImpl定义了Scan()、GetSize()、GetID()和GetName()的接口。当应用程序调用ProcessList::Scan()时,ProcessList会通过Scan()去调用相应平台的版本。而要得到这个指针,ProcessList先调用ProcessesFactory::GetProcessesFactory(),随后它再次调用WindowsFactory::GetFactoryInstance()。因为ProcessesFactory::GetProcessesFactory()只是被部分编译了而已
#pragma once
ProcessList::ProcessList() :m_processesImpl(NULL)
{
ProcessesFactory *factory = ProcessFactory::GetProcessesFactory();
if (factory)
m_processesImpl = factory->MakeProcesses();
}
ProcessFactory *ProcessesFactory::GetProcessesFactory() {
static ProcessesFactory * processesFactory = 0;
if (!processesFactory)
#if defined*(HAVE_WIN32)
processesFactory = WindowsFactory::GetFactoryInstance();
#endif
return processesFactory;
}
WindowsFactory::GetFactoryInstance会实例化ProcessesImpl的子类WindowsProcessesImpl,随后指向这个对象的指针将会返回给ProcessList的构造函数。
scan的序列图
这里ProcessList每个函数代码都差不多,先检查工厂对象返回的ProcessesImpl指针是不是为空,若是非空,则调用相应的函数
int ProcessList::Scan(){
if (m_processesImpl)
return m_processesImpl->Scan();
return -1;
}
int ProcessList::GetCount() {
if (m_processesImpl) {
return m_processesImpl->GetPID(i);
return -1;
}
const char*
ProcessList::GetName(const int i) {
if (m_processesImpl)
return m_processesImpl->GetName(i);
return NULL;
}
}
其中Scan()调用的WindowsProcessesImpl的实现