Aristarhys
Aristarhys

Reputation: 2122

JNI - Free ByteBuffer from C++

Summary

  1. Create ByteBuffer in Java called buffer with ByteBuffer.allocateDirect(someBufferSize)
  2. Fill buffer with data
  3. Pass buffer to C++ as jobject - jbuffer
  4. Get buffer direct pointer with env->GetDirectBufferAddress(jbuffer)
  5. Work with buffer data on C++ side. How prevent GC to cleanup our buffer or it will never happen?
  6. Work done - we don't need jbuffer now.
  7. Release jbuffer? free(jbuffer) - will raise an invalid address error

Long part

I use next code to load PNG files with Java AssetManager to use them for Open GL ES 2.0 textures creation.

Java side PNG class

import java.nio.ByteBuffer;
import android.graphics.Bitmap;

public class PNG 
{
 private static final int BYTES_PER_PIXEL_PNG = 4;
 private static final String LOG_TAG = "[PNG]";
 public int width;
 public int height;
 public ByteBuffer pixels;


 public PNG(Bitmap bitmap)
 {
  this.width = bitmap.getWidth();
  this.height = bitmap.getHeight();
  this.pixels = ByteBuffer.allocateDirect(this.width * this.height * BYTES_PER_PIXEL_PNG);
  bitmap.copyPixelsToBuffer(this.pixels);
 }
}

public static PNG loadPNG(String path)
{
    InputStream is = null;
    try
    {
        is = ASSETS_MANAGER.open(path);//get png file stream with AssetsManager instance
    }
    catch (IOException e)
    {
        Log.e(LOG_TAG, "Can't load png - " + path, e);
    }

    return new PNG(BitmapFactory.decodeStream(is));
}

C++ side PNG

typedef struct png
{
int width;
int height;
char* pixels;
} png;

png* load_png(const char* path)
{
 png* res = (res*) malloc(sizeof(png);
 ...
 jobject _png = env->CallStaticObjectMethod(get_java_lib_class(), get_method_id(JAVA_LIB_LOAD_PNG, JAVA_LIB_LOAD_PNG_SIGN), _path);//Calling loadPng() from Java, get PNG jobject
 jobject _pixels =  env->GetObjectField(_png, PNG_FIELDS->PNG_PIXELS_ID);//Getting pixels field from Java PNG jobject
 res->pixels = (char*) env->GetDirectBufferAddress(_pixels);//Get direct pointer to our pixel data
 //Cleanup
 ...
 env->DeleteLocalRef(_png);
 env->DeleteLocalRef(_pixels);
 return res;
}

Then using png to create texture

void test_create_tex(const char* path)
{
 ...
 png* source = load_png(path);
 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source->width, source->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, source->pixels);
//We don't need source pixel data any more
//free(source->pixels);//INVALID HEAP ADDRESS (deadbaad)
free(source);
}

So how release byte buffer after using in C++ side with it's direct pointer? It is allocate directly (like malloc - on native side) and must be freed or i will get OutOfMemory error.

Upvotes: 4

Views: 3803

Answers (2)

Pavel Zdenek
Pavel Zdenek

Reputation: 7278

You don't need to release the buffer. You have allocated it on Java side, which means it's JVM object and GC will take care about it. As contrary to allocating on C side, thus being a native object which GC doesn't know about. You even do not need to do DeleteLocalRef as all local references will be deleted for you by the JNI machinery upon returning from the native method. You would need to explicitly delete only if there would be hundreds of JNI calls back to JVM in scope of one native call, so you would run out of handles even before returning back to JVM.

I must admit that i don't know exactly how GC knows that it should not touch your ByteBuffer, but i would guess that by calling GetObjectField you're incrementing refcount on the ByteBuffer and decrementing with DeleteLocalRef. So between those two JNI calls, ByteBuffer is safe to stay.

Upvotes: 4

Dagang Wei
Dagang Wei

Reputation: 26478

In my opinion, you don't need to worry about the releasing of the ByteBuffer pixels, because it's managed by JVM. What you should really care about is keeping it from garbage collected when it's used by C++.

Upvotes: 2

Related Questions