Android / Amazon Fire How to include C/C++ Code in Android Extensions

Mick

Member
GM Version: GMS1.4+ (some steps might differ for GMS2, but the idea is the same)
Target platform: Android
Download: N/A
Links: N/A

Intro

I have recently created a few extensions for Android to play chiptunes in various formats. This required the ability to run native C/C++ code. Native extensions for Android are written in Java, but using JNI (Java Native Interface) you can include C/C++ code in your extension. The caveat to this method is that the native functions can't be used in any Java class, they are tightly connected to the package and class name used when compiling the shared libraries (.so). Here is an attempt to explain how to complete this somewhat advanced task. For this tutorial, I assume that you have your environment setup so you can build java classes (JDK) and Android source (NDK) from the command line.


Tutorial
1. Create a folder for your NDK project, when following this example, name it "HelloNativeAndroid".

2. In that folder create two new folders, name one "bin" and the other "jni".

3. The "jni" folder is where you will put your C / C++ files. In this example we will only use one C file, so create a new file and name it "hello.c". Leave it empty for now.

4. Also in this folder, create another file and name it "Android.mk" (this is similar to makefile on Linux). Paste the following content into the newly created file:
Code:
# Makefile for building hello.c with Android NDK

# Set local path (An Android.mk file must begin by defining the LOCAL_PATH variable)
LOCAL_PATH := $(call my-dir)

# Clears LOCAL_XXX variables
include $(CLEAR_VARS)

# Tell the compiler that include files can be in the parent folder (for the jni header)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
LOCAL_CPP_INCLUDES := $(LOCAL_PATH)/..

# Name of shared library
LOCAL_MODULE := HelloNativeAndroid

# Source file(s)
LOCAL_SRC_FILES := hello.c

# Example: include all C source files
# LOCAL_SRC_FILES := $(notdir $(wildcard $(LOCAL_PATH)/*.c))

# The following command will build the library
include $(BUILD_SHARED_LIBRARY)
You can read more about how to use the Android.mk file here: https://developer.android.com/ndk/guides/android_mk.html

5. Back in the "HelloNativeAndroid" folder, create a new file named "HelloNativeAndroid.java". This will be the java interface to the native C/C++ functions. Paste the following content into the java file:
Code:
// In your own projects, change the package name to something else
package com.gamemaker.Tutorial;

// The name of the class must match the name of the file
public class HelloNativeAndroid
{
    // Declare the native functions (note the native keyword)
    public static native double HelloNativeAndroid_Add(double number1, double number2);
    public static native String HelloNativeAndroid_Hello();

    // Load the shared library (note that you should not include the .so extension)
    static {
        System.loadLibrary("HelloNativeAndroid");
    } 
}
6. Open a command prompt in this folder and build the java file with the command: "javac -d bin HelloNativeAndroid.java". You should now see a "HelloNativeAndroid.class" file in the folder "HelloNativeAndroid/bin/com/gamemaker/Tutorial.

7. Now we need to create a jni header file using the java class we compiled in the previous step. This can be done "automatically" with the following command (from the project root folder): "javah -classpath bin -jni com.gamemaker.Tutorial.HelloNativeAndroid". Note the package name + java class name, these have to match the ones in your java file created in step 5. You should now see a newly created header file named "com_gamemaker_Tutorial_HelloNativeAndroid.h".

8. You can now copy the function declarations from the created header file and implement them in the hello.c file we created in step 3. Like so:
Code:
// Include jni.h, the automatically created jni header file and any other header files required by your code
#include <jni.h>
#include "com_gamemaker_Tutorial_HelloNativeAndroid.h"

const char* _hello = "Do Androids Dream of Electric Sheep?";

// Simple function to add two doubles
double Add(double, double);

// HelloNativeAndroid_Add
JNIEXPORT jdouble JNICALL Java_com_gamemaker_Tutorial_HelloNativeAndroid_HelloNativeAndroid_1Add
  (JNIEnv *env, jclass cl, jdouble number1, jdouble number2)
{
    return Add(number1, number2);
}

// HelloNativeAndroid_Hello
JNIEXPORT jstring JNICALL Java_com_gamemaker_Tutorial_HelloNativeAndroid_HelloNativeAndroid_1Hello
  (JNIEnv *env, jclass cl)
{
    // To return a C string, we need to convert it to a jstring like so:
    jstring jstrBuf = (*env)->NewStringUTF(env, _hello);
 
    // From C++ you would do it like this:
    // jstring jstrBuf = env->NewStringUTF(_hello);
 
    // Return the jstring
    return jstrBuf;
}

// Implementation of Add function
double Add(double number1, double number2)
{
    return number1 + number2;
}
9. Now build the shared library (32-bit) with the command: "ndk-build NDK_HOST_32BIT=1". If successful, you see two newly created folders, "obj" and "libs". The libs folder should have a couple of subfolders for different architechtures with the file "libHelloNativeAndroid.so" in each of them. We will copy some of these subfolders later when creating the GMS Android extension.

