Skip to content

user_gmidl_docs

Francisco Dias edited this page Apr 9, 2026 · 7 revisions

GMIDL Introduction

GMIDL (GameMaker Interface Definition Language) is how you describe your extension API. From one .gmidl file, extgen generates GML bindings, C++ bridge code, Android glue, iOS/tvOS glue, console integration, and CMake projects.


Table of Contents

  1. Basic Structure
  2. Attributes
  3. Type Rules
  4. Using type_hint
  5. Collections
  6. Optional Types
  7. Functions
  8. Classes (Structs)
  9. Enums
  10. Quick Rules Summary
  11. Special Types: Functions & Buffers

1. Basic Structure

A GMIDL file looks like this:

[gml_api]
module MyExtension;

// Functions
[global, bind = typed_gml]
function my_function() : unit;

// Classes
[global]
class `[[MyStruct]]` prototype Object
{
    [field]
    property value : string { get; set; }
}

// Enums
[global, bind = typed_gml]
enum MyEnum {
    A,
    B
}

You define functions, classes (structs), and enums.


2. Attributes

Anything inside brackets [...] is an attribute. Attributes modify how extgen generates bindings, wrappers, metadata, and editor behavior.

[global, bind = typed_gml]
function my_function() : unit;

Attributes can be:

  • Flags - global
  • Key/value pairs - bind = typed_gml
  • Type modifiers - type_hint = `uint64`

They can be applied to functions, classes, properties ([field]), class functions, and structs.


Commonly Used Attributes

global

Marks a function or class as globally accessible in GML.

[global]
function do_something() : unit;

Important

GameMaker functions are globally accessible by default. global currently has no enforced namespace effect, but it exists for forward compatibility. When targeting environments that support namespaces (such as future GMRT-only builds), this attribute will affect symbol visibility. Always declare it explicitly when required by spec.


bind = typed_gml

Controls how a function binds to GML. typed_gml enables strict typed wrapper generation.

[bind = typed_gml]
function add(self, value : int32) : unit;

field

Marks a property as a generated struct field. Only properties marked [field] are generated.

[field]
property id : int32 { get; set; }

type_hint

Overrides or clarifies native typing. Used when marshaling needs explicit type control.

[field, type_hint = `uint64`]
property handle : gmval { get; set; }

hidden

Removes the symbol from GML auto-complete (IntelliSense). Can be applied to functions, properties, and classes. Does not change binding behavior.

[hidden]
function __internal_helper() : unit;

start_fn

Marks a function to be called automatically when the game starts.

[start_fn]
function initialize() : unit;

Rules: only one start_fn per module; cannot be used inside a class; must be a top-level function.


finish_fn

Marks a function to be called automatically when the game finishes.

[finish_fn]
function shutdown() : unit;

Rules: only one finish_fn per module; cannot be used inside a class; must be a top-level function.


Attribute Constraints

  • start_fn and finish_fn cannot be applied to class methods.
  • A module cannot declare more than one of each.
  • field only applies to properties.
  • hidden only affects editor visibility.
  • global exists for future namespace-aware targets.

3. Type Rules

There are two ways to declare types.


A) Direct types

These can be written directly after ::

double
float
int32
uint32
int64
bool
string
object
array
gmval
func
unit  (return only)
function add(a: int32, b: int32) : int32;

property name : string;

B) Everything else requires type_hint

Use type_hint when the type is:

  • uint8, int8, uint16, int16, uint64
  • buffer
  • A custom enum
  • A custom class
  • Optional (?)
  • A collection ([])
  • A fixed-size collection ([X])

The hint value must be inside backticks:

type_hint = `...`

4. Using type_hint

uint64

[field, type_hint = `uint64`]
property channel_id : gmval { get; set; }

The visible type stays gmval; the real type is defined in type_hint.


Custom struct

[field, type_hint = `DiscordUserHandle`]
property user : gmval { get; set; }

Custom enum

[field, type_hint = `DiscordStatusType`]
property status : gmval { get; set; }

5. Collections

Collections require type_hint.

Dynamic array

[field, type_hint = `uint64[]`]
property participants : gmval { get; set; }

Fixed-size array

[field, type_hint = `uint8[16]`]
property key : gmval { get; set; }

6. Optional Types

Optionals require type_hint.

[field, type_hint = `uint64?`]
property parent_id : gmval { get; set; }

Features can be combined:

[field, type_hint = `DiscordUserHandle[]?`]
property users : gmval { get; set; }

7. Functions

Parameters

function add(a: int32, b: int32) : int32;

When a parameter requires a special type:

function get_user([type_hint = `uint64`] user_id) : gmval;

Return Types

Normal return type:

function get_count() : int32;

Custom or advanced return type - the type_hint goes in the function attributes:

[global, bind = typed_gml, type_hint = `DiscordCall`]
function get_call() : gmval;
Location Where does type_hint go?
Parameter Next to the parameter
Return value In the function attribute list

8. Classes (Structs)

Classes represent structured objects returned to GML. They generate native structs (C++, Java, ObjC, Swift), GML constructors, and optional class-bound functions.

Required Format

