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:
    1,161
    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 hard!

    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;
    
    #define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
    extern "C" const char *cocoa_window_get_caption(void *window_handle);
    
    EXPORTED_FUNCTION char *window_get_caption(void *window)
    {
       static string window_caption;
       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:
    extern "C" 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. As you see with I what I did in "main.cpp", make sure you put "EXPORTED_FUNCTION", (without the quotes), in front of each function definition you want to call from GameMaker.

    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: Mar 4, 2019
    AivanF.com and klys like this.
  2. klys

    klys Member

    Joined:
    Jun 21, 2016
    Posts:
    110
    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:
    1,161
    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:
    110
    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:
    1,161
    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:
    110
    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:
    1,161
    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...
     
  8. AivanF.com

    AivanF.com Member

    Joined:
    Sep 29, 2018
    Posts:
    6
    Hi everyone! Thanks for the tutorial, it is really cool!

    However, I have some troubles with it, could you please help me?

    I did everything just like you described. Then...

    1) I compiled dylib using Xcode and after some strange manipulations (initially GM:S2 said that it can't load external function, IDK why) my GM:S2 project become working. But...

    2) If I compile dylib using terminal commands, I get a "YoYo Runner quit unexpectedly" window with large traceback and other complicated descriptions. "Termination Reason: Namespace CODESIGNING, Code 0x2" And the last called function is "ImageLoaderMachO::crashIfInvalidCodeSignature()"

    3) I thought "0k, I will just use compilation with Xcode.. Let's try to add another function, `echo` which will just take a string and return it." I wrote the function, compiled dylib, and... I got the same exception window!!! Named reason is the same: "Termination Reason: Namespace CODESIGNING, Code 0x2" But the functions are different: "0x0000000118442156 ImageLoaderMachO::validateFirstPages(linkedit_data_command const*, int, unsigned char const*, unsigned long, long long, ImageLoader::LinkContext const&) + 104"

    4) Then I thought "0k, let's just check that at least something is working...". I removed my new function and compiled dylib again. And what do you think?? I got the absolutely same error as before!

    What's going on??! I'm so tired after several hours of googling, compiling, and trying to understand all these mess. Does anybody know what to do?

    By the way, I have some additional notes:

    5) My first compiled dylib still works well.
    6) Dylib from your project leads to error just like in my 2nd step.
    7) I tried to use my dylibs in your projects, initially some of them worked, but now all of them lead to one of mentioned error... It is really very strange. Look like a big problem with GMS or something like that. Although I always press "Clean build cache".
    8) I also used "nm -gU PathToDyLib" and "objdump -t PathToDyLib" commands to check that dylibs are correct, and all of them have the functions, and they do. Unfortunately, I'm not able to read Assembler code to find out something deeper.
    9) It worth to mention that dylibs created with Xcode are about 30KB, and ones created by terminal commands are about 10 KB (probably Xcode links many framework stuff).
     
    Last edited: Mar 3, 2019
    Samuel Venable likes this.
  9. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    Hey!
    • What version of GMS 2 runtime are you using?
    • What version of Xcode are you using?
    • What version of clang (tuns out gcc is just an alias to clang on mac)?
    I'll check it out as soon as I get this information. :)
     
  10. AivanF.com

    AivanF.com Member

    Joined:
    Sep 29, 2018
    Posts:
    6
    Hi! Thanks for reply, it's good to see that I'm not lost with this terrible problem:) I plan to develop an interesting extension for GM:S2.

    Sorry, it was silly to forget to add such technical details. I just use the last versions: IDE is 2.2.1.375, and Runtime is 2.2.1.291. Also, my Mac OS X version is 10.14.3.
     
    Samuel Venable likes this.
  11. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    I'm on High Sierra. I hope that isn't the problem. I haven't upgraded to Mojave so I could still use and develop 32-bit apps for older macs.

    I have a small theory. Try compiling this, its an alternate way of doing the original code for this tutorial, (replaces the *.cpp file):
    Code:
    #include <string>
    using std::string;
    
    #define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
    extern "C" const char *cocoa_window_get_caption(void *window_handle);
    
    EXPORTED_FUNCTION char *window_get_caption(void *window)
    {
       static string window_caption;
       window_caption = cocoa_window_get_caption(window);
    
       return (char *)window_caption.c_str();
    }
    The runtime you mentioned does work for me when I run the project.
    Are you sure this isn't a problem with how you codesigned your app? The errors you describe seem to have more to do with that than the use of my extension example, but I could be wrong. I also recommend turning code signing off temporarily by leaving your signing identity blank to test that.
     
    Last edited: Mar 4, 2019
    AivanF.com likes this.
  12. AivanF.com

    AivanF.com Member

    Joined:
    Sep 29, 2018
    Posts:
    6
    I replaced cpp file with your code, compiled DyLib with Xcode and raw g++, both versions worked! BUT... Then I added simple echo function (using your
    EXPORTED_FUNCTION), compiled, and both versions lead to some new exception. Here is the most important parts of it, in my opinion:
    Code:
    Exception Type:        EXC_BAD_ACCESS (Code Signature Invalid)
    Termination Reason:    Namespace CODESIGNING, Code 0x2
    
    0   dyld                             0x00000001131a176f ImageLoaderMachOCompressed::rebase(ImageLoader::LinkContext const&, unsigned long) + 159
    
    Well, I deleted these DyLibs, removed my additional code, cleaned GM:S2 Cache, compiled again, and now I get the same error with just your code! It looks really weird, like a glitch, the result is completely independent of my work :(

    ---
    Update 1: Compilation with g++ doesn't sign the code. Also, I disabled code signing in Xcode project.

    Update 2: I visited Apple Developers website, got certificates, signed the DyLib's code, etc, used new DyLib in GMS, got the same exception as in my 3d note. Also, I posted it online, maybe it can help: https://pastebin.com/Dwc0U6D6
     
    Last edited: Mar 4, 2019
  13. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    I really don't know what else to say. This sounds extremely odd. What version of Xcode and g++/clang are you using? Maybe you should contact the YoYoGames helpdesk and they might be able to confirm whether this is a bug with the runtime or not. It's weird though because I haven't had any issues with this.

    http://help.yoyogames.com
     
    AivanF.com likes this.
  14. AivanF.com

    AivanF.com Member

    Joined:
    Sep 29, 2018
    Posts:
    6
    G++ is 4.2.1, and Clang says "Apple LLVM version 10.0.0 (clang-1000.11.45.2)"

    Yesterday I wrote about this strange behaviour as about a bug here: https://accounts.yoyogames.com/contact-us#studio2
    I attached my code, several DyLibs, exceptions descriptions, and a link to this thread. And I got "Ticket number 155492" and nothing else that can help to monitor the progress. I hope YoYo staff will view it, solve (if possible) and notify me, because I really wanted to create a nice extension. Also, thank you Samuel for your tutorial attempts to help me!
     
    Samuel Venable likes this.
  15. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    You're welcome! :) I hope they'll be able to help.
     
  16. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    842
    @Samuel Venable You may want to check into your use of std::string instances on the stack that you return c_str() for, for instance, and perhaps add static in front of it at least. When the function returns the string will be deallocated and the pointer returned may not be valid when used by the caller later.
     
    Samuel Venable likes this.
  17. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,161
    Oh yeah I forgot to make it static. I'll update my code examples and see if that makes a difference.

    Edit:

    Tutorial updated. Perhaps if he tries it again it will be fixed?
     
    Last edited: Mar 4, 2019

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