ТОМ - платформа для текстовых игр

Объявление

Платформа ТОМ предназначена для создания текстовых игр на русском языке и имеет развитый парсер, позволяющий взаимодействовать с играми с помощью команд на близком к естественному языке. В данный момент активно разрабатывается версия ТОМ 2.
Последнюю версию платформы можно скачать здесь.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » ТОМ - платформа для текстовых игр » Документация » Создание плагина к ТОМ 2 на Delphi


Создание плагина к ТОМ 2 на Delphi

Сообщений 1 страница 2 из 2

1

Создание плагина к ТОМ 2 на Delphi

Вступление
Несмотря на то, что я очень редко работаю с dll и очень мало знаю про них, всё-же опишу как создать плагин к ТОМ 2 в Delphi. Вдруг кому-то пригодится.
Работать мы будем в Embarcadero RAD Studio XE и будем во всю использовать юникод. Если вы программируете на более ранней версии, в которой ещё нет поддержки Unicode (Delphi 7, например), то отписывайте ниже, попробуем разобраться.

Подготовка проекта
Для начала запустим Delphi и создадим новый проект dll:
Выбираем в меню File -> New -> Other...
там ищем Dynamic-Link Library, выбираем и жмём Ok.
Перед нами открывается заготовка dll проекта:

Код:
library TestPlugin;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  SysUtils,
  Classes;

{$R *.res}

begin

end.

В комментарии нас пугают сложностями работы со строками и с прочими вкусностями Delphi в dll. Плюём на это и удаляем этот комментарий :)
Сразу сохраним проект и назовём его, например TestPlugin.
Все плагины ТОМа должны иметь двойное расширение ".plg.dll". Что-бы не париться и каждый раз после компиляции не добавлять эту приставку ".plg", лезем в Project -> Options..., открываем раздел "Application" и в поле "LIB Suffix:" пишем ".plg" (без кавычек, конечно). Жмём OK. Теперь при компиляции к dll перед расширением будет добавляться этот суффикс.

Точка входа
Т.к. в делфи не всё так просто с точкой входа в dll как в Си, нам придётся немного пошаманить. Если вы не будете использовать в плагине глобальные объекты, которые надо выгружать по завершении работы плагина, то можно просто писать код между begin и end., но мы пойдём по чуть более сложному пути и выловим эти точки входа и выгрузки dll.
В разделе uses объявим модуль Windows.
Добавим функцию DLLEntryPoint:

Код:
//============== Точка входа в DLL =============================================
procedure DLLEntryPoint(Process: Integer);
begin
  case Process of
    DLL_PROCESS_ATTACH:  // При запуске dll
    begin
      // Здесь будет коннект к tom.dll и регистрация функций
    end;
    DLL_PROCESS_DETACH:  // При выгрузке dll
    begin
      // Здесь выгружаем все глобальные объекты, которые насоздавали
    end;
  end;
end;

Между основными begin ... end. напишем следующее:

Код:
begin
  DLLProc := @DLLEntryPoint;
  DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

Функции плагина
Для примера, наш плагин будет регистрировать две функции: getTime/время и getDate/дата, которые будут возвращать время и дату в виде строки. Понимаю, что такой плагин бесполезен, но для примера сойдёт.
Небольшая проблема кроется в том, что ТОМ не выгружает строки за нами. Мы должны делать это сами, причём после того как он заберёт результат этой функции. Чтобы не заморачиваться с этим, создадим глобальную переменную:

Код:
var
  ssStr: array of Char;

И сделаем функцию преобразования:

Код:
//============== Задание строки для вывода =====================================
procedure SetStr(const s: string);
var
  i: Integer;
begin
  SetLength(ssStr, Length(s) + 1);
  for i := 1 to Length(s) do
    ssStr[i-1] := s[i];
  ssStr[Length(ssStr)-1] := #0;
end;

Криво, но работает.

Теперь настало время описать наши две функции:

