引言
能被不同平臺共享的代碼越多,跨平臺的項目就越容易。所有平臺上公用的功能應該被標識出來避免它們在平臺相關的代碼裏重複出現。並且它們的編譯、測試和部署應該貫穿在整個生命週期中。建議隱藏到一個統一的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的實現