GameMaker Basic extension creation

L

Lonewolff

Guest
GM Version: GM2
Target Platform: Windows

There has always been a bit of mystery surrounding DLL's, extension creation, and even more so - how to inject DirectX code to the renderer (or even replace GM's renderer if you feel that way inclined).

Why would we want to create an extension in the first place? Could be any of the following;
  • Expand upon a particular GMS limitation.
  • Get a better understanding on how things work.
  • Increase performance in a particular task.
  • Add some sort of multi-threading.
  • Open up endless possibilities.
So I thought I'd start a 'mega topic' to go through some of the mystery and mayhem.

Once I add subsequent sections (pending any interest out there), I'll break up each part into 'spoiler' sections so as not to overwhelm and to give quicker navigation.

We'll start out by setting up the build environment and then having a foundation to work from, progress from there.

To quickly extinguish the 'cross platform entusiast' argument. The fundamentals are the same for all platforms. If you can write platform independent code, you can create external libraries for any operating system.

Granted the subject of DirectX will most certainly platform lock, if you choose to endeavour down the path.

Here goes :)


Phase 1 - Building the foundations

If you don't have it installed allready, grab a copy of Visual Studio (https://www.visualstudio.com/). For those who do have it installed but are wondering if it is a recent enough version, VS2012 or newer should be fine.