[global]
class `[[StructName]]` prototype Object
{
    [field]
    property property_name : type { get; set; }

    [bind = typed_gml]
    function method_name(self, arg : type) : return_type;
}

Rules

  1. Must have [global].
  2. Name must use special syntax - inside backticks, wrapped in [[ ]]:
    class `[[User]]`
    
  3. Prototype must be Object.
  4. Properties must use the property keyword, be marked [field], and include { get; set; }.

Class Functions

Classes may declare functions inside their body. These are structural GML methods, not native struct methods.

[global]
class `[[BenchmarkSuite]]` prototype Object
{
    [bind = typed_gml]
    function add(self, fun : func) : unit;

    [bind = typed_gml]
    function run_all(self) : unit;
}

On the GML side, the generated constructor includes these functions as struct members:

var suite = new BenchmarkSuite();
suite.add(my_func);
suite.run_all();

self is not passed manually in GML - the wrapper injects the struct instance as the first argument.

On the native side, extgen generates namespaced free functions:

void BenchmarkSuite__add(const BenchmarkSuite& self, GMFunction fun);
void BenchmarkSuite__run_all(const BenchmarkSuite& self);
  • The function name is prefixed with the class name.
  • self is automatically typed as the containing class.
  • You do not declare a type for self in GMIDL.

Design Notes

Class functions use a free-function pattern at the native level. This keeps the ABI surface lightweight and works well with handle-based designs:

[field]
property handle : uint64 { get; set; }

Store a small native handle in the struct and implement all behavior via class functions that operate on that handle. This keeps native memory ownership controlled and the GML interface clean.


Class Constraints

  • Class functions must use normal GMIDL function syntax.
  • The first parameter must be named self.
  • self is resolved as the containing class type - no explicit type needed.
  • Native structs do not receive member methods.
  • Only properties marked [field] are generated as struct fields.
  • { get; set; } is required for all properties.
  • Custom getters/setters are not supported.

9. Enums

Required Format

[global, bind = typed_gml]
enum ActivityActionTypes
{
    Invalid = 0,
    Join = 1,
    JoinRequest = 5,
}

Rules

  1. Must have [global].
  2. bind = typed_gml produces a strongly-typed GML enum.
  3. Underlying type can be specified directly for int32, uint32, int64:
    enum ErrorCode : int32
    {
        None = 0,
        NetworkError = 1
    }
    
    For other types (e.g. uint8), use type_hint:
    [global, type_hint = `uint8`]
    enum SmallEnum { A = 0, B = 1 }
    
  4. Enum member values must be numeric integers.

10. Quick Rules Summary

Direct types (no type_hint needed)

double  float  int32  uint32  int64  bool  string  object  array  gmval  func  unit

Requires type_hint

  • uint8, int8, uint16, int16, uint64
  • buffer
  • Custom enums and classes
  • Arrays ([]), fixed arrays ([X]), optionals (?)

Syntax summary

Feature Syntax
Dynamic array Type[]
Fixed array Type[4]
Optional Type?
Hint required type_hint = `Type`

Complete example

[global, bind = typed_gml, type_hint = `DiscordCall`]
function get_call([type_hint = `uint64`] channel_id) : gmval;
  • channel_id is uint64
  • Return type is DiscordCall
  • GML receives a typed struct

11. Special Types: Functions & Buffers

Restrictions

func and buffer are only allowed as function parameters. They cannot be:

  • Struct/class property types
  • Function return types

funcGMFunction

function do_something(callback: func) : unit;

In native C++, this becomes GMFunction.

std::uint64_t getId() const;

template<class... Args>
void call(Args&&... args) const;

Functions passed from GML are represented as lightweight handles. They are cleaned up automatically when they go out of scope - no manual release needed.

Calling back into GameMaker:

callback.call(123, "hello", myStruct);

Accepted argument types: numeric scalars, custom structs, custom enums, std::string, std::string_view, const char*.

Thread safety: call() is thread-safe. Data is queued internally and executed in GameMaker's next frame. Safe to call from worker threads.


bufferGMBuffer

function process_data([type_hint = `buffer`] data) : unit;

In native C++:

struct GMBuffer {
    void* data() const noexcept;
    std::uint64_t length() const noexcept;

    GMBufferReader getReader();
    GMBufferWriter getWriter();
};

Reading:

auto reader = buffer.getReader();
reader.read<T>();
reader.readBytes(ptr, size);

Writing:

auto writer = buffer.getWriter();
writer.write<T>(value);
writer.writeBytes(ptr, size);

Important

Buffers are raw memory. You cannot write custom structs, enums, strings, or other complex types directly. Serialize them to raw bytes first.

On Android (JNI): buffers are represented as java.nio.ByteBuffer.


Summary

Type As parameter As struct field As return value
func ✅ Yes ❌ No ❌ No
buffer ✅ Yes ❌ No ❌ No

Use func for async callbacks, buffer for binary payloads, structs for typed data, and enums for strongly typed states.

This restriction ensures a stable ABI and consistent cross-platform behavior (C++, GMRT, legacy runner, all supported platforms).

Clone this wiki locally