GMS 2 Basic extension creation

Discussion in 'Tutorials' started by Ghost in the IDE, Feb 13, 2018.

  1. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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'.

    [​IMG]

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

    [​IMG]

    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'

    [​IMG]

    Then add the dll we previously built into the extension.

    [​IMG]

    Lastly add the function names as below.

    [​IMG]

    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: Feb 14, 2018
    xot, kraifpatrik, NeZvers and 11 others like this.
  2. jackquake

    jackquake Member

    Joined:
    Aug 17, 2016
    Posts:
    75
    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.
     
    Ghost in the IDE likes this.
  3. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
  4. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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.
     
    Ghost in the IDE likes this.
  5. xygthop3

    xygthop3 Member

    Joined:
    Jun 20, 2016
    Posts:
    110
    Whens the next tutorial coming?
     
  6. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    Soon-ish. I have the project files and all that kind of thing written out. Just need to put it in to words. There is a stack to cover just to get a triangle displayed on the screen.

    [​IMG]
     
    Last edited: Feb 19, 2018
  7. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    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!
     
    Ghost in the IDE likes this.
  8. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    @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.
     
    Agreeable and rIKmAN like this.
  9. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    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. :)
     
    Samuel Venable likes this.
  10. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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.
     
    Agreeable, MadZenno, hippyman and 2 others like this.
  11. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    @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. :)
     
  12. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    I must be missing something here.
     
    Agreeable likes this.
  13. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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: Feb 27, 2018
  14. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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
     
    Agreeable likes this.
  15. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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.
     
  16. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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.
     
    Agreeable and hippyman like this.
  17. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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: Feb 27, 2018
  18. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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.
     
    Agreeable and Samuel Venable like this.
  19. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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.
     
  20. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    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: Feb 27, 2018
  21. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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.
     
    Agreeable likes this.
  22. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    Sounds like you're just the guy for a tutorial on the process then haha! ;)
     
    Ghost in the IDE likes this.
  23. hippyman

    hippyman Member

    Joined:
    Jun 20, 2016
    Posts:
    566
    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.
     
    Ghost in the IDE likes this.
  24. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    Exactly that. It is a pretty simple process.

    Knowing the target API is the hard part.
     
    Agreeable likes this.
  25. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    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?
     
  26. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    Yeah I missed the 'has to be a char*' bit.

    * is just a pointer.
     
    Agreeable and Samuel Venable like this.
  27. Ghost in the IDE

    Ghost in the IDE Member

    Joined:
    Jan 8, 2018
    Posts:
    576
    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. :)
     
    Agreeable and rIKmAN like this.
  28. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    Ahh that's a shame, thanks for the DLL tutorial though.
     
    Ghost in the IDE likes this.
  29. GilM

    GilM Member

    Joined:
    Jul 2, 2016
    Posts:
    17
    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.
     
  30. hippyman

    hippyman Member

    Joined:
    Jun 20, 2016
    Posts:
    566
    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.
     
  31. GilM

    GilM Member

    Joined:
    Jul 2, 2016
    Posts:
    17


    Thank you sooo much! This is all I needed to know :D
     
    hippyman likes this.
  32. DarklinkFighter

    DarklinkFighter Member

    Joined:
    Mar 25, 2018
    Posts:
    26
    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
     
  33. rIKmAN

    rIKmAN Member

    Joined:
    Sep 6, 2016
    Posts:
    4,056
    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
     
  34. DarklinkFighter

    DarklinkFighter Member

    Joined:
    Mar 25, 2018
    Posts:
    26

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice