Shared Buffers

The goal of this DLL is to create a platform that other Game Maker DLLs can use to exchange binary data. Binary data can be written to buffers and read back later. Buffers can also be used directly in Game Maker (in GML), so it's also possible to read or write binary data manually.

This DLL provides little functionality on its own, it is designed to be used together with other DLLs that support Shared Buffers.

Using buffers

Buffers are simply blocks of memory with a certain length. Buffers also have a position, which indicates where the data will be read and where the new data will be written. The position can never be higher than the length of the buffer. Buffers also have an error flag which indicates whether an error has occurred.

Data can be written to buffers with one of the write functions. New data will be written to the current position of the buffer, and the position will be advanced. The buffer will be enlarged if required. For example, suppose the buffer has a length of 20 bytes and the position is 18. If you write 4 bytes of data to this buffer, its new length will be 22 and its new position will be 22 too.

Data can be read from buffers with one of the read functions. The data will be read from the current position of the buffer, and the position will be advanced. The buffer will not be enlarged if you try to read past the end of the buffer, instead the function will return the default value and the error flag will be set. The value depends on the type of data you're reading:

Types of data

This table lists the most important properties of each type.

Type Size (bytes) Lowest value Highest value Precision Other names
int8 1 -128 127 1 byte, char
uint8 1 0 255 1 unsigned byte, unsigned char
int16 2 -32.768 32.767 1 short
uint16 2 0 65.535 1 unsigned short, WORD
int32 4 -2.147.483.648 2.147.483.647 1 int
uint32 4 0 4.294.967.296 1 unsigned int, DWORD
int64 8 -9.223.372.036.854.775.808 9.223.372.036.854.775.807 1 long long
uint64 8 0 18.446.744.073.709.551.615 1 unsigned long long, QWORD
float32 4 -1.7014e38 1.7014e38 ~8 significant figures float
float64 8 -8.9885e307 8.9885e307 ~16 significant figures double
string length+1 - - - NULL-terminated string, C string

GML functions

Example code

Writing data to a buffer and storing it in a file:

var buffer;

// create a buffer
buffer = buffer_create();

// write data to the buffer
buffer_write_uint8(buffer, 42);
buffer_write_uint8(buffer, 77);
buffer_write_float32(buffer, 3.14);
buffer_write_string(buffer, "hello world");

// save the data to a file
buffer_write_to_file(buffer, "data.txt");

// destroy the buffer
buffer_destroy(buffer);

Loading the file and reading the data:

var buffer, a, b, c, d;

// create a buffer
buffer = buffer_create();

// read the data from the file
if !buffer_read_from_file(buffer, "data.txt") {
    buffer_destroy(buffer);
    show_message("Error: Reading data.txt failed! Make sure the file exists.");
    exit;
}

// read data from the buffer
// this should be done in exactly the same order
a = buffer_read_uint8(buffer);
b = buffer_read_uint8(buffer);
c = buffer_read_float32(buffer);
d = buffer_read_string(buffer);

// display the result
show_message("a = "+string(a)+"#b = "+string(b)+"#c = "+string(c)+"#d = "+d);
// output:
//   a = 42
//   b = 77
//   c = 3.14
//   d = hello world

// destroy the buffer
buffer_destroy(buffer);

For DLL developers

If you're creating your own DLL and you want to use Shared Buffers, just include SharedBuffers.h in your project. This file defines the struct SharedBufferInterface which your DLL can use as an interface to manipulate buffers. Before you can use the interface, you have to create a function to set the interface address:

#include "SharedBuffers.h"

SharedBufferInterface *sbi = NULL;

gmexport double yourdllname_init_shared_buffers(double handle) {
    sbi = (SharedBufferInterface*)((uintptr_t)(handle));
    return 1;
}

This function has to be called in Game Maker before you can use the Shared Buffers:

// initialize shared buffers interface for yourdllname.dll
yourdllname_init_shared_buffers(shared_buffers_get_handle());

After the interface address has been set, you can use Shared Buffers in your DLL just like you would do in GML:

gmexport double yourdllname_some_function(double buffer_id) {
	if(sbi == NULL) return 0; // the interface address has not been set yet
	Buffer *b = sbi->Find((unsigned int)(buffer_id));
	if(b == NULL) return 0; // the buffer does not exist
	sbi->WriteInt32(b, 12345);
	sbi->WriteString(b, "Hello world");
	sbi->WriteFloat32(b, 42.42f);
	return 1;
}

C++ functions

The C++ functions are very similar to their GML counterparts, but they use pointers instead of ids.

There's one small difference though: Unlike buffer_set_length, SetLength does not fill the new memory with null bytes when the buffer is enlarged. It is the responsibility of the DLL developer to fill the buffer with valid data.

ToString and GetData are very similar, but not identical. ToString returns a const char* and GetData returns a char*. If the buffer is not empty, ToString and GetData will both return the same pointer. But if the buffer is empty, ToString will return a pointer to an empty null-terminated string and GetData will return NULL. The pointer returned by ToString and GetData remains valid until the buffer is resized (either by calling SetSize or by writing past the end of the buffer).

Below is a list of all C++ functions:

Buffer* Create();
void Destroy(Buffer* buffer);
unsigned int GetID(Buffer* buffer);
Buffer* Find(unsigned int id);
const char* ToString(Buffer* buffer);
char* GetData(Buffer* buffer);
unsigned int GetPos(Buffer* buffer);
unsigned int GetLength(Buffer* buffer);
bool IsAtEnd(Buffer* buffer);
bool GetError(Buffer* buffer);
void ClearError(Buffer* buffer);
void Clear(Buffer* buffer);
void SetPos(Buffer* buffer, unsigned int newpos);
void SetLength(Buffer* buffer, unsigned int newlength);
bool ReadFromFile(Buffer* buffer, const char* filename);
bool WriteToFile(Buffer* buffer, const char* filename);
int8_t ReadInt8(Buffer* buffer);
uint8_t ReadUint8(Buffer* buffer);
int16_t ReadInt16(Buffer* buffer);
uint16_t ReadUint16(Buffer* buffer);
int32_t ReadInt32(Buffer* buffer);
uint32_t ReadUint32(Buffer* buffer);
int64_t ReadInt64(Buffer* buffer);
uint64_t ReadUint64(Buffer* buffer);
float ReadFloat32(Buffer* buffer);
double ReadFloat64(Buffer* buffer);
void WriteInt8(Buffer* buffer, int8_t value);
void WriteUint8(Buffer* buffer, uint8_t value);
void WriteInt16(Buffer* buffer, int16_t value);
void WriteUint16(Buffer* buffer, uint16_t value);
void WriteInt32(Buffer* buffer, int32_t value);
void WriteUint32(Buffer* buffer, uint32_t value);
void WriteInt64(Buffer* buffer, int64_t value);
void WriteUint64(Buffer* buffer, uint64_t value);
void WriteFloat32(Buffer* buffer, float value);
void WriteFloat64(Buffer* buffer, double value);
const char* ReadString(Buffer* buffer);
void WriteString(Buffer* buffer, const char* string);
void ReadData(Buffer* buffer, char* ptr, unsigned int bytes);
void WriteData(Buffer* buffer, const char* ptr, unsigned int bytes);
void ReadHex(Buffer* buffer, char* ptr, unsigned int bytes);
void WriteHex(Buffer* buffer, const char* ptr, unsigned int bytes);
void WriteBuffer(Buffer* buffer, Buffer* source);
void WriteBufferPart(Buffer* buffer, Buffer* source, unsigned int pos, unsigned int bytes);

The functions GetID and Find can be used to convert pointers to ids and vice versa.

Good luck!