10. In your GMS project, right click "Extensions" in the resource tree and click "Create Extension". Give it the name "HelloNativeAndroid" and select "Android" under "Select andy additional features...". Click "Next" and for class name, write "JHelloNativeAndroid". We will use the J prefix here to avoid a conflict with the java interface class we created earlier. For this example we don't need any extra permissions, so click "Create" to create the extension. You can read more about creating extensions for Android here: https://help.yoyogames.com/hc/en-us...ting-A-Native-Extension-For-Android-GMS-v1-3-

11. Right-click the extension in the resource tree and select Add Placeholder/Add Generic. Right-click "HelloNativeAndroid.ext" and click properties. Under "copies to", deselect all exports except "Android" and "Adnroid (YYC)".

12. Add two functions (right-click "HelloNativeAndroid.ext" and select "Add Function"). Double-click each function and set the properties like below. Again, we use the J prefix for the external names to avoid a conflict with the native functions.
Code:
Name: HelloNativeAndroid_Add
External Name: JHelloNativeAndroid_Add
Help: HelloNativeAndroid_Add(number1, number2)
Return type: double
Arguments: Add two arguments, type should be double for both
 
Name: HelloNativeAndroid_Hello
External Name: JHelloNativeAndroid_Hello
Help: HelloNativeAndroid_Hello()
Return type: string
Arguments: None

13. Right-click "HelloNativeAndroid" and click "Open in Explorer". Go to the folder "HelloNativeAndroid/AndroidSource/Java" and create a new file named "JHelloNativeAndroid.java" with the following content:
Code:
package ${YYAndroidPackageName};

import android.util.Log;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.String;

import ${YYAndroidPackageName}.R;
import com.yoyogames.runner.RunnerJNILib;

// Import the native functions declared in HelloNativeAndroid.java
import static com.gamemaker.Tutorial.HelloNativeAndroid.HelloNativeAndroid_Add;
import static com.gamemaker.Tutorial.HelloNativeAndroid.HelloNativeAndroid_Hello;

// Class name need to match the one you used when creating the extension in GMS
public class JHelloNativeAndroid
{
    public double JHelloNativeAndroid_Add(double number1, double number2)
    {
        return HelloNativeAndroid_Add(number1, number2);
    }
 
    public double JHelloNativeAndroid_Hello()
    {
        return HelloNativeAndroid_Hello();
    }
}
14. Copy "HelloNativeAndroid.java" from the NDK project folder into the same folder as "JHelloNativeAndroid.java".

GMS2.x/GMS1.4 gradle: Copy and paste the "libs" folder in the NDK project and rename the copy of the folder to "lib". Create a zip file of the "lib" folder so the file paths are saved in the zip file (lib/..). Change the extension of the zip file to jar and copy it to the libs folder of the GMS extension ("HelloNativeAndroid/AndroidSource/libs"). When building with gradle, the .so files are not added to the .apk if they are just copied to the libs folder like on older versions of GMS1.4.

GMS1.4 pre-gradle: Copy the folders "armeabi", "armeabi-v7a", "mips" and "x86" from the NDK project libs folder to the libs folder of the GMS extension ("HelloNativeAndroid/AndroidSource/libs").

15. To test the extension, create a new object and add the following to the Create event:
Code:
show_message(HelloNativeAndroid_Hello());
For native extensions to work in Android, you cannot just press the play button, you need to create an executable and run that. Set Android as target and create the executable (it should run automatically if you have a device setup). If all goes well you should see a message box on the screen saying "Do Androids Dream of Electric Sheep?".


Passing pointers to C/C++
A reliable method to pass pointers (for buffers etc.) to C/C++ is to convert the pointer to string in GMS and pass it as string to the Android extension, then using the function below, you can convert the string to a long integer in the Java class for the extension. You then pass the pointer as a long integer to the native C/C++ function and cast it to a pointer there.
Code:
// This converts a memory address in string format to a long integer
// It will be cast to a pointer in C++
private long StringAddressToLongPointer(String address)
{
    if(address.charAt(1) == 'x')
        return Long.parseLong(address.substring(2), 16);
    else
        return Long.parseLong(address, 16);
}
 
Last edited:
D

damand23

Guest
Thanks for the great tutorial.