Код:
//================= Получение времени ==========================================
function FnGetTime(const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
begin
  SetStr(TimeToStr(Time));
  Result := PChar(ssStr);
end;

//================= Получение даты =============================================
function FnGetDate(const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
begin
  SetStr(DateToStr(Date));
  Result := PChar(ssStr);
end;

Все функции, которые надо подключать к ТОМу, надо объявлять именно так:
(const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;

Подключение и регистрация функций
Итак, сначала объявим типы и константы, которые понадобятся для написания плагина:

Код:
const
  // Типы функций
  FnInput   = 1;
  FnOutput  = 2;
  FnOther   = 3;
  FnCommand = 4; // функция доступна игроку из командной строки в игровом режиме

type
  // Функция плагина
  // Login - ID сессии, которая вызывает функцию
  // Text - строковый аргумент функции
  // Dop - дополнительный строковый аргумент
  wcsFn = function (const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
  // Функция регистрации функции плагина
  // Name - имя функции
  // Func - указатель на функцию
  // Tp - тип регистрируемой функции
  // CntPar - Кол-во аргументов регистрируемой функции (от 0 до 2)
  wcsRegFn = function (const Name: PChar; Func: wcsFn; Tp: Integer; CntArg: Integer): Integer;  stdcall;

Приведу описание типов функций:
FnOutput - функция что-нибудь выводит. Печатает текст на экран, воспроизводит звук, читает текст, рисует картинки и т.д.
При использовании эти функции не вызываются сразу, а накапливаются в пул. Это нужно для сохранения последовательности вывода текста, музыки, звуков и т.д. А также для пост-обработки накопленного выводимого текста целиком, а не обрывками.

FnInput - функция что-нибудь запрашивает. Например ввод командной строки или ответ на вопрос.
Перед выполнением такой функции сначала прокручивается и очищается пул функций FnOutput, а затем вызывается сама функция ввода.

FnOther - функция выполняется незаметно для пользователя. Ничего не выводит и ничего не просит. Выполняется сразу.

FnCommand - это тип функций, доступных игроку из командной строки в игровом режиме. Все остальные недоступны.

Функции хорошо прокомментированы, так-что объяснять не буду. То что не понятно, мы не будем использовать.
Директива stdcall нужна для совместимости dll с программами на Си, так-что не забываем.

Теперь поймаем tom.dll за шкирку и втюрим ему наши функции :) Для этого изменим код функции DLLEntryPoint:

Код:
//============== Точка входа в DLL =============================================
procedure DLLEntryPoint(Process: Integer);
var
  EngineDll: THandle;
  RegFunc: wcsRegFn;
begin
  case Process of
    DLL_PROCESS_ATTACH:  // При запуске dll
    begin
      //получаем функции движка
      EngineDll := GetModuleHandle(PChar('tom.dll'));  //движок уже загружен
      if EngineDll = 0 then
        Exit;
      @RegFunc := GetProcAddress(EngineDll, 'RegFunction');
      if Assigned(@RegFunc) then
      begin
        RegFunc('getTime', FnGetTime, FnCommand, 0);
        RegFunc('время',   FnGetTime, FnCommand, 0);
        RegFunc('getDate', FnGetDate, FnCommand, 0);
        RegFunc('дата',    FnGetDate, FnCommand, 0);
      end;
    end;
    DLL_PROCESS_DETACH:  // При выгрузке dll
    begin
      // Здесь выгружаем все глобальные объекты, которые насоздавали
    end;
  end;
end;

Ну вот и всё, плагин готов. Компилируем, копируем в папку с ТОМом и проверяем из командной строки:

> время
14: 17: 56
> дата
24. 06. 2011

Заключение
Думаю, этих набросков хватит, чтобы разобраться с написанием плагинов к ТОМ 2 на Delphi.
Стоит отметить, что dll на делфи получаются очень пухлыми. Модуль SysUtils прибавляет к dll лишних 57 Кб, а модуль Classes и вовсе 223-275 Кб. Так-что используйте их только в случае крайней необходимости.

Полный исходник плагина:

Код:
library TestPlugin;

uses
  Windows,
  SysUtils{,
  Classes};

{$R *.res}

{$LIBSUFFIX '.plg'}

const
  // Типы функций
  FnInput   = 1;
  FnOutput  = 2;
  FnOther   = 3;
  FnCommand = 4; // функция доступна игроку из командной строки в игровом режиме

type
  // Функция плагина
  // Login - ID сессии, которая вызывает функцию
  // Text - строковый аргумент функции
  // Dop - дополнительный строковый аргумент
  wcsFn = function (const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
  // Функция регистрации функции плагина
  // Name - имя функции
  // Func - указатель на функцию
  // Tp - тип регистрируемой функции
  // CntPar - Кол-во аргументов регистрируемой функции (от 0 до 2)
  wcsRegFn = function (const Name: PChar; Func: wcsFn; Tp: Integer; CntArg: Integer): Integer;  stdcall;

var
  ssStr: array of Char;

//============== Задание строки для вывода =====================================
procedure SetStr(const s: string);
var
  i: Integer;
begin
  SetLength(ssStr, Length(s) + 1);
  for i := 1 to Length(s) do
    ssStr[i-1] := s[i];
  ssStr[Length(ssStr)-1] := #0;
end;

//================= Получение времени ==========================================
function FnGetTime(const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
begin
  SetStr(TimeToStr(Time));
  Result := PChar(ssStr);
end;

//================= Получение даты =============================================
function FnGetDate(const Login: PChar; const Text: PChar; const Dop: PChar): PChar;  stdcall;
begin
  SetStr(DateToStr(Date));
  Result := PChar(ssStr);
end;

//============== Точка входа в DLL =============================================
procedure DLLEntryPoint(Process: Integer);
var
  EngineDll: THandle;
  RegFunc: wcsRegFn;
begin
  case Process of
    DLL_PROCESS_ATTACH:  // При запуске dll
    begin
      //получаем функции движка
      EngineDll := GetModuleHandle(PChar('tom.dll'));  //движок уже загружен
      if EngineDll = 0 then
        Exit;
      @RegFunc := GetProcAddress(EngineDll, 'RegFunction');
      if Assigned(@RegFunc) then
      begin
        RegFunc('getTime', FnGetTime, FnCommand, 0);
        RegFunc('время',   FnGetTime, FnCommand, 0);
        RegFunc('getDate', FnGetDate, FnCommand, 0);
        RegFunc('дата',    FnGetDate, FnCommand, 0);
      end;
    end;
    DLL_PROCESS_DETACH:  // При выгрузке dll
    begin
      // Здесь выгружаем все глобальные объекты, которые насоздавали
    end;
  end;
end;

begin
  DLLProc := @DLLEntryPoint;
  DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

2

Спасибо! очень ценный мануал получился.  :flag:


Вы здесь » ТОМ - платформа для текстовых игр » Документация » Создание плагина к ТОМ 2 на Delphi