Как написать собственную функцию в CTPP2?
Введение
В API шаблонизатора CTPP2 реализована возможность подключения пользовательских функций. Пользовательские функции могут значительно повысить производительность шаблонизатора и практически неограниченно расширить его функционал. Этот документ расскажет вам, как самостоятельно написать такое расширение.
Разрабатываемая в этом примере функция будет форматировать дату, переданную ей в виде unixtime.
Базовым классом для всех пользовательских функций является класс SyscallHandler из пространства имен CTPP. Класс описан в VMSyscall.hpp, поэтому нам придется подключить этот заголовочный файл. Класс содержит в себе 6 виртуальных методов: InitHandler, PreExecuteSetup, Handler, GetName, GetVersion, DestroyHandler.
Предназначение методов следующее:
Метод InitHandler используется для инициализации обработчика пользователем. Метод имеет ровно один параметр типа CDT. Если в инициализации нет необходимости, метод реализовывать необязательно.
virtual INT_32 InitHandler(CDT & oCDT);
Метод PreExecuteSetup вызывается непосредственно перед запуском байткода в виртуальной машине и нужен для передачи функции необходимых для работы данных. Если функция не работает с сегментами данных, функций или глобальными параметрами, реализация метода необязательна.
virtual INT_32 PreExecuteSetup(OutputCollector & oCollector,
CDT & oCDT,
const ReducedStaticText & oSyscalls,
const ReducedStaticData & oStaticData,
const ReducedStaticText & oStaticText);
Метод Handler является "рабочим телом" пользовательской функции и вызывается каждый раз, когда требуется выполнить функцию. Метод содержит три параметра: массив аргументов, их количество и возвращаемое значение, передаваемое по ссылке.ВАЖНО: аргументы задаются в обратном порядке, поэтому при вызове FOO_BAR(a, b, c), в параметры в массиве аргументов будут переданы как aArguments[0] -> c, aArguments[1] -> b, aArguments[2] -> a.
virtual INT_32 Handler(CDT * aArguments,
const UINT_32 & iArgNum,
CDT & oCDTRetVal) = 0;
Важный момент:
Перед запуском метода Handler в переменной oCDTRetVal находится мусор, поэтому, если функции требуется вернуть результат исполнения, необходимо указать тип переменной. Это можно сделать как явно, написав, к примеру, oCDTRetVal = CDT(CDT::INT_VAL);, так и неявно: oCDTRetVal = 10;
Метод GetName возвращает имя функции (asciz-строку).
virtual CCHAR_P GetName() = 0;
Метод GetVersion возвращает версию API; текущая версия - 2. В случае изменения API хранимых функций, знание версии позволит выбирать правильный механизм запуска методов.
В случае, если в методе InitHandler требуется выделить для работы память, подключиться к БД, или выподнить подобные действия, в методе DestroyHandler возможно освободить выделенную память, закрыть подключение к БД и т.д.
virtual INT_32 DestroyHandler(CDT & oCDT);
Собственная функция
Поскольку разрабатываемая функция не обращается ни к данным, ни к стеку, нам необходимо реализовать всего два метода: Handler и GetName. Соответственно, файл заголовков будет выглядеть так:
FnDateFormat.hpp
#ifndef _FN_DATE_FORMAT_HPP__
#define _FN_DATE_FORMAT_HPP__ 1
#include <VMSyscall.hpp>
namespace MY_NS
{
class FnDateFormat:
public CTPP::SyscallHandler
{
public:
~FnDateFormat() throw();
private:
INT_32 Handler(CDT * aArguments,
const UINT_32 & iArgNum,
CDT & oCDTRetVal);
CCHAR_P GetName();
};
}
#endif // _FN_DATE_FORMAT_HPP__
Файл реализации FnDateFormat.сpp:
#include <FnDateFormat.hpp>
#include <time.h>
namespace MY_NS
{
FnDateFormat::~FnDateFormat() throw() { ;; }
INT_32 FnDateFormat::Handler(CDT * aArguments,
const UINT_32 & iArgNum,
CDT & oCDTRetVal)
{
if (iArgNum != 2) { return -1; }
CHAR_8 szBuffer[1024 + 1];
time_t iTime = aArguments[1].GetInt();
const struct tm * pTM = localtime(&iTime);
if (strftime(szBuffer, 1024,
aArguments[0].GetString().c_str(), pTM) == 0) { return -1; }
oCDTRetVal = szBuffer;
return 0;
}
CCHAR_P FnDateFormat::GetName() { return "date_format"; }
}
Шаблон для вызова функции:
Date: <TMPL_var DATE_FORMAT(1200490323, "%Y-%m-%d %H:%M:%S")>
Проверка работы
Заходим в каталог examples/extra_fn дистрибутива CTPP2, компилируем проект: cmake . && make
Компилируем шаблон: ctpp2c date_format.tmpl date_format.ct2
Проверяем работу: ./date_test date_format.ct2
После запуска виртуальная машина выведет
Date: 2008-01-16 16:32:03
|