|
GA的原帖已经丢失,这个帖子还原自飞雪殿的Blog上的备份
http://flyingsnow.myinfo.ws/
注意所有的相关文件都在本帖附件中
在这篇指南中,我将告诉你如何使用xttoc's jAPI来些你自己的War3本地函数。你必须先了解C/C++并且有一个你了解其调用协定的编译器。我将使用Borland's bcc 5.60。很不幸的,我无法得到任何免费的工具。
理论上使用这个技术我们可以做任何事,虽然可能需要大量的研究。但有一个严重的限制,那张地图无法在BNet上发布。我要明确的警告某些为地图写运行库的人,因为那将导致安全问题。不管怎么样,我们先开始。在C程序中数据结构特别细致。我们将继承两个:一个堆-只要动态分配数组即可。然后我们做一些更有想象里的事,一个迷宫的不相交集。
将附件中的API工具材料Copy到你的War3目录,除了jNatives文件夹外-你需要留下文件夹,但要清空其中的内容。你很可能需要为loader建立快捷方式,只需为LoaderWar3.exe添加标记一个window命令行来测试,如"D:\games\Warcraft III\LoaderWar3.exe" -window
我们需要做的流程概要:
-写本地函数
-将本地函数导入War3
-将本地函数的声明添加到common.j
-用本地函数写代码
-运行游戏
我们以经典的Hello World开始。以下是test.cpp
- /*
- The following routines are all exported by japi.dll:
- char* MemStrSearch(unsigned char *start, unsigned char *end, char *str);
- bool MemPatternCompare(unsigned char *address, unsigned char *pattern, unsigned char *mask, unsigned long length);
- unsigned char* MemPatternSearch(unsigned char *start, unsigned char *end, unsigned char *pattern, unsigned char *mask, unsigned long length);
- PIMAGE_SECTION_HEADER GetImageSectionHeaders(HMODULE hModule, WORD *count);
- PIMAGE_SECTION_HEADER GetImageSectionHeader(HMODULE hModule, unsigned char name[8]);
- void jAPI jBindNative(void *routine, char *name, char *prototype);
- void jAddNative(void *routine, char *name, char *prototype);
- jString jAPI jStrMap(char *str);
- char* jAPI jStrGet(jString strid);
- */
- // - Andy Scott aka xttocs
- #include <windows.h>
- #include <stdio.h>
- #define jNATIVE __stdcall
- #define jAPI __msfastcall
- #define FloatAsInt(f) (*(long*)&f)
- #define IntAsFloat(i) (*(float*)&i)
- typedef long jString;
- typedef long jInt;
- typedef long jReal;
- typedef void (jAPI *jpAddNative)(void *routine, char *name, char *prototype);
- typedef jString (jAPI *jpStrMap) (char *str);
- typedef char * (jAPI *jpStrGet) (jString strid);
- jpAddNative jAddNative;
- jpStrMap jStrMap;
- jpStrGet jStrGet;
- #pragma warning ( disable : 4996 )
- jString jNATIVE test(jString js, char *fnname)
- {
- char *s;
- s = jStrGet(js);
- s[0] = 'A';
- return jStrMap(s);
- }
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
- {
- if (ul_reason_for_call == DLL_PROCESS_ATTACH)
- {
- DisableThreadLibraryCalls(hModule);
- HMODULE hjApi = GetModuleHandle("japi.dll");
- jAddNative = (jpAddNative)GetProcAddress(hjApi, "jAddNative");
- jStrMap = (jpStrMap)*GetProcAddress(hjApi, "jStrMap");
- jStrGet = (jpStrGet)*GetProcAddress(hjApi, "jStrGet");
- jAddNative(test, "test", "(S)S");
- }
- return TRUE;
- }
复制代码
在这里有两点很重要
- jString jNATIVE test(jString js, char *fnname)
- {
- char *s;
- s = jStrGet(js);
- s[0] = 'A';
- return jStrMap(s);
- }
复制代码
和
- jAddNative(test, "test", "(S)S");
复制代码
如果运行时崩溃,并且你没有使用Borland的编译器,试一下用__fasecall代替__msfastcall。
我们的测试函数接收一个string的参数并返回一个string。它仅仅简单的用'A'代替string中的第一个字符,并且无法处理传递的空的string。此外,引擎会将调用的函数名传递给本地,所以我们要将函数名包括在内以确保当我们完成时堆栈被释放。
jAddNative函数的第三个参数给出了类型信息,括号内就是参数,如(ISI)代表integer x,string s,integer y。在括号后是返回类型,在我们的例子中是s,即string。
我们需要编译成一个DLL,用一个命令行:
- bcc32 -WD -e"test.xjp" test.cpp
复制代码
-WD表示build一个DLL,-e后是输出的文件,test.cpp是输入的源文件。文件扩展名是.xjp而不是.dll,因为这样才能被jAPI识别并链接如War3。现在将test.xjp复制到jAPI将要检查,也就是War3目录下的jNatives文件夹中(如果不复制则留空)。
接下来我们将本地函数添加到common.j。如果你没有它的一份副本,可以用mpq工具从war3patch.mpq中解出来。
[jass]native test takes string s returns string [/jass]
现在从jAPI包中启动LoaderWorldEditor.exe。如果什么也没有发生,可能是war3的注册信息问题,重装下War3试试。如果崩溃了,很可能是你设定的函数的调用约定出了问题,你可以将错误信息发送给我看看。好,现在运行WE,创建一张新地图,做任何您想做的事,将你修改过的common.j导入,并把它的路径从war3mapImported\改为Scripts\来确保它把默认的common.j覆盖。如果几次保存过后你仍然得到本地函数声明问题,检查一下你在common.j中设置的类型和在jAddNative中设置的类型是否匹配。
如果一切OK,写一些JASS来测试test函数。试一下游戏开始几秒后执行BJDebugMsg(test("Hello"))。保存地图。如果仍出现本地函数的声明问题,检查common.j的原型。如果保存无误,不要动“测试地图”按钮。运行jAPI LoaderWar3.exe代替,或者用命令行-window来运行会更合适。选择你的地图并开始、如果选中地图后没有显示玩家信息,很有可能你在.xjp文件中丢失了一些本地函数。所有你添加到common.j中的本地函数在你的自定义DLL中都要有入口。
如果游戏运行后显示"Aello",恭喜,你完成了你的第一个自定义本地函数。现在让我们做一些更有趣的事。动态分配内存的缺乏是JASS编程者的一个共同的遗憾。我们甚至不能传递数组参数!在C中,我们可以轻松的用malloc()来分配一大块内存。这有些危险,因为这些分配的内存并不会随着地图结束而释放。尽管我们需要手动释放,但它依然不失为一个好的实践。简单起见,并且我们有return bug来协调,我们仅仅需要用integer来实现数组。设想我们需要4个函数:
[jass]native ArrayAlloc takes integer length returns integer
native ArrayFree takes integer arr returns nothing
native ArrayGet takes integer arr, integer index returns integer
native ArraySet takes integer arr, integer index, integer val returns nothing[/jass]
将它们添加到common.j。
接下来复制test.cpp到array.cpp并且用上面的数组本地函数代替原来的test本地函数
- jInt jNATIVE ArrayAlloc(jInt len, char *fnname)
- {
- return (jInt)malloc(sizeof(jInt)*len);
- }
- jInt jNATIVE ArraySet(jInt array,jInt index,jInt val, char *fnname)
- {
- int *p = (int *)array;
- p[index] = val;
- return p[index];
- }
- jInt jNATIVE ArrayGet(jInt array, jInt index, char *fnname) {
- int *p = (int *) array;
- return p[index];
- }
- jInt jNATIVE ArrayFree(jInt array, char *fnname) {
- int *p = (int *)array;
- free(p);
- return 0;
- }
复制代码
在所有的函数中我都返回了一些值,因为JASS似乎希望所有的函数都有返回值,即使函数什么也没有返回。
要给以上的数组本地函数添加注册信息,我们要写以下几行:
- jAddNative(ArrayAlloc,"ArrayAlloc","(I)I");
- jAddNative(ArraySet,"ArraySet","(III)I");
- jAddNative(ArrayGet,"ArrayGet","(II)I");
- jAddNative(ArrayFree,"ArrayFree","(I)I");
复制代码
用 bcc32 -WD -e "array.xjp" array.cpp 来编译,并用array.xjp代替jNatives文件夹下的test.xjp
测试一些简单的代码,如:
[jass]local integer heap = ArrayAlloc(16)
call ArraySet(heap,5,42)
call BJDebugMsg(I2S(ArrayGet(heap,5)))
call ArrayFree(heap)[/jass]
如果有问题,可以参考上面test实例中的错误信息
练习:为数组添加边界检测和动态调整大小
以下是另一些本地函数,和他的迷宫生成地图,一个不相交集
- typedef struct {
- int parent;
- int rank;
- } node;
- jInt jNATIVE DJSNew(jInt len, char *fnname)
- {
- node *set = (node *)malloc(sizeof(node)*len);
- int i;
- for(i=0;i<len;i++) {
- set[ i ].parent = -1;
- set[ i ].rank = 0;
- }
- return (int)set;
- }
- jInt jNATIVE DJSFind(jInt iset, jInt x, char *fnname) {
- node *set = (node *)iset;
- if (set[x].parent == -1) return x;
- set[x].parent = DJSFind(iset,set[x].parent);
- return set[x].parent;
- }
- jInt jNATIVE DJSUnion(jInt iset,jInt x,jInt y, char *fnname)
- {
- node *set = (node *)iset;
- int xr = DJSFind(iset,x);
- int yr = DJSFind(iset,y);
- if(set[xr].rank > set[yr].rank)
- set[yr].parent = xr;
- else if(set[xr].rank < set[yr].rank)
- set[xr].parent = yr;
- else {
- set[yr].parent = xr;
- set[xr].rank++;
- }
- return 0;
- }
- jInt jNATIVE DJSFree(jInt iset, char *fnname) {
- free((node *)iset);
- return 0;
- }
复制代码
一个比较常用的结构pair和配套函数
- typedef struct {
- int car;
- int cdr;
- } pair;
- pair *allocpair() {
- return (pair *)malloc(sizeof(pair));
- }
- void freepair(pair *p) {
- free(p);
- }
- jInt jNATIVE cons(jInt x, jInt y, char *fnname) {
- pair *newpair = allocpair();
- newpair->car = x;
- newpair->cdr = y;
- return (jInt)newpair;
- }
- jInt jNATIVE car(jInt ipair, char *fnname) {
- pair *p = (pair *)ipair;
- return p->car;
- }
- jInt jNATIVE cdr(jInt ipair, char *fnname) {
- pair *p = (pair *)ipair;
- return p->cdr;
- }
复制代码
原型字母表:
- Integer I
- Real R
- String S
- Code C
- Boolean B
- Returns nothing V
- Handles H; (?)
- Handle ext. H followed by ext followed by semicolon e.g. Hplayer;
复制代码
非常感谢xttocs所做的所有努力和你的阅读。如果你设法用其他不同的编译器做这些工作,或者想用Handle做一些事或者其他任何实际问题,请发送消息!祝你好运。
---------
一些好的编译器
Borland 5.60
Borland 5.5(免费)
---------
重要更新:每个本地函数的最后需要一个额外的"char * fnname"参数。War3会把调用的本地函数名传递进来,我们需要它来确保栈被释放。感谢xttocs。
如果你的stock loader有问题,试一下loader.rar中的那个。 |
|