JNI Throwing StackOverflowExcpetion when native libraries aren't loaded from java.exe directory

Viewed 85

I've written a UI in java for Eric Conway's Game of Life and the logic behind the game in C#. Using the JNI, I've got it working "correctly". As long as the DLL that contains the game logic and the (CPP) DLL written from the generated .h file from javac -h that bridges java and C# together are both in the java.exe directory, everything works fine, the program launches, and everything seems to work perfectly.

However, I don't want to have to make the user copy my DLL's into their java directory (seems silly to me), just to run the program. Currently in my static constructor, I have System.load("Bridge"); (My C++ DLL connecting java to C#) which again works fine as long as the Bridge dll and the game logic dll are both in the java directory.

Odd behavior: If I change System.load to System.loadLibrary(filePathToDLL); the program appears to load it correctly (seeing this using the -verbose:jni option), but the program immediately throws StackOverflowException upon calling the first native dll function, (not upon starting execution, first dll call comes later in main). Since this didn't work, I tried this hack:

        try
        {
            String temp = new File(GOLGUI.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
            temp = temp.substring(0, temp.lastIndexOf("\\"));
            System.load(temp + "\\Bridge.dll");
            System.load(temp + "\\GOL.dll");
        }
        catch (URISyntaxException e)
        {
            e.printStackTrace();
        }

(I did this with and without the "GOL" line, (GOL is the game logic dll, but it should be loaded by Bridge, so I didn't think it was necessary to begin with, but I was testing everything I thought of)). Same behavior in this case, throws a StackOverflowException.

Third attempt: I set up a simple batch file.

java -jar -Xss8m -verbose:jni -Djava.library.path=%~dp0 Conway.jar
@echo off
pause

Hypothetically, the -Djava.library.path etc should have it check the same directory as this batch file for the Bridge dll referenced in the code (at this point I reverted to the old System.load("Bridge");). Nothing so far has worked, any ideas? I have No idea why it works fine when the dlls are in java.exe directory, but not in any of the other cases, especially when it appears to load the dlls still.

By the way, when I say that this JNI business has been throwing StackOverflowException, I mean it. At no point during this did I get "UnsatisfiedLinkError"

There's a decent amount of code, not sure what all is necessary, but here's the meat of what I think might be needed:

Java:

static
{
    System.loadLibrary("Bridge");
}

private static native boolean[] GetSingle();

private static native void Handshake(int width, int height);

private static native void SetValue(int pos, boolean value);

private static native void Step();

C++ (Bridge DLL):

//automatically generated .h file from javac -h (not including the bajillion #defines)
/*
 * Class:     ui_GOLGUI
 * Method:    GetSingle
 * Signature: ()[Z
 */
JNIEXPORT jbooleanArray JNICALL Java_ui_GOLGUI_GetSingle
  (JNIEnv *, jclass);

/*
 * Class:     ui_GOLGUI
 * Method:    Handshake
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_ui_GOLGUI_Handshake
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     ui_GOLGUI
 * Method:    SetValue
 * Signature: (IZ)V
 */
JNIEXPORT void JNICALL Java_ui_GOLGUI_SetValue
  (JNIEnv *, jclass, jint, jboolean);

/*
 * Class:     ui_GOLGUI
 * Method:    Step
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_ui_GOLGUI_Step
  (JNIEnv *, jclass);

//cpp file that contains the handles to the C# functions, and the implementations of the above declarations
#include "pch.h"

#include "Bridge.h"
#include "ui_GOLGUI.h"

//using System::Text::Encoding;

array<bool>^ GetSingle()
{
    return GOL::GOL::GetSingle();
}

void Handshake(int width, int height)
{
    GOL::GOL::Handshake(width, height);
}

void SetValue(int pos, bool value)
{
    GOL::GOL::SetValue(pos, value);
}

void Step()
{
    GOL::GOL::Step();
}

JNIEXPORT jbooleanArray JNICALL Java_ui_GOLGUI_GetSingle(JNIEnv* env, jclass c)
{
    array<bool>^ arr = GetSingle();
    jboolean* buff = new jboolean[arr->Length];
    for (int i = 0; i < arr->Length; i++)
    {
        buff[i] = arr[i];
    }
    jbooleanArray jba = env->NewBooleanArray(arr->Length);
    env->SetBooleanArrayRegion(jba, 0, arr->Length, buff);
    //env->DeleteLocalRef(jba);
    //env->ReleaseBooleanArrayElements()
    delete[] buff;
    return jba;
}

JNIEXPORT void JNICALL Java_ui_GOLGUI_Handshake(JNIEnv* env, jclass c, jint width, jint height)
{
    Handshake(width, height);
}

JNIEXPORT void JNICALL Java_ui_GOLGUI_SetValue(JNIEnv* env, jclass c, jint val, jboolean isWhite)
{
    SetValue(val, isWhite);
}

JNIEXPORT void JNICALL Java_ui_GOLGUI_Step(JNIEnv* env, jclass c)
{
    Step();
}

C# (GOL DLL):

//literally all of my C# code, contents of the function probably don't matter too much for issue.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GOL
{
    public static class GOL
    {
        private static bool[,] grid;
        private static bool[,] buffer;
        private static int width;
        private static int height;

        public static void Handshake(int width, int height)
        {
            GOL.width = width;
            GOL.height = height;
            grid = new bool[width, height];
            buffer = new bool[width, height];
        }

        public static void Step()
        {
            //i horizontal, <- negative i
            //j vertical, ^- positive j
            int CountLiveNeighbors(int i, int j)
            {
                int sum = 0;
                if (i > 0) //left check
                {
                    sum += grid[i - 1, j] ? 1 : 0;
                }
                if (i < width - 1) //right check
                {
                    sum += grid[i + 1, j] ? 1 : 0;
                }
                if (j > 0) //down check
                {
                    sum += grid[i, j - 1] ? 1 : 0;
                }
                if (j < height - 1) //up check
                {
                    sum += grid[i, j + 1] ? 1 : 0;
                }
                if (i > 0 && j > 0) //bottom left check
                {
                    sum += grid[i - 1, j - 1] ? 1 : 0;
                }
                if (i < width - 1 && j > 0) //bottom right check
                {
                    sum += grid[i + 1, j - 1] ? 1 : 0;
                }
                if (i > 0 && j < height - 1) //top left check
                {
                    sum += grid[i - 1, j + 1] ? 1 : 0;
                }
                if (i < width - 1 && j < height - 1) //top right check
                {
                    sum += grid[i + 1, j + 1] ? 1 : 0;
                }
                return sum;
            }
            //calculate and put into buffer.
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    int temp = CountLiveNeighbors(i, j);
                    if (grid[i, j])
                    {
                        //this is a white cell.
                        if (temp > 1 && temp < 4)
                        {
                            buffer[i, j] = true;
                        }
                        else
                        {
                            buffer[i, j] = false;
                        }
                    }
                    else if (temp == 3)
                    {
                        buffer[i, j] = true;
                    }
                    else
                    {
                        buffer[i, j] = false;
                    }
                }
            }
            //copy buffer into real grid.
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    grid[i, j] = buffer[i, j];
                }
            }
        }

        public static void SetValue(int pos, bool value)
        {
            int x = pos % width;
            int y = (pos - x) / height;
            grid[x, y] = value;
        }

        public static bool[] GetSingle()
        {
            bool[] result = new bool[width * height];
            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    result[i + (j * height)] = grid[i, j];
                }
            }
            return result;
        }
    }
}
0 Answers
Related