Create a new project of either 'Win32 Console Application' or 'Win32 Project' (the choice doesn't matter) and name it 'dx_injection'.



Click on Application settings and set up your project as follows.



Right click on 'source files' and add a 'C++ File (.cpp)'. Default file name will be 'Source.cpp', change the name if you really want to.

In the code editor add the following source code.

Code:
#define fn_export extern "C" __declspec (dllexport)

fn_export double test_linkage()
{
    return 1;
}
This is the start of our C++ extension. The 'fn_export' declaration just tells the DLL what functions will have access from the outside world (i.e. access from GMS).

Press F5 to build the DLL, making note of the path of the file in the build window. If there was a build failure, you'll need to go back and confirm the project settings are correct (as above).

The function 'test_linkage()' is just a quick test function that we will call from GMS to make sure that everything is interfacing correctly. We will soon call this from an object to make sure that we are actually communicating with the DLL.

Now fire up GMS and create a new GML based project.

Start out by creating a new extension and call it 'dx_injection'



Then add the dll we previously built into the extension.



Lastly add the function names as below.



I have chosen to prefix any functions with 'dx_' to save any possible naming clashes inside of GMS.

Now we can create a normal object (let's say 'obj_tutorial') and add it to the room.

Add a Draw_GUI event and insert the following code.

Code:
draw_text(20, 20, dx_test_linkage());
You'll notice that 'dx_test_linkage' will be orange. As far as GMS is concerned this is now a native function. No scripts or other shennanigans required.

Build and run the project.

All going well, you will the number '1' displayed on the screen. If it displays '0' then something was missed along the way. This is GM telling you that it couldn't call the function from the DLL.

Congrat's - you made your first DLL extension.

(That's all for now :D)
 
Last edited by a moderator:

jackquake

Member
Thank you for taking the time to unravel this mystery. This is very kewl stuff.

I've read some other posts where it is debated of whether to include functions as a .dll or to write a script in GML to do the same. With the introduction of GMS2 / YYC compiler, does one test performance both ways to decide which to use?

Also, my first thought is that one may be inclined to build a library of common functions in a .dll and then link to have all of them available instantly or is it better to simply do the same in a GML script package? It probably depends on the functions, but I'm curious of opinions on this topic.
 
S

Sam (Deleted User)

Guest
You should write a tutorial on how to write ActiveX controls that you can use with my extension with every language possible! (Kidding, but I am too lazy to do that myself atm). This is very useful to beginners and should be stickied if no other topic that covers these specifics exists.
 

rIKmAN

Member
I'd like a tutorial on how to wrap a simple dll for use in GMS2, just some basic functions from a Windows dll or something that I could then extrapolate out from myself to wrapping more complicated stuff.
Maybe add it to your list of tutorial ideas?

Look forward to seeing the next parts of whatever you have planned!
 
L

Lonewolff

Guest
@rIKmAN - The tutorial so far pretty much covers this. It is just a matter of writing the required function in the DLL and interfacing it with GMS2.

For example;

Code:
fn_export double multiply(double a, double b)
{
    double result = a * b;
    return result;

    // Yes you could just return a * b, but expanding it out a little for the fun of it.
}
Then, where you create the functions in GMS2, you would add two 'arguments' of 'double' which becomes the link to the two 'doubles' in the C++ function.
 

rIKmAN

Member
@rIKmAN - The tutorial so far pretty much covers this. It is just a matter of writing the required function in the DLL and interfacing it with GMS2.

For example;

Code:
fn_export double multiply(double a, double b)
{
    double result = a * b;
    return result;

    // Yes you could just return a * b, but expanding it out a little for the fun of it.
}
Then, where you create the functions in GMS2, you would add two 'arguments' of 'double' which becomes the link to the two 'doubles' in the C++ function.
I don't mean writing my own DLL to add numbers, print text or whatever - I mean if there is an existing DLL or SDK that I wanted to wrap for use in GMS2 using glue code or whatever.
Examples would be things like Bass.dll (audio library) or a wrapper for Box2D or Bullet Physics etc that have been wrapped for other languages using existing DLLs.

Obviously I'm only using them as examples, but a tutorial showing how to do this for some simple windows DLL functions or something would be super useful as a starting point.

It's something I want to get into, but don't really know where to start and it's hard to research when you don't know where or what to look for.
Maybe @Samuel Venable would know a bit more as he seems to wrap a fair bit of stuff?

It was just a suggestion for a future tutorial anyway, it's a bit of a black hole in terms of GMS specific information. :)
 
L

Lonewolff

Guest
Obviously I'm only using them as examples, but a tutorial showing how to do this for some simple windows DLL functions or something would be super useful as a starting point.
Yeah, it is a matter of knowing the API you want to wrap. Which means you need to know the API itself.

Take DirectX 11 as an example. If I wanted the GMS renderer to become multi-threaded, I'd have to know how to do this in C++, the functions required etc.

Luckily I have done this before and have made the GMS2 renderer multi-threaded.

In this case we are using d3d11.h as the API.

Here is the example in its entirety.

Code:
#define fn_export extern "C" __declspec (dllexport)

#include <d3d11.h>

fnExport double MakeMultithreaded(ID3D11Device* d3dDevice)
{
    const CComQIPtr<ID3D10Multithread> pMultithread = d3dDevice;
    pMultithread->SetMultithreadProtected(TRUE);
    return 0;
}
^^ This enables multi-threaded right there.

If you mean by interfacing a DLL that is written already, without the 'in-between' code, then that gets ugly. You'd have to find documentation about that specific DLL (not the API). Then you have to pray that the DLL falls withing the restrictions that GMS imposes (arguments must be double or string and return value must be double or string).

I can't imagine anyone would pick up a random DLL somewhere and just hook it straight in to GM. You have to have an intimate knowledge of the DLL itself to have a chance at pulling that one off.
 
S

Sam (Deleted User)

Guest
@rIKmAN well, this would require building your game as an installer to register ActiveX controls, and the extension being Windows-only, but the easiest way that I know of that you could do this, is to follow my tutorial I posted several days ago. Instead of using VB5/VB6 you may also use VB.NET since that is free and still an active product available from Microsoft. VB.NET really simplifies the process of wrapping DLL's, because it does all necessary type conversions for you. With the said limitations, would a tutorial on this still interest you? I can make it the next one in my video series if you want. The tutorial I linked to only covers half of the process of what you are looking to do. :)
 
S

Sam (Deleted User)

Guest
You aren't missing anything if you read the whole post. :p I meant for the specific route I would choose to do it, personally. I haven't checked, but I think VB.NET is just like VB5 and VB6 in that it can't make non-ActiveX DLL's, but if it it can, I guess that is a different story.

Yes I'm aware we could wrap it in a non-VB based language, and then you wouldn't need any ActiveX stuff written to registry. But type conversion outside of the VB world is a nightmare, and by no means something a beginner should expect to pick up on 100% after one 10-30min video tutorial.
 
Last edited by a moderator:
L

Lonewolff

Guest
Ah ok, I personally don't go down the VB path.

Doing it in C++ doesn't require any installers, registry keys, and if you plan carefully no other requisite libraries.

Just one DLL to rule them all :D
 
S

Sam (Deleted User)

Guest
Yeah but there is no one-size-fits-all tutoral on type conversion, which is really the biggest factor in wrapping functions for use in GM, (when it comes to C++).

And to be technical GM games are not necessarily going to work on Windows unless you build your game as an installer, which Russell pointed out the other day.
 
L

Lonewolff

Guest
There is no type conversion ever needed.

It is either a 'double' or a 'pointer' (string in YYG terminology).

Look at my earlier example

Code:
fnExport double MakeMultithreaded(ID3D11Device* d3dDevice)
The function expects a pointer, so you just pass the 'string' to it. String in GMS is a pointer in any other language.
 
S

Sam (Deleted User)

Guest
Wait, so you can just use string and it will recoginize the ID3D11Device* type? I thought the pointer could only represent a char * or an HWND, and that only works in case of arguments. A string return type has to be and can only be a char *. Also when you need to return a real number you have to convert it to a double. If you need a function that uses LPCWSTR you need to convert that to a char *. So when wrapping functions last I checked you do need to do type conversions. The example you gave is on your custom multi-threaded function, that is not the same thing as wrapping existing API's/DLL's from non-GML languages.
 
Last edited by a moderator:
L

Lonewolff

Guest
Yep, doesn't matter what the input is, if it refers to a memory location then it is a pointer, so you can use string as the argument type.

With the return type, have a look at what you just wrote.

The * means it is a pointer. You are not returning a literal string.

You can use the GMS string return type to send data back from your DLL to buffers. You need a pointer to do that ;)

So no, you are not limited to 'char *' as your return type. Can be any type of pointer, it's just a memory address after all.
 
S

Sam (Deleted User)

Guest
Interesting. I tried returning an HWND in the past and for whatever reason it didn't work. I guess I must've did something wrong.
 

rIKmAN

Member
Thanks for the offer @Samuel Venable but I don't really want to start using VB - it gives me nightmares from back in the day.
I realise it's going to be harder but if I'm gonna spend time doing something I'd prefer to go the C++ / C# route as I have a bit of knowledge there already and that will give me way more options going forward with regards to wrapping and transferable knowledge in future - I appreciate the offer though!

@Ghost in the IDE
Thanks for that - and yeah I realise it's not going to be as easy as just grabbing DLLs left right and centre, but there are loads of DLLs wrapped for most languages, and I know there are DLL inspection tools which can give you the function names and hooks etc - I've toyed with them before and I know they don't work for every DLL but can be a good tool.

There is a DLL section on GMToolbox which has a mix of written DLLs and wrappings of existing ones for use with GMS (create PDFs, bass.dll etc) and ultimately it'd be the wrapping that would interest me as I feel like GMS2 is going towards the "make it an extension" route with regards to features (Spine for example), and with the plugin API that we might get sometime in the future.

With that in mind I don't want to be relying on community members to keep things up to date that I may be using and so I want to start learning as much as I can about the process so I can hopefully also contribute something myself.

I'm not looking to try and extend the rendering pipeline or anything fancy like you - just being able to wrap existing DLLs and SDKs and wrap them for use in GMS2 will be more than enough for me.
DLLs like those I mentioned previously as examples, and wrapping SDKs like others have on the marketplace.

That's a good tip about the string thing - I was actually thinking that it must be a real liability only being able to pass reals and strings - but that clears that up.

Like I said add it to your tutorial list, and if you get round to it great - if not, no worries! :)
 
Last edited:
L

Lonewolff

Guest
Yeah, I have made wrappers for all sorts of things in the past and the process is identical for creating each. It is just a matter of knowing the API.

Some of the things I have wrapped up (or at least the functions I wanted to use);
  • Ogre 3D
  • DirectX9
  • DirectX11
  • Microsoft Speech
  • MySQL
  • XInput
  • XAudio2
  • Direct Show
  • Microsoft Media Foundation
  • General use multi-threading
Probably more out there, but you get the idea.

Looking at possibly doing a 3D physics library at some stage.
 

rIKmAN

Member
Yeah, I have made wrappers for all sorts of things in the past and the process is identical for creating each. It is just a matter of knowing the API.

Some of the things I have wrapped up (or at least the functions I wanted to use);
  • Ogre 3D
  • DirectX9
  • DirectX11
  • Microsoft Speech
  • MySQL
  • XInput
  • XAudio2
  • Direct Show
  • Microsoft Media Foundation
  • General use multi-threading
Probably more out there, but you get the idea.

Looking at possibly doing a 3D physics library at some stage.
Sounds like you're just the guy for a tutorial on the process then haha! ;)
 

hippyman

Member
Sounds like you're just the guy for a tutorial on the process then haha! ;)
Honestly, he pretty much already did the tutorial in the original post. Everything else is related to whatever you're trying to wrap. The wrapping part can be simple and less about GM and more about just learning those frameworks. You just have to know how the API of your choice works and keep your wrapper functions within the limitations that GM has put in place, i.e. char*/double variables only. You can use other data-types inside your DLL code, but when you are going to talk between the DLL and GM, it HAS to be a double (real) or a char* (string).

That's pretty much it.
 
L

Lonewolff

Guest
Exactly that. It is a pretty simple process.

Knowing the target API is the hard part.
 
S

Sam (Deleted User)

Guest
Honestly, he pretty much already did the tutorial in the original post. Everything else is related to whatever you're trying to wrap. The wrapping part can be simple and less about GM and more about just learning those frameworks. You just have to know how the API of your choice works and keep your wrapper functions within the limitations that GM has put in place, i.e. char*/double variables only. You can use other data-types inside your DLL code, but when you are going to talk between the DLL and GM, it HAS to be a double (real) or a char* (string).

That's pretty much it.
That pretty much contradicts everything I thought we just talked about, but ok. I know this isn't true because I have passed an HWND to GM as a string, not a char *, and @Ghost in the IDE just confirmed you can use any pointer type for a string when communicating with GM, so I'm a little confused why he just agreed with you?
 
L

Lonewolff

Guest
At this stage I have lost all interest in low level coding. If anyone else is interested in taking this series further, you have my blessings. :)
 
