17 mar
2023
Работа с нативными функциями
Added by IceFog,
published
Предназначение:
Прочее
Порой, имеющегося функционала игры не хватает, а реализовывать дополнительные возможности посредством JASS-сценариев с мемхаком неудобно да и результат не может похвастаться высокой производительностью.
В таком случае, может помочь добавление собственных нативных функций, которые будут написаны на более подходящих языках программирования.
Исходники с примером использования этой библиотеки можете скачать здесь.
заголовочный файл
unit jassapi;
{$mode objfpc}{$H+}
interface
const
JASS_API_DLL_NAME = 'JassApi.dll';
type
PJassString = Pointer;
TJassStringHandle = Dword;
TJassObjectHandle = Dword;
TJassFunctionHandle = Dword;
TSingle = record Value: Single end;
TJassEventType = (
jeMainBeingCalled = 0,
jeMainThreadDestruction = 1,
jeMainThreadDestroyed = 2
);
TJassEventCallback = procedure (Parameter: Pointer); stdcall;
TEventRegistrationHandle = Dword;
function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall; external JASS_API_DLL_NAME;
function UnregisterJassNative(Name: PChar): LongBool; stdcall; external JASS_API_DLL_NAME;
function GetJassNative(Name: PChar): Pointer; stdcall; external JASS_API_DLL_NAME;
function GetStringHandle(Value: PChar): TJassStringHandle; stdcall; external JASS_API_DLL_NAME;
function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall; external JASS_API_DLL_NAME;
function CreateJassString(AContent: PChar): PJassString; stdcall; external JASS_API_DLL_NAME;
procedure DestroyJassString(AString: PJassString); stdcall; external JASS_API_DLL_NAME;
procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall; external JASS_API_DLL_NAME;
function GetJassStringContent(JassString: PJassString): PChar; stdcall; external JASS_API_DLL_NAME;
procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;
procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;
function AddEventHandler(EventType: TJassEventType; Callback: TJassEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall; external JASS_API_DLL_NAME;
function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall; external JASS_API_DLL_NAME;
procedure PostEvent(EventType: TJassEventType); stdcall; external JASS_API_DLL_NAME;
implementation
end.
список изменений
- 04.04.2023
- Добавил функции CreateJassString, DestroyJassString и SetJassStringContent на замену небезопасной функции GetJassString.
- 02.04.2023
- Добавил событие происходящее после уничтожения главного потока.
- Дополнительный мод, позволяющий использовать нативки, принимающие параметры с типом code.
- 18.03.2023
- Не ожидал, что функция из стандартной библиотеки для получения индекса элемента в словаре выбросит исключение. Игра больше не должна падать.
- Исправил неинициализирвоанные локальные переменные в UnregisterJassNative, GetJassNative.
- Функции для работы со строками теперь проверяют индексы перед использованием.
JASS API
С этим вам поможет библиотека, которая предлагается к скачиванию в этом ресурсе.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
В примерах используются JASS и Pascal.
Работа с нативными функциями
Вызов нативных функций
Для вызова нативных функций используется конвенция вызова cdecl.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Таблица типов
JASS тип | Тип параметра | Тип результата |
---|---|---|
integer | Int32 | Int32 |
real | PSingle | TSingle (костыль, чтобы значение возвращалось как и целые числа, через регистр процессора EAX, а не через специальный стэк FPU) |
boolean | LongBool | LongBool |
string | PJassString | TJassStringHandle |
handle | TJassObjectHandle | TJassObjectHandle |
code | TJassFunctionHandle | Указатель на байт-код (на деле нет ни одной нативки, что возвращает такое значение) |
При получении от нативки хэндла строки, не забудьте уменьшить её счетчик ссылок, когда она вам не будет более нужна, при помощи функции DecrementHandleReferences.
RegisterJassNative
function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall;
Регистрирует нативную функцию с адресом Address под именем Name и сигнатурой Signature (Подробнее о сигнатурах).
Пример
Предположим, имеется следующая функция, которую нужно добавить в игру:
function SetMinimapImage(FilePath: String): Boolean;
В таком случае, для неё нужно будет сделать адаптированную версию:
function jass_SetMinimapImage(FilePath: PJassString): LongBool; cdecl;
begin
Result:= SetMinimapImage(GetJassStringContent(FilePath));
end;
И затем зарегистрировать:
RegisterJassNative('SetMinimapImage', '(S)B', @jass_SetMinimapImage);
Теперь в игре можно будет воспользоваться ею:
native SetMinimapImage takes string FilePath returns boolean
UnregisterJassNative
function UnregisterJassNative(Name: PChar): LongBool; stdcall;
Удаляет функцию с именем Name из списка зарегистрированных.
Если функция была ранее зарегистрирована, то возвращает True, а иначе False.
Если функция была ранее зарегистрирована, то возвращает True, а иначе False.
Пример
UnregisterJassNative("SetMinimapImage");
GetJassNative
function GetJassNative(Name: PChar): Pointer; stdcall;
Возвращает указатель на нативную функцию с именем Name.
В случае, если функция с указанным именем не была найдена, возвращает нулевой указатель.
В случае, если функция с указанным именем не была найдена, возвращает нулевой указатель.
Пример
type
TSetMinimapImageFunction = function (FilePath: PJassString): LongBool; cdecl;
var
pSetMinimapImage: TSetMinimapImageFunction;
jFilePath: PJassString;
Pointer(pSetMinimapImage):= GetJassNative("SetMinimapImage");
jFilePath:= CreateJassString('war3mapImported/minimap.blp');
pSetMinimapImage(jFilePath);
DestroyJassString(jFilePath);
Работа со строками
GetStringHandle
function GetStringHandle(Value: PChar): TJassStringHandle; stdcall;
Возвращает хэндл строки со значение Value в таблице строк главного потока.
Если такой строки не существует, то создается новая.
Если такой строки не существует, то создается новая.
Не увеличивает счетчик ссылок, так что, прежде чем возвращать хэндл скрипту, следует воспользоваться функцией IncrementStringReferences. В оригинальном интерпретаторе строки продолжат утекать, но с альтернативным эта проблема будет устранена.
Пример
Если нужно создать нативку, возвращающую строку скрипту.
function jass_GetMinimapImage: TJassStringHandle;
begin
Result:= GetStringHandle(FilePath);
IncrementStringReferences(Result);
end;
RegisterJassNative('GetMinimapImage', '()S', @jass_GetMinimapImage);
В карте:
native GetMinimapImage takes nothing returns string
GetStringFromHandle
function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall;
Возвращает указатель на содержимое строки с хэндлом StringHandle из таблицы главного потока.
Если главный потока еще не был создан или в его таблицы нет такого хэндла, то возвращается нулевой указатель.
Если главный потока еще не был создан или в его таблицы нет такого хэндла, то возвращается нулевой указатель.
Пример
Если нужно воспользоваться строкой, возвращенной другой нативной функцией.
procedure OnChatMessage;
var
hMessage: TJassStringHandle;
Message: PChar;
begin
hMessage:= pGetEventPlayerChatString();
Message:= GetStringFromHandle(hMessage);
case Message of
'-start': Start();
'-stop': Stop();
end;
end;
CreateJassString
function CreateJassString(AContent: PChar): PJassString; stdcall;
Создает новый объект-строку с указанным содержимым AContent и возвращает указатель на него.
Такие объекты затем можно передавать нативкам, требующим строки.
Такие объекты затем можно передавать нативкам, требующим строки.
Примечание: функция использует объектный пул, поэтому можете не переживать о падении производительности из-за частого создания и уничтожения строк.
Пример использования смотрите в описании функции GetJassNative.
DestroyJassString
procedure DestroyJassString(AString: PJassString); stdcall;
Уничтожает объект-строку AString.
Пример использования смотрите в описании функции GetJassNative.
SetJassStringContent
procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall;
Устанавливает значение строки AString равным AContent.
Не используйте её для изменения значения строковых параметров нативных функций.
Не используйте её для изменения значения строковых параметров нативных функций.
GetJassStringContent
function GetJassStringContent(JassString: PJassString): PChar; stdcall;
Извлекает строку из элемента таблицы строк главного потока, на который ссылается JassString.
Применяется для извлечения строк из получаемых аргументов в нативных функциях.
Пример смотрите в описании функции RegisterJassNative.
IncrementStringReferences
procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Увеличивает счетчик ссылок у строки с хэндом StringHandle.
Для того, чтобы гарантировать что строка, хэндлом которой вы владеете, не будет внезапно уничтожена, необходимо увеличить её счетчик ссылок.
DecrementStringReferences
procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Уменьшает счетчик ссылок у строки с хэндом StringHandle.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если ранее вы увеличили счетчик ссылок строки, но теперь она вам уже не нужна, то важно уменьшить её счетчик, а иначе она никогда не будет уничтожена.
Хэндлы
IncrementHandleReferences
procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Увеличивает счетчик ссылок на хэндл ObjectHandle, препятствуя его преждевременному уничтожению.
DecrementHandleReferences
procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Уменьшает счетчик ссылок на хэндл ObjectHandle.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.
Полезные события
AddEventHandler
function AddEventHandler(EventType: TJassEventType; Callback: TEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall;
Добавляет в реестр функцию Callback и вызывает её с параметром Parameter при событии EventType.
Всегда создает новую запись и возвращает её хэндл.
Всегда создает новую запись и возвращает её хэндл.
Возможные события:
Имя | Описание |
---|---|
jeMainBeingCalled | Событие создается перед вызовом функции "main" в главном потоке. Здесь можно инициализировать какие-либо данные перед игрой. |
jeMainThreadDestruction | Событие создается перед уничтожением главного потока. Может пригодиться для очистки накопившихся за прошедшую игру данных. |
jeMainThreadDestroyed | Событие создается после уничтожения главного потока. Не вызывайте нативные функции на этой стадии. |
RemoveEventHandler
function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall;
Отменяет подписку с хэндлом ID, который был возвращен функцией AddEventHandler.
PostEvent
procedure PostEvent(EventType: TJassEventType); stdcall;
Уведомляет всех подписчиков, зарегистрированных при помощи функции AddEventHandler о событии EventType.
Используется внутри библиотеки для создания событий, но может пригодится и пользователям.
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
PostEvent(jeMainBeingCalled);
Передача функций в качестве параметров
Для того, чтобы создавать более сложные нативки, а то и вовсе писать весь сценарий на внешнем языке, может потребоваться получать события от игры, но триггеры в качестве действий принимают только JASS-функции.
Эту проблему решает дополнительный мод RedirectCalls. Смените расширение файла из архива на ".mix" и киньте в папку с игрой, после чего все нативки начнут принимать в качестве параметров указатели на внешние функции (указатель на машинные инструкции реального процессора).
При этом JASS-скрипт продолжает работать как ни в чем не бывало.
При этом JASS-скрипт продолжает работать как ни в чем не бывало.
Становится возможным создавать триггеры на равне со скриптом.
Пример нативного скрипта, использующего триггеры.
В ответ на сообщение "hello" от игрока красного пишет "Good bye.".
library NativeScript;
{$mode objfpc}{$H+}
uses
jassapi;
type
TAgentHandle = Dword;
TPlayerHandle = TAgentHandle;
TTriggerHandle = TAgentHandle;
TTriggerActionHandle = TAgentHandle;
TEventHandle = TAgentHandle;
TNativeScriptActionFunction = procedure;
TNativeScriptConditionFunction = function: LongBool;
var
pPlayer: function (number: Int32): TPlayerHandle; cdecl;
pDisplayTimedTextToPlayer: procedure (toPlayer: TPlayerHandle; x, y, duration: PSingle; message: PJassString); cdecl;
pCreateTrigger: function: TTriggerHandle; cdecl;
pTriggerAddAction: function (whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle; cdecl;
pTriggerRegisterPlayerChatEvent: function (whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: PJassString; exactMatchOnly: LongBool): TEventHandle; cdecl;
hChatTrigger: TTriggerHandle;
function Player(Number: Integer): TPlayerHandle;
begin
Result:= pPlayer(Number);
end;
procedure DisplayTimedTextToPlayer(toPlayer: TPlayerHandle; x, y, duration: Single; message: String);
var
jMessage: PJassString;
begin
jMessage:= CreateJassString(PChar(Message));
pDisplayTimedTextToPlayer(toPlayer, @x, @y, @duration, jMessage);
DestroyJassString(jMessage);
end;
function CreateTrigger: TTriggerHandle;
begin
Result:= pCreateTrigger();
end;
function TriggerAddAction(whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle;
begin
Result:= pTriggerAddAction(whichTrigger, actionFunc);
end;
function TriggerRegisterPlayerChatEvent(whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: String; exactMatchOnly: Boolean): TEventHandle;
var
jChatMessageToDetect: PJassString;
begin
jChatMessageToDetect:= CreateJassString(PChar(chatMessageToDetect));
Result:= pTriggerRegisterPlayerChatEvent(whichTrigger, whichPlayer, jChatMessageToDetect, exactMatchOnly);
DestroyJassString(jChatMessageToDetect);
end;
procedure OnChatMessage;
begin
DisplayTimedTextToPlayer(Player(0), 0.0, 0.0, 60.0, 'Good bye.');
end;
procedure InitTrigger;
begin
hChatTrigger:= CreateTrigger();
IncrementHandleReferences(hChatTrigger);
TriggerAddAction(hChatTrigger, @OnChatMessage);
TriggerRegisterPlayerChatEvent(hChatTrigger, Player(0), 'hello', True);
end;
procedure InitNatives;
begin
Pointer(pPlayer):= GetJassNative('Player');
Pointer(pDisplayTimedTextToPlayer):= GetJassNative('DisplayTimedTextToPlayer');
Pointer(pCreateTrigger):= GetJassNative('CreateTrigger');
Pointer(pTriggerAddAction):= GetJassNative('TriggerAddAction');
Pointer(pTriggerRegisterPlayerChatEvent):= GetJassNative('TriggerRegisterPlayerChatEvent');
end;
procedure OnInit({%H-}Parameter: Pointer); stdcall;
begin
InitNatives;
InitTrigger;
end;
begin
AddEventHandler(jeMainBeingCalled, @OnInit, nil);
end.
To leave a comment please sign in to the site.
Блог
Проекты :
Двойное Собачье Иномирье
Re фанфик
Порядок «Волчья Повесть»
Is the official representative of the site Administration
Блог
Проекты :
ТТМФ
Space Engineers
Edited by Vampir_kolik
Edited by IceFog
Похоже, что главным файлом ресурса считается идущий последним в списке, а не первым.