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.
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:
Numbers: The default value is 0.
Strings: The default value is an empty string.
Raw data: The default value is a block of null bytes (if you try to read 4 bytes, the result is 4 bytes with a value of 0, which evaluates to an empty string in GML).
Hexadecimal data: The default value is a block of zero characters (if you try to read 4 bytes, the result is "00000000")
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 |
shared_buffers_get_handle() - This function returns the address of the Shared Buffer Interface. You can't use this number in GML, but other DLLs will need it to use Shared Buffers.
buffer_create() - Creates a new buffer and returns the id. Don't forget to destroy the buffer when you don't need it anymore!
buffer_destroy(id) - Destroys the buffer with the given id.
buffer_exists(id) - Returns whether a buffer with the given id exists.
buffer_to_string(id) - Returns the contents of the buffer as a string. This won't work correctly if the buffer contains null bytes (bytes with a value of 0), because Game Maker thinks the null byte marks the end of the string. Only use this if you know the buffer doesn't contain null bytes.
buffer_get_pos(id) - Returns the current position of the buffer.
buffer_get_length(id) - Returns the length of the buffer, in bytes.
buffer_at_end(id) - Returns whether the current position is at the end of the buffer. This is the same as checking whether the position is equal to the length of the buffer.
buffer_get_error(id) - Returns whether the error flag has been set. The error flag is set when you try to read past the end of the buffer.
buffer_clear_error(id) - Clears the error flag of the buffer.
buffer_clear(id) - Clears the buffer. The position and length of the buffer will be set to zero. This function also clears the error flag.
buffer_clear_error(id) - Clears the error flag of the buffer.
buffer_set_pos(id,pos) - Sets the current position of the buffer.
buffer_read_from_file(id, filename) - Clears the buffer, then reads the contents of the file with the given filename, and copies the contents to the buffer. Returns whether successful.
buffer_write_to_file(id, filename) - Writes the contents of the buffer to the file with the given filename. Returns whether successful.
buffer_read_int8(id) buffer_read_uint8(id) buffer_read_int16(id) buffer_read_uint16(id) buffer_read_int32(id) buffer_read_uint32(id) buffer_read_int64(id) buffer_read_uint64(id) buffer_read_float32(id) buffer_read_float64(id)
Reads a number from the buffer at the current position, and advances the position. If you try to read past the end of the buffer, the error flag will be set and the function will return 0.
buffer_write_int8(id, value) buffer_write_uint8(id, value) buffer_write_int16(id, value) buffer_write_uint16(id, value) buffer_write_int32(id, value) buffer_write_uint32(id, value) buffer_write_int64(id, value) buffer_write_uint64(id, value) buffer_write_float32(id, value) buffer_write_float64(id, value)
Writes a number to the buffer at the current position, and advances the position. If you try to write past the end of the buffer, the buffer will be enlarged.
buffer_read_string(id) - Reads a string from the buffer at the current position, and advances the position. If you try to read past the end of the buffer, the error flag will be set and the function will return an empty string.
buffer_write_string(id, string) - Writes a string to the buffer at the current position, and advances the position. If you try to write past the end of the buffer, the buffer will be enlarged.
buffer_read_data(id, bytes) - Reads raw data from the buffer at the current position, and advances the position. If you try to read past the end of the buffer, the error flag will be set and the function will return an empty string. This won't work correctly if the raw data contains null bytes (bytes with a value of 0), because Game Maker thinks the null byte marks the end of the string. Only use this if you know the raw data doesn't contain null bytes.
buffer_write_data(id, string) - Writes raw data to the buffer at the current position, and advances the position. If you try to write past the end of the buffer, the buffer will be enlarged. This function is almost identical to buffer_write_string, but this function will not write a null byte to mark the end of the string. This means you have to know the size of the raw data in advance if you want to read it again later.
buffer_read_hex(id, bytes) - Reads hexadecimal data from the buffer at the current position, and advances the position. If you try to read past the end of the buffer, the error flag will be set and the function will return a string of zero characters with the correct length (for example, if you try to read 4 bytes the result will be "00000000").
buffer_write_hex(id, string) - Writes hexadecimal data to the buffer at the current position, and advances the position. If you try to write past the end of the buffer, the buffer will be enlarged.
buffer_write_buffer(id, source_id) - Writes the contents of the buffer source_id to the buffer id at the current position, and advances the position. If you try to write past the end of the buffer, the buffer will be enlarged. The source and destination buffer can be the same.
buffer_write_buffer_part(id, source_id, offset, bytes) - Writes a part of the contents of the buffer source_id to the buffer id at the current position, and advances the position. offset is the offset of the part, bytes is the length of the part. If you try to write past the end of the buffer, the buffer will be enlarged. The source and destination buffer can be the same.
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);
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; }
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!