G

GilM

Guest
Beautifully made tutorial. Love it! Short and very simple to understand.

It might be a dumb question, but I'll throw it your way either way. If I created a script like this:

Code:
///@description _draw_text_formatted(halign, valign, colour, X, Y, String);
///@param argument0 //font_halign
///@param argument1 //font_valign
///@param argument2 //draw_set_colour
///@param argument3 // X
///@param argument4 // Y
///@param argument5 // String

draw_set_halign(argument0);
draw_set_valign(argument1);
draq_set_colour(argument2);
draw_text(argument3, argument4, argument5);
Would I be able to just do this to have it recognized by GMS as a built-in function by placing it like this?


Code:
#define fn_export extern "C" __declspec (dllexport)

fn_export double _draw_text_formatted()
{
  
///@description _draw_text_formatted(halign, valign, colour, X, Y, String);
///@param argument0 //font_halign
///@param argument1 //font_valign
///@param argument2 //draw_set_colour
///@param argument3 // X
///@param argument4 // Y
///@param argument5 // String

draw_set_halign(argument0);
draw_set_valign(argument1);
draq_set_colour(argument2);
draw_text(argument3, argument4, argument5);

}
or do I need to know something else in order to make extensions like this that I can just inject?
I know that I might as well just create a script and import it, but I am looking forward to making a "built-in" library of custom functions that I can use with any projects I make. Thanks a lot for the tutorial :D

