通常来说,我们要为一个应用程序提供各种资源,比如配置文件、作为插件的动态链接库文件、样式表文件等。为了保证有一个清晰明了的应用程序目录结构,我们需要把这些文件在目录中有效组织。
Root
|--- Application.exe
|--- Plugins
|--- plugin1.dll
|--- plugin2.dll
|--- Config
|--- AppConfig.ini
|--- AnotherConfig.ini
|--- Resources
|--- Images
|--- Icons
|--- StyleSheet
应用程序在初始化过程中,需要读取配置文件来进行初始化, 比如恢复上次关闭时的UI状态。 这个动作一般发生在程序最开始。 如果应用程序使用 Qt 框架开发,这个过程一般发生在 QApplication
对象被构造之前。
int main(int argc, char** argv)
{
Configuration::get().init();
QApplication app(argc, argv);
return app.exec();
}
为了读取配置文件,我们需要先获得应用程序此时的路径信息, 再确定配置文件存放的位置。因为这个过程早于QApplication
的构造,所以不能使用QCoreApplication::applicationFilePath()
之类的API来获取。 所以我们需要自己组织数据结构,来进行这项工作。
首先,需要设计一个单例类Configuration
,在这个类中保存所有需要用到的路径。这个对象的生命周期需要明确指出,因为要确保配置信息是最先被初始化的。
class Configuration
{
using Self = Configuration;
public:
void init()
{
// 初始化路径变量
Self::initializeDirs();
// TODO: 初始化配置信息
}
static Configuration& get()
{
return *getPtr();
}
static Configuration* getPtr()
{
static Configuration g_configuration;
return &g_configuration;
}
private:
Configuration()=default;
~Configuration()=default;
private:
void initializeDirs()
{
// TODO: 初始化所有 Path
}
};
我们在经典的Singleton
模式上增加一个init()
Api, 这是把初始化语义从构造函数中分离出来。 因为经典的Singleton
模式中,对象的初始化是在第一次调用get()
时进行的。 程序规模变大后,代码中到处充斥着get()
,便很难确定对象是何时初始化的。
在我们的程序中,如果你想确定configuration
对象的初始化时间, 在项目中直接搜索Configuration::get().init();
即可。
我同时使用了一个using
增强了语义,你一看到代码,就知道initializeDirs()
这个函数,是Configuration
类的成员函数。这在多级继承中非常有效,我会专门开一贴来讲这个做法带来的好处。
假设我们的程序需要读取配置文件, 配置文件存放在./Config目录下。为了读取文件,我们需要修改当前的类。代码如下
#include <Windows.h>
#include <pathcch.h>
class Configuration
{
...
private:
void initializeDirs()
{
int ret = 0;
// 获取应用程序目录
ret = ::GetModuleFileName(NULL, EXE_DIR, MAX_PATH);
if (ret != 0)
{
::OutputDebugString(TEXT("Fail to get .exe file path.");
return;
}
// 去掉.exe后缀
ret = ::PathCchRemoveFileSpec(EXE_DIR, MAX_PATH);
if (ret != 0)
{
::OutputDebugString(TEXT("Fail to get .exe file directory.");
return;
}
// 初始化 配置文件目录 和 Style Sheet 目录
::swprintf(INI_DIR, TEXT(%s\\Config\\), EXE_DIR);
::swprintf(RESOURCE_DIR, TEXT(%s\\Resource\\StyleSheet\\), EXE_DIR);
}
const TCHAR* exeDir() const { return EXE_DIR; }
const TCHAR* iniDir() const { return INI_DIR; }
const TCHAR* resourceDir() const { return RESOURCE_DIR; }
private:
static const TCHAR EXE_DIR[MAX_PATH];
static const TCHAR INI_DIR[MAX_PATH];
static const TCHAR RESOURCE_DIR[MAX_PATH];
};
注意, 为了使用PathCchRemoveFileSpec
这个Api,需要在静态链接选项里引入pathcch.lib
。
这里使用了C语言和Windows Api,简洁高效快速。为了保证这三个变量是只读的,我们只开放出读取Getter, 也没有提供Setter。
接下来我们开始读取文件。在init()
函数中新增一个函数调用。
class Configuration
{
// ...
public:
void init()
{
// 初始化路径变量
Self::initializeDirs();
// 初始化配置信息
Self::initializeConfiguration();
}
private:
void initializeConfiguration()
{
TCHAR pathBuf[MAX_PATH] = {0};
const TCHAR* dir= iniDir();
::swprintf(pathBuf, TEXT("%sAppConfig.ini"), dir);
// 读取配置文件
// ::getPrivateProfileString(..., pathBuf);
}
}