Добавлен , опубликован
Раздел:
Основы
С выходом двух материалов составленых IceFog'ом (xgm.guru/p/wc3/jass-api (Работа с нативными функциями) и xgm.guru/p/wc3/fast-jass#h1.0.1 (Ускоренная виртуальная машина)) про создание своих нативок в War 3(1.26a). Добавляю свой код для создания нативок на языке C++.
JassApi.h
typedef int LongBool;
typedef LongBool (__stdcall *pRegisterJassNative) (char *Name, char *Signature, void *Address);
<DLL_NAME>.cpp
#include "JassApi.h"

#include <windows.h>
#include <stdio.h>

pRegisterJassNative RegisterJassNative = NULL;
int __stdcall test()
{
	return 3;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{	
	if (ul_reason_for_call != DLL_PROCESS_ATTACH) return TRUE;
	DisableThreadLibraryCalls(hModule);

	HMODULE hjApi = GetModuleHandle(L"JassApi.dll");
	if(!hjApi) throw("Could not locate JassApi");

	RegisterJassNative = (pRegisterJassNative)GetProcAddress(hjApi, "RegisterJassNative");
	if(!RegisterJassNative) throw("Could not find RegisterJassNative");

	RegisterJassNative("test", "()I",(void *)test);

	return TRUE;
}
Чтобы создать dll которая будет подгружаться в Warcraft'е, нужно отключить поддержку Clr-среды в свойствах проекта.
Собираем dll. Закидываем её в папку с игрой. Переименовываем её с расширением ".mix". Закидываем в папку с игрой Jass.dll и RedirectCalls.dll из указанных статей выше. RedirectCalls.dll переименовываем в RedirectCalls.mix.
Чтобы вызвать нативку в игре пишем:
native test nothing returns integer
Новые нативки работают и в AI скриптах и в ".j" файле.
Для теста создадим два файла common.ai и human.ai в mpq архиве карты и поместим их по пути "Scripts\".
common.ai - предназначен для общих функций для скриптов human.ai, elf.ai, orc.ai, undead.ai. А эти подскрипты предназначены для AI этих рас.
Код файла common.ai:
native test nothing returns integer
Код файла human.ai:
function main takes nothing returns nothing
    call DisplayTextToPlayer( GetLocalPlayer(), 0.0, 0.0, "hello!123" )
    if test() == 3 then
    call DisplayTextToPlayer( GetLocalPlayer(), 0.0, 0.0, "34444444444444" )
    endif
endfunction
Чтобы подгрузить эти скрипты в карте воспользуемся следующим кодом в файле "war3map.j":
call MeleeStartingAI()
Добавим этот код в мейн функцию в файле "war3map.j".
Добавим через ГУИ пару игроков чтобы проверить скрипт. Выберем расу у компьютера "human".
И при за грузке карты мы должны увидеть следующие два сообщения:
"hello!123"
"34444444444444"
Тест закончен.
Карту и dll закидывать не буду. Учимся всё делать сами.
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
19
11 месяцев назад
0
Шёл 2023 год, а мы только научились добавлять свои нативки. :\
0
11
10 месяцев назад
0
Ev3nt, комюнити делает для игры больше, чем сделали близы, ага)
0
1
10 месяцев назад
0
Можно увидеть пример наивной функции, использующей JREAL?
0
14
10 месяцев назад
Отредактирован IceFog
0
int __stdcall test()
Нативки используют конвенцию вызова cdecl.
Можно увидеть пример наивной функции, использующей JREAL?
Я не специалист по C++, но если хочешь создать нативку принимающую/возвращающую вещественные числа, то это будет выглядеть примерно так:
uint32_t __cdecl jIncrementReal(float *value) {
	return reinterpret_cast<uint32_t>(*value + 1.0f);
}

