Gamemaker Studio 2 GML与buffer与C++

Author Avatar
空気浮遊 2021年02月16日
  • 在其它设备中阅读本文章

终于弄明白了 GMS2 怎么用 C++ 的东西做扩展了。所以来记录一下 GML 中的 string、buffer 和 DLL 扩展相关内容。

字符串 - string

GML 中的 string 十分傻逼。具体来说:

  • string 类型不能当成 array 来用,比如用下标访问内容。要访问某个字符的内容需要用函数。
  • string 序列从 1 开始编号。
  • GML 没有 char 类型变量。利用chrord以进行 unicode 到字符(单字符字符串)的转换。
  • 单字符也是用双引号括起来的。单引号在 GMS2 中被抛弃了。(在 GM8 中是可以交换使用的)

但 string 支持一些友好的运算:

  • 字符串可以相加。例如:"51_"+"513"
  • 可以将常用类型强制类型转化为 string,例如 real(实数)和 int64(整数)。例如:string(12.0)
  • 可以对实数转化用函数做更精确的调整,即string_format函数。

通过 这里 来访问 string 在官方文档上的内容。

内存处理 - buffer

GML 中唯一可以按字节对内存进行任意处理的数据结构。类似 unsigned char 数组。

buffer 内置一个指针,用于处理目前读写的位置。修改指针位置需要用函数 buffer_seek,访问具体位置的函数是 buffer_peek

创建 buffer 使用函数buffer_create(size, type, alignment)。参数分别为 buffer 的大小(字节数)、类型和对齐(字节数)。

常用的类型是两种:buffer_fixedbuffer_grow。前者规定 buffer 为固定大小,后者规定 buffer 在写入更多的内容时大小会增长。

对齐这个东西比较神奇。它说的是你每次用内置函数处理 buffer 内容时,指针移动的字节数,即多少字节分成一格。但buffer_tell这个用于返回你目前处理到第几字节的函数只会返回你最后一次写入的 字节数 位置 +1,这与你即将要写入的下一个位置是 不同 的。挺意义不明的,实际上大部分时候你只需要将对齐设置为 1 就行,设置错误的话可能会出奇怪的错误,这依赖于进一步的实验。

关于buffer_grow,你可能会需要在 buffer 后写入 buffer,但这玩意是必须要用buffer_copy来实现的。在使用buffer_copy时,buffer_grow大小不会增长。

buffer 的数据处理主要依赖于buffer_readbuffer_write。指定写入的数据类型和值即可对内存进行操作。常用类型为buffer_u8(无符号 8 位整形,即 8 位二进制数),buffer_stringbuffer_text等。buffer_text是不以 0 值结尾的字符串,但谁也不知道 GML 的 string 相关函数会怎么处理含 0 值的字符串。通常使用base64_encode等不受 0 值影响的函数来处理字符串使得字符串不包含 0 值。

buffer 是一种数据结构,在 GML 中理所当然的也通过一个独立 ID 进行访问。这意味着你可以在独立函数里对 buffer 进行处理,类似指针。GML 本身并不支持指针或者引用等特性,甚至都不是很支持递归。

通过 这里 来访问 buffer 在官方文档上的内容。

GML 与 C++ - DLL 扩展的编写

新的编译器 YYC 支持将 GML 工程翻译成 C ++ 代码交给编译器编译成程序,虽然 bug 不少。故这给我们提供了一个新的将 GML 与 C ++ 共同运作的方式,即直接修改翻译过后的 C ++ 源代码再进行编译。GM 社区已经有老哥做了这项工作,你可以自行 google 搜索相关信息。

当然还有一种更方便和友好的方式就是使用编译好的 DLL(动态链接库)来与 GML 下的游戏进行直接沟通,而无需使用什么命令行和文件 IO 等绕圈子的方式。

动态链接库(英语:Dynamic-link library,缩写为 DLL)是微软公司在微软视窗操作系统中实现共享函数库概念的一种实现方式。这些库函数的扩展名是.DLL、.OCX(包含 ActiveX 控制的库)或者.DRV(旧式的系统驱动程序)。

所谓动态链接,就是把一些经常会共享的代码(静态链接的 OBJ 程序库)制作成 DLL 档,当可执行文件调用到 DLL 档内的函数时,Windows 操作系统才会把 DLL 档加载存储器内,DLL 档本身的结构就是可执行档,当程序有需求时函数才进行链接。通过动态链接方式,存储器浪费的情形将可大幅降低。静态链接库则是直接链接到可执行文件。

DLL 的文件格式与视窗 EXE 文件一样——也就是说,等同于 32 位视窗的可移植执行文件(PE)和 16 位视窗的 New Executable(NE)。作为 EXE 格式,DLL 可以包括源代码、数据和资源的多种组合。

在更广泛的意义上说,任何同样文件格式的电脑文件都可以称作资源 DLL。这样的 DLL 的例子有扩展名为 ICL 的图标库、扩展名为 FON 和 FOT 的字体文件。

你可以在 这里 访问 YYG 官方提供的 CPP 扩展例子。总的概括下来实际上模板如下:

#include<cstring>

#if !defined( _MSC_VER)
#define EXPORTED_FN __attribute__((visibility("default")))
#else
#define EXPORTED_FN __declspec(dllexport)
#endif

extern "C" {
    EXPORTED_FN double fun(char *str) {
        // ...
    }
    // ...
    // your program
    // ...
}

GML 实际上只有两种类型,一种是 double,一种是字符串。因此在 C ++ 的 DLL 扩展中,参数和返回值的类型只能为char*(void*)或者double。看上去似乎十分受限制,但 GML 同样也支持传递 buffer,即使用buffer_get_address(buff)函数,它会返回一个 buffer 的内存地址供 DLL 使用。但这个内存地址不能被释放和改变大小,也不能越界写入或访问数据。

编译和使用十分简单。在 VS2019 中新建控制台项目,然后写好你的程序,访问项目属性,配置类型改为.dll,F5 编译即可。

在 GMS 中新建 Extension,在 Resources 一栏中选择 Add File,进入下一级后在 Functions 一栏中选择 Add Function, 在 Name 中填写 GML 下的函数命名(你在 GML 中通过这个名字调用该函数),在 Extern Name 中填写 C ++ 中的函数名。选择返回值类型和添加 arguments,然后在 Help 中写下函数描述,例如fun(buffer, size)

每次重新编译之后都需要重新添加该 DLL。

Something else

C++ 真的比 GML 快多了。