Edit: Sorry about the double posting, I thought I had replied with the right one and didn't realize I did it twice.
 

hippyman

Member
Would I be able to just do this to have it be recognized by GMS as a built-in function?
You can make GML only extensions. That C++ code above just won't work. You can't call GML functions from the C++ side.

Straight from the docs: https://docs2.yoyogames.com/source/_build/2_interface/1_editors/extensions.html
To start with you need to create the file that is going to be used. For a GML extension, this would be a text file (saved with the .gml extension) and would be formatted something like this:
Code:
#define c_alice_blue
return make_color_rgb(240,248,255);
#define instance_create_colour
var i = instance_create_layer(argument0, argument1, argument2);
with (i)
    {
    image_blend = argument3;
    }
return i;
As you can see we start each section with a #define call, which in the example above is used to define two functions. You don't need to supply argument fields for the functions as this will be added later, simply use the define then the name of the function and go ahead and add the GML to match. If you were writing a DLL or JS extension, then the process would be similar but in the correct language for the extension file format.
 
G

GilM

Guest
You can make GML only extensions. That C++ code above just won't work. You can't call GML functions from the C++ side.

Straight from the docs: https://docs2.yoyogames.com/source/_build/2_interface/1_editors/extensions.html
To start with you need to create the file that is going to be used. For a GML extension, this would be a text file (saved with the .gml extension) and would be formatted something like this:
Code:
#define c_alice_blue
return make_color_rgb(240,248,255);
#define instance_create_colour
var i = instance_create_layer(argument0, argument1, argument2);
with (i)
    {
    image_blend = argument3;
    }
return i;
As you can see we start each section with a #define call, which in the example above is used to define two functions. You don't need to supply argument fields for the functions as this will be added later, simply use the define then the name of the function and go ahead and add the GML to match. If you were writing a DLL or JS extension, then the process would be similar but in the correct language for the extension file format.


Thank you sooo much! This is all I needed to know :D
 
D

DarklinkFighter

Guest
How does building the extension work exactly to use it in multiple projects?
I only find an import option but no export...
And after compiling there seems to be no .gmez file
 

rIKmAN

Member
How does building the extension work exactly to use it in multiple projects?
I only find an import option but no export...
And after compiling there seems to be no .gmez file
In GMS2 you are required to upload it to the marketplace and mark it as private, you will then be able to download it and have it appear in your library within the IDE.

This tutorial,is for 1.4, but the process for GMS2 is the same.
https://help.yoyogames.com/hc/en-us...-An-Asset-Package-For-Testing-Or-Personal-Use
 
Top