Mac OSX [macOS Extension Creation] Combining C++ with CoCoa and Objective-C

Discussion in 'Tutorials' started by Samuel Venable, Aug 27, 2018.

  1. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    978
    GM Version: GMStudio 1.4 and GMStudio 2.x
    Target Platform: Mac OS X / macOS
    GMStudio 1.4 Download: [macOSExtension.gmz]
    GMStudio 2.x Download: [macOSExtension.yyz]

    What will this tutorial not be covering?

    To be clear, I will NOT be teaching you how to program in C++ or Objective-C, nor will I give you instructions on how to use the GNU G++ compiler. This tutorial only demonstrates how to communicate between C++ and Objective-C. You must already be pretty familiar with how to create C++ extensions for this tutorial to make any sense to you. You should be able to follow this quite well even if you are have never learned Objective-C, due to how little Objective-C code is actually used in this tutorial. If you have programmed C++ extensions before on any desktop platform, and if you know how to install and use the GNU G++ command line compiler, you should be all set! Google how to install and use the GNU G++ compiler on Mac if you must, it actually isn't too difficult!

    What this tutorial does cover?

    One very important reason a user might need to bridge the C++ and Objective-C languages is so he or she might access and manipulate the properties of the GameMaker game's window, via passing the window_handle() as an argument to a C++ function.

    This process is much more simple on Windows and Linux, because everything can be done with one programming language, most preferably C++.

    But in Mac, all window manipulation is done in the CoCoa API using Objective-C, and none of it can actually be done in pure C++. However, as I mentioned already, you can bridge between them.

    So, in this tutorial I will be showing you how to pass GameMaker's window_handle() to a C++ function, which simply will act as a wrapper for a CoCoa function. The CoCoa function will retrieve the game's title bar caption text, based on the game's associated window_handle(). Then, that value is returned and passed over to the C++ side, which is where we will be calling the function from inside GameMaker Studio.

    Step 1) The Objective-C code

    For starters, here's the Objective-C code, be sure to paste it in an empty file you will name "cocoa.m":
    Code:
    #import <Cocoa/Cocoa.h>
    
    const char *cocoa_window_get_caption(void *window_handle)
    {
        NSWindow *window;
        window = (NSWindow *)window_handle;
     
        return [[window title] UTF8String];
    }
    GameMaker's window_handle() on macOS is an NSWindow * pointer. This type can only be accessed in Objective-C, so in order to use it in C++, you need to C-style cast it to (or from) a type that both C++ and Objective-C will recognize as a valid pointer type. In this case, we are casting it from a void * pointer, this way we can ensure that no data is lost, because casting to (or from) a void * pointer does not modify the value of what is being casted at all.

    Step 2) The C++ code

    Now for the C++ code, which you will need to paste into an empty file named "main.cpp":
    Code:
    #include <string>
    using std::string;
    
    extern "C"
    {
        char *window_get_caption(void *window)
        {
            string window_caption;
            const char *cocoa_window_get_caption(void *window_handle);
            window_caption = cocoa_window_get_caption(window);
     
            return (char *)window_caption.c_str();
        }
    }
    Look at the code above, and then notice the line that says this:
    Code:
    const char *cocoa_window_get_caption(void *window_handle);
    This is how you declare a function from your Objective-C code stored in the "cocoa.m" file you created earlier. Notice there is no body to the function, please do not declare the function with a body; instead, give the declaration a semicolon like you normally do at the end of a declaration line. Don't forget to list the function's name, return type, all of its arguments, and the correct argument types, just as shown in the two snippets above. If you only use the function you are declaring once, then you should be fine with how the file is. If you want to use the cocoa_window_get_caption() function within multiple functions, take it outside of the function body it was in, like so:
    Code:
    #include <string>
    using std::string;
    
    extern "C"
    {
        const char *cocoa_window_get_caption(void *window_handle);
    
        char *window_get_caption(void *window)
        {
            string window_caption;
            window_caption = cocoa_window_get_caption(window);
     
            return (char *)window_caption.c_str();
        }
    }
    Just make sure the cocoa_window_get_caption() declaration is always within the body of extern "C", along with all the functions you want to export, otherwise the function can not be recognized or called from your Objective-C code.

    Step 3) Building the DYLIB for GMStudio

    Now, finally, build the DYLIB. You need your "main.cpp" file and "cocoa.m" file in the same directory.

    Run these terminal commands, (make sure you install xCode, homebrew, and g++ first):
    Code:
    cd /Path/To/Your/macOSExtension/
    g++ -c -std=c++11 main.cpp -m64
    g++ -c -ObjC cocoa.m -m64
    g++ main.o cocoa.o -o macOSExtension.dylib -shared -framework Cocoa -m64
    Import the DYLIB file as an Included File resource into GMStudio. Then, you may use the following GML to execute the DYLIB's function, (paste it into an empty Script resource, and name the Script resource "cocoa_window_get_caption"):
    Code:
    var CoCoa_result;
    CoCoa_result = external_call(external_define("macOSExtension.dylib", "window_get_caption", dll_cdecl, ty_string, 1, ty_string), window_handle());
    external_free("macOSExtension.dylib");
    return CoCoa_result;
    The above Script resource has no arguments. Always pass the window_handle() as a ty_string, because ty_string is a pointer, and can represent either a char * pointer or a window handle pointer.

    You can now see the value the function returns with the following GML, (which can be put in an empty Room resource's Creation Code if you want):
    Code:
    show_message_async(cocoa_window_get_caption());
    And there you have it. PM me if you run into any problems with this tutorial.
    Samuel
     
    Last edited: Sep 1, 2018
    klys likes this.
  2. klys

    klys Member

    Joined:
    Jun 21, 2016
    Posts:
    108
    Cool, im just testing Mac OS Import, and make extensions for this platform with this guide will be a easy piece of cake.

    Thank you for your tutorials. Are great.

    By the way... What version of Xcode are you using?

    You should mention which version of Xcode are you using, and what version of Mac OS X, but i think with the version of Xcode is enough.
     
    Samuel Venable likes this.
  3. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    978
    In my opinion it is always best to use the latest non-beta version of macOS and Xcode, which is what I used at the time it was compiled.
    • macOS High Sierra 10.13.6
    • Xcode 9.4.1
    However these versions are not strictly required.
     
    klys likes this.
  4. klys

    klys Member

    Joined:
    Jun 21, 2016
    Posts:
    108
    Well since Apple dont give retro compatibility support, it is very important in which version you made this tutorial, it probably will not work on other versions...

    Thank you again for give a reply so faster!
     
    Samuel Venable likes this.
  5. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    978
    You're welcome! :) Feel free to share with me the extensions you'll make using this tutorial! I'd love to see what you do with it.
     
  6. klys

    klys Member

    Joined:
    Jun 21, 2016
    Posts:
    108
    Im thinking in a cross platform C++ extension for Android, iOS, MacOS, Windows and Ubuntu for client networking...

    nothing new, just something i wanna try...
     
  7. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    978
    Would anyone like me to do a tutorial on accessing the window_handle() from an extension on Windows, Linux, and HTML5? Special thanks to @YellowAfterlife for writing the HTML5 pointer-lock extension without making it obfuscated, this is my biggest resource for making the HTML5 version...
     

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