RegisterJassNative("IncrementReal", "(R)R", &jIncrementReal);
В JASS скрипте объявляется следующим образом:
native IncrementReal takes real value returns real
Также можешь взглянуть на этот ресурс.
В примере оттуда есть сгенерированные обертки для всех нативок из "common.j", но на pascal'е.
0
9
10 месяцев назад
Отредактирован Vampir_kolik
0
IceFog,
cdecl (C calling convention)
Стандартное соглашение о вызовах для программ на C/C++.
В данном соглашение аргументы передаются справа налево и кладутся на стек, как и в [[#STDCALL Standart Calling Convention|stdcall]], возвращаемое значение кладется в регистр EAX. Но вот стек уже очищается функцией, которая вызывает. Имена функций начинаются с символа нижнего подчеркивания, без указания количества байт для аргументов к конце.
stdcall (Standart Calling Convention)
STDCALL это стандартное соглашение для Win32 API. В данном соглашение, аргументы передаются справа налево и очистка стека ложится на вызываемую функцию. Для передачи аргументов используется стек, т.е. перед вызовом нужно положить аргументы на стек. Возвращаемое значение записывается в регистр eax.
КОРОЧЕ, РАЗНИЦА:
__cdecl - очистку стека производит вызывающая программа (*). Основной способ вызова для Си. Это основной способ вызова функций с переменным числом аргументов.
__stdcall (он же winapi) - очистку стека производит вызываемая подпрограмма. (**). Применяется при вызове функций WinAPI.
Даже в твоём коде файла moduleutils.pas используется stdcall:
unit ModuleUtils;

interface

const
	JASS_API_DLL_NAME = 'JassApi.dll';

type
	PJassString = Pointer;
	
	TJassStringHandle = longword;
	TJassFunctionHandle = longword;
	TJassHandle = longword;

	TSingle = record Value: Single end;

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 GetJassString(Value: PChar): PJassString; stdcall; external JASS_API_DLL_NAME;
function GetJassStringContent(JassString: PJassString): PChar; stdcall; external JASS_API_DLL_NAME;
implementation
end.
0
14
10 месяцев назад
0
Даже в твоём коде файла moduleutils.pas используется stdcall:
Библиотека "JassApi.dll" экспортирует функции, используя соглашение о вызове stdcall, а нативки варкрафта, возвращаемые функцией GetJassNative, используют соглашение cdecl.
И приведеный тобою код на самом деле находится в модуле "jassapi.pas".
0
9
10 месяцев назад
0
IceFog, всё равно будет работать. Я проверял.
0
9
10 месяцев назад
0
Да ты прав GetJassNative, используют соглашение cdecl. Но RegisterJassNative я использую c stdcall.
// Natives variable
HRACE (__cdecl *_ConvertRace) (Integer i);
bool fRegistrNatives()
{
	*((int*)&_ConvertRace) = (int)GetJassNative("ConvertRace");
}

HRACE ConvertRace(Integer i)
{
	return _ConvertRace(i);
}
Могу опубликовать все нативные функции на с++.
0
20
10 месяцев назад
Отредактирован Unryze
0
Да ты прав GetJassNative, используют соглашение cdecl. Но RegisterJassNative я использую c stdcall.
Ну, и толку от этого почти никакого. stdcall используется для импортируемых/экспортируемых функций, как на том же примере Storm.dll, а сама игра использует cdecl/thiscall/fastcall, говоря короче, твой выбор != то как должно быть или же правильно. Вызовы stdcall можно вызывать и через __cdecl, только это чревато прикольными такими утечками хипа, а порой даже и стека, так что ну такое себе.
Загруженные файлы
0
14
10 месяцев назад
0
Использование неправильной конвенции вызова приведет к повреждению памяти стэка.
Хранящиеся в нём локальные переменные и адреса возврата могут быть перезаписаны случайными значениями, что приведет к непредсказуемым последствиям.
Хотя, стоит отметить, что виртуальная машина JASS'а (по крайней мере на 1.26а версии) способна правильно вызывать нативки использующие как cdecl так и stdcall, что используется мемхаком.
Но, если такую неправильную нативку вызовет кто другой, то весьма вероятны баги и краши.
0
20
10 месяцев назад
0
Использование неправильной конвенции вызова приведет к повреждению памяти стэка.
Хранящиеся в нём локальные переменные и адреса возврата могут быть перезаписаны случайными значениями, что приведет к непредсказуемым последствиям.
Хотя, стоит отметить, что виртуальная машина JASS'а (по крайней мере на 1.26а версии) способна правильно вызывать нативки использующие как cdecl так и stdcall, что используется мемхаком.
Но, если такую неправильную нативку вызовет кто другой, то весьма вероятны баги и краши.
JassVM пушит на стэк через alloca и вызывает через cdecl, не знаю откуда ты взял stdcall там. Но опять же, не суть. А Мемхак использует std_call для вызова Storm функций, но опять же, эти нюансы на деле важны лишь паре землекопов, ибо большинство эти нюансы не знает и даже не пытается в них лезть (и слава богу).
0
9
10 месяцев назад
0
Unryze, а если я память не освобожу в функции, то это будет утечка при __cdecl, когда я выйду из варкрафта?
1
20
10 месяцев назад
1
Unryze, а если я память не освобожу в функции, то это будет утечка при __cdecl, когда я выйду из варкрафта?
Утечка памяти высвобождается при уничтожении процесса, ибо вся занятая ею память освобождается системой. (По крайней мере так утверждает Microsoft), ибо как ни как эти утечки stack/heap - часть программы, а у неё выделена память от 0x0 до 2GB (при патчере 4GB) памяти. Я не особо практиковал утечки памяти, потому не могу прямо утверждать, что всё так просто или нет, но думаю Microsoft как-то да и контролируют приложения. :D
Чтобы оставить комментарий, пожалуйста, войдите на сайт.