系列入口:
编程实战:类C语法的编译型脚本解释器(系列)-CSDN博客
本文讲解插件(自定义函数)的接口。
下文中的“插件”和“自定义函数”是两个概念:
- “插件” 提供自定义函数功能的类,具有特殊的接口,一个插件实现一个自定义函数
- “自定义函数” 脚本里的函数调用,形如“fun(a,b,c)”,脚本解释器负责将脚本里的函数调用与插件对应
目录
一、插件接口
二、插件管理器
一、插件接口
//插件struct CPlugin{string plugin_name;Variable::types plugin_return_type;CPlugin() :plugin_name(""), plugin_return_type(Variable::NULLVARIABLE) {}CPlugin(char const* _name, Variable::types _type) :plugin_name(_name), plugin_return_type(_type) {}virtual string& help(string& ret){ret = plugin_name + " : 返回值 " + Variable::TypeStr(plugin_return_type) + "\r\n";return ret;}virtual bool CheckPlugin(vector<Variable >& params, void*& pc, string& msg) = 0;virtual bool ExecFunction(vector<Variable >& params, void* const& pc, Variable& ret, string& msg, void* pe) = 0;};
这是个关键的接口,只有两个成员变量:
类型 | 名称 | 说明 |
string | plugin_name | 自定义函数名称,也就是函数名 |
Variable::types | plugin_return_type | 自定义函数的返回值类型,返回值放在一个Variable中,必须明确指定返回类型,编译时会检查返回值类型是否正确 |
两个构造函数无关紧要,只是提供了设置自定义函数名称和返回值类型而已。
三个虚函数很重要:
- help() 返回自定义函数说明,已经提供了一个示例实现,这种参数传递方式是用来避免不必要的对象创建的。
- CheckPlugin() 编译时检查,如果返回false则编译不通过。后面详细介绍。
- ExecFunction() 执行,执行脚本时通过此接口获得返回值。 后面详细介绍。
CheckPlugin()和ExecFunction()有很多共同的参数,一起介绍:
类型 | 名称 | 说明 |
vector<Variable> | params | 函数的参数列表,也就是fun(a,b,c)的“a,b,c”,编译时提供的值是无意义的,但可以检查类型是否符合预期 |
void * & | pc | 编译时用户提供的指针的引用,用户可以在编译时修改,意即:用户可以在CheckPlugin里面申请内存,保存在pc变量里供执行时使用 此变量在执行时是只读的,但是指向的内容仍然是可以修改的(我感觉在运行时不断修改这个指针是难以理解的)(大BUG,这个值其实根本没设置,编译和运行都没有设置,没有这个参数) |
Variable & | ret | 存放函数的返回值 |
string & | msg | 如果执行出错,可以在这里放返回值 |
void * | pe | 运行时由用户提供的指针 |
CheckPlugin()和ExecFunction()本身的返回值为bool,表达插件本身执行成功或失败,具体区别借助实例来理解:
struct CMax : public CPlugin{CMax() :CPlugin("max", Variable::DOUBLE) {}virtual string& help(string& ret){ret = CPlugin::help(ret);ret += "取最大值,1-N个参数,参数必须是数值\r\n";return ret;}virtual bool CheckPlugin(vector<Variable >& params, void*& pc, string& msg){msg = "";if (params.size() < 1)msg += "参数不足\r\n";for (size_t i = 0; i < params.size(); ++i){if (!params[i].isNumber())msg += "参数必须是数值\r\n";}return 0 == msg.size();}virtual bool ExecFunction(vector<Variable >& params, void* const& pc, Variable& ret, string& msg, void* pe){size_t _max = 0;for (size_t i = 1; i < params.size(); ++i){if (params[i].GetDouble() > params[_max].GetDouble())_max = i;}ret = params[_max];return true;}};
这是内置函数max的插件,CheckPlugin检查参数的个数和类型,ExecFunction则把参数转换为double然后把最大的放在ret中返回。
这个插件没有用到pc和pe这两个参数,目前可以无视,因为这两个参数是客户代码使用的,插件解释器只是传递而并不操作,到需要用的时候你自然知道怎么用。
二、插件管理器
//插件表class CPluginMap{public:struct HANDLE{string plugin_name;bool isNULL()const { return 0 == plugin_name.size(); }};private:static map<string, CPlugin*>& GetPluginMap();public:template<typename T>static void addplugin(map<string, CPlugin*>& mapPlugins);static bool AddPlugin(char const* name, Variable::types type, CPlugin* p);static CPlugin* GetPlugin(string const& fun_name);static CPlugin* GetPlugin(HANDLE const& h);static string& PluginHelp(string& ret);};
插件管理器管理所有的插件,也就是管理所有的自定义函数。
子类型HANDLE是内部使用的,提供类似指针的快速访问,不过目前并没有性能优势,因为内部藏的还是自定义函数名。
addPlugin往插件管理器里面添加插件,是静态方法。插件管理器的实现相当简单,也很随意,所以就不展开解释了。
用户代码只需要在执行脚本前用addPlugin或AddPlugin添加插件即可。
(这里是结束,但是不是整个系列的结束)