一个标准的移植接口模块的目录架构如下:
项目根路径/
|--include/
|----模块名/
|------interface/
|------具体实现/
|------另一种具体实现/
|------更多具体实现..../
include目录的每一个子目录对应一个移植接口模块。每个模块的接口定义位于其下的interface目录,而具体实现则位于模块目录下。
特别的: *
include/pure下存放着其本身不需要任何移植就能使用(或者完全依赖于别的移植接口模块)的库,这些库通常提供了一些基本定义(例如NULL的宏定义)。
*
项目主体通过调用移植接口来实现功能,但移植接口不应该调用项目主体。即项目主体只能单向调用移植接口。
本项目的接口定义参考了C#的接口类设计,因此文件名和类名以字母I开头,这是Interface的缩写。
Stamon的移植接口类设计采用一种自创的方法——CITP(Curiously
Iterating Template
Pattern,奇异递推模板模式)。其详细原理参见CITP设计文档.md。利用CITP强大的接口约束表达力,开发者在按要求实现对应接口后,可以非常轻松的将自己的实现对接到项目中。
以经典的ArrayList模块为例。其代码位于include/ArrayList目录,接口定义在include/ArrayList/interface目录(即include/ArrayList/interface/IArrayList.hpp),而诸如include/ArrayList/stdc、include/ArrayList/stdcpp则分别是基于标准C语言、标准C++的接口实现。
根据模块的文档等相关引导,直接使用C++的#include语法即可。
例如,导入模块ArrayList只需要以下代码:
#include "ArrayList.hpp"其中,ArrayList.hpp由模块开发者提供,作为该模块的入口文件
一个模块可能会有若干个入口文件,具体情况因不同模块而异。
值得注意的是: *
通常情况下,被导入的文件不应该是接口约束文件。例如应导入ArrayList.hpp而非IArrayList.hpp
*
通常情况下,请导入入口文件的文件名,而不是具体的绝对路径或相对路径,文件名所在路径应该在编译时指定。例如应导入ArrayList.hpp而非D:/stamon2/include/ArrayList/stdc/ArrayList.hpp
Stamon采用一体化编译的方式来编译项目。因此可直接添加目标模块的接口约束文件所在目录和对应实现文件所在目录作为编译器的头文件搜索路径。
例如,在G++编译器中,使用-I <dir>即可将<dir>添加至头文件搜索路径中。若要导入ArrayList的stdc实现,则可添加以下参数:
-I include/ArrayList/interface -I include/ArrayList/stdc此时的工作目录应该是项目根目录。
开发者在移植过程中,可以挑选并接入最佳的移植实现,也可以自己动手实现。
我们欢迎更多开发者为不同平台贡献移植实现。
接下来,笔者将带领大家从头开发一个Example模块,并把它接入到项目中。
笔者在Windows 10的系统环境下进行开发,编译器版本为gcc version 15.1.0 (x86_64-posix-seh-rev0, Built by MinGW-Builds project)。
在Stamon源码的基础上创建如下的目录和文件结构:
项目根路径/
|--include/
|----Example/
|------interface/
|--------IExample.hpp
|------stdc/
|--------Example.hpp
|--src/
|----ExampleMain.cpp
IExample.hpp的代码如下所示:
#pragma once
#include "StamonLib.hpp"
#include "stdio.h"
namespace stamon::interface {
template<class Impl> class IExample : public Impl {
// 示例类的接口约束
public:
IExample(int val)
: Impl(val) {
}
int getVal() {
return Impl::getVal();
}
void example() {
Impl::example();
}
};
} // namespace stamon::interfaceExample.hpp的代码如下所示:
#pragma once
#include "IExample.hpp"
#include "stdio.h"
namespace stamon::stdc {
class Example {
// 示例类的标准C实现
public:
int value;
Example(int val)
: value(val) {
}
int getVal() {
return value;
}
void example() {
printf("Hello, This is an example!\n");
printf("value=%d\n", getVal());
}
};
} // namespace stamon::stdc
namespace stamon {
using Example = interface::IExample<stdc::Example>;
}ExampleMain.cpp的代码如下所示:
#include "Example.hpp"
int main()
{
stamon::Example e(114);
e.example();
return 0;
}使用如下指令编译示例项目:
g++ src/ExampleMain.cpp -o bin/example.exe -O2 -std=c++17 -static -I include/pure -I include/Example/interface -I include/Example/stdc
生成的可执行文件路径为bin/example.exe
-O2 -std=c++17 -static用于优化项目、指定代码标准、生成静态的可执行文件。
值得关注的是-I include/pure -I include/Example/interface -I include/Example/stdc。通常情况下,任何项目都应该包含include/pure,pure目录中存放着整个项目所依赖的,平台无关的基础代码。除此之外开发者还要记得把自己开发的模块的接口约束目录和对应实现目录都添加进来。
运行bin/example即可得到输出。至此,我们成功开发了一个简单的模块。
>bin/example
Hello, This is an example!
value=114
>
CITP编写接口约束代码时,应注意CITP的局限性。详情已在CITP设计文档.md中指出