I have one question, in my C++ code I am using libcurl library to make HTTP calls. I have added libcurl as static ('.a') library to my c++ code and then build ('.so) for my application that I will be including in my GM Extension using above tutorial. Do I have to do similar setup for libcurl of I can get away with just the above tutorial.

Thanks.
 
Last edited by a moderator:

Mick

Member
Do I have to do similar setup for libcurl of I can get away with just the above tutorial.
I'm not sure about this. If libcurl is included as a static library in your shared .so library I guess it should work without any extra hassle.
 
D

damand23

Guest
Hello,

Sorry for late question about this topic again. I am having issue when following the tutorial. But I am getting the following, "Can't find argfree method on extension class:null" and value is "undefined". I could not resolve the issue. Is it something I did wrong while building .so or I am missing something on GameMaker Side. Please let me know, if any other information is required.

Thanks.
 

Mick

Member
Hello,

Sorry for late question about this topic again. I am having issue when following the tutorial. But I am getting the following, "Can't find argfree method on extension class:null" and value is "undefined". I could not resolve the issue. Is it something I did wrong while building .so or I am missing something on GameMaker Side. Please let me know, if any other information is required.

Thanks.
Hmm, the chain is so long so it's easy to make a bad connection somewhere. I can't really say why you got the error. If you are willing to send me the .so project and the gms project I can have a look (a test gms project is ok, where you have set up the extension).
 
D

damand23

Guest
Hmm, the chain is so long so it's easy to make a bad connection somewhere. I can't really say why you got the error. If you are willing to send me the .so project and the gms project I can have a look (a test gms project is ok, where you have set up the extension).
Thanks for the help.

This is the Console output when Application is Run on Device.
Code:
08-21 11:04:01.328 12338 12357 I yoyo    : Can't find argfree method on extension class:null
08-21 11:04:01.332 12338 12357 I yoyo    : ShowMessage("undefined")
How can I send you the Projects ? I mean they are sample project I can share them on this thread but they are bigger in size than allowed.

I have provided the shareable link on my Google Drive
Game Maker Project Link:
https://drive.google.com/open?id=0B5Zs09JHkV8pTWU1VG1VeFhyVTA

NDK Project Link:
https://drive.google.com/open?id=0B5Zs09JHkV8pb3NSYnhaM005X3c

Thanks Again.
 

Mick

Member
Any idea guys ?
Hi, I just downloaded your GMS project and it's working fine for me. I chose create application and it ran fine on my Samsung Galaxy S5. I don't know what the problem might be. Which exact version of GMS are you using?
 
D

damand23

Guest
Hi, I just downloaded your GMS project and it's working fine for me. I chose create application and it ran fine on my Samsung Galaxy S5. I don't know what the problem might be. Which exact version of GMS are you using?
I am using Version 1.4.1772.
 

Mick

Member
I'm using an older version of GMS1.4 (pre gradle). Seems that native C/C++ functions can't be used like this post gradle. I will dig into it, hopefully there is a solution.
 

Mick

Member
I found a solution! I'm not sure if there is a bug in the Android gradle compiling process but the .so files in the libs folder will not be added to the final apk. like before. So instead what seems to work is this (I also updated step 14 in the tutorial):

1. Take a copy of the "libs" folder in the NDK project and rename that copy to "lib".
2. Create a zip file of the "lib" folder saving the full path (lib/...).
3. Change the extension of the zip file to .jar and copy the jar file to the "libs" folder of the extension in the GMS project.

That way, the jar file will be extracted when compiling and the .so will be included in the final apk in the correct folders.

Heads up to @damand23 !
 

Yumeito

Member
Could you make a tutorial on how to make extensions with java code? Do I need to use android studio as the work environment? I'm new to this stuff and I recently signed up to use an SDK(http://xmode.io/docs.html) for my game, but I have no idea where to start when making an extension.....
 

Mick

Member
Could you make a tutorial on how to make extensions with java code? Do I need to use android studio as the work environment? I'm new to this stuff and I recently signed up to use an SDK(http://xmode.io/docs.html) for my game, but I have no idea where to start when making an extension.....
You can follow steps 10-13 in this tutorial with small modifications (like removing the native function imports from the code in step 13). Nocturne also wrote a tutorial how to create JAVA extensions: https://help.yoyogames.com/hc/en-us...ting-A-Native-Extension-For-Android-GMS-v1-3-
 
Last edited:
W

Wraithious

Guest
Hi @Mick just have a quick question, when you say 'pass a buffer pointer' to the java extension, is that passing the buffer to the extension or just an address to the buffer? ( I see in your case the work youre doing to the buffer happens in a c lib, I just want to do something right in my java extension) I was wondering because I want to pass an audio file in/as a buffer to my java extension that will handle the rest, as long as it can get the buffer. My plan is to use file_copy to get the file in memory, load that into a buffer, and send it to my java extension
 

Mick

Member
Hi @Mick just have a quick question, when you say 'pass a buffer pointer' to the java extension, is that passing the buffer to the extension or just an address to the buffer? ( I see in your case the work youre doing to the buffer happens in a c lib, I just want to do something right in my java extension) I was wondering because I want to pass an audio file in/as a buffer to my java extension that will handle the rest, as long as it can get the buffer. My plan is to use file_copy to get the file in memory, load that into a buffer, and send it to my java extension
The address of the buffer is passed to the java extension as a string. The string is then converted to a long and sent to the C library. I don't have any experience how to read memory locations in java I'm afraid.
 
W

Wraithious

Guest
Ok thanks, I'm messing around with it now to see what I can come up with.

EDIT: @Mick I ended up catching the buffered file in memory in my java class using the java function memoryFile, it's still not 100% working but if you look at the last post in my topic it's probably something I'm overlooking or not doing correctly either in the gms side or my java side.
 
Last edited by a moderator:
Top