How to Load and Use Structs and Functions from a C DLL in Java?

788 views Asked by At

I have a C dll that is used to open and read a certain file type. I have created a python SDK that can load the dll and access the functions in order to read the files. The ctypes module also has helped me create the structures to handle and grab the data by passing the correct c type parameters into the functions.

Now I'm trying to build another SDK but in Java with the endgame to be able to use this SDK to build an android app. I've been able to load the jna jar file and access functions that only require primitive variable types. This is the C++ header file that has the functions available in the dll (abridged a little):

#ifdef PL2FILEREADER_EXPORTS
#define PL2FILEREADER_API __declspec(dllexport)
#else
#define PL2FILEREADER_API __declspec(dllimport)
#endif


#include "PL2FileStructures.h"

#define PL_BLOCK_TYPE_SPIKE (1)
#define PL_BLOCK_TYPE_ANALOG (2)
#define PL_BLOCK_TYPE_DIGITAL_EVENT (3)
#define PL_BLOCK_TYPE_STARTSTOP_EVENT (4)

extern "C" {
    PL2FILEREADER_API int PL2_OpenFile(const char* filePath, int* fileHandle);

    PL2FILEREADER_API void PL2_CloseFile( int fileHandle );

    PL2FILEREADER_API void PL2_CloseAllFiles();

    PL2FILEREADER_API int PL2_GetLastError(char *buffer, int bufferSize);

    PL2FILEREADER_API int PL2_GetFileInfo(int fileHandle, PL2FileInfo* info);

    PL2FILEREADER_API int PL2_GetAnalogChannelInfo(int fileHandle, int zeroBasedChannelIndex, PL2AnalogChannelInfo* info);


    PL2FILEREADER_API int PL2_GetAnalogChannelInfoByName(int fileHandle, const char* channelName, PL2AnalogChannelInfo* info);
//other functions as well. I just cut it off here

This is a C++ header file containing the structs that will hold information about the files (abridged as well):

#pragma once

// this header is needed for tm structure
#include <wchar.h>


#pragma pack( push, 8 )

struct PL2FileInfo {
    PL2FileInfo() { memset( this, 0, sizeof( *this ) ); }
    char m_CreatorComment[256];
    char m_CreatorSoftwareName[64];
    char m_CreatorSoftwareVersion[16];
    tm m_CreatorDateTime;
    int m_CreatorDateTimeMilliseconds;
    double m_TimestampFrequency;
    unsigned int m_NumberOfChannelHeaders;
    unsigned int m_TotalNumberOfSpikeChannels;
    unsigned int m_NumberOfRecordedSpikeChannels;
    unsigned int m_TotalNumberOfAnalogChannels;
    unsigned int m_NumberOfRecordedAnalogChannels;
    unsigned int m_NumberOfDigitalChannels;
    unsigned int m_MinimumTrodality;
    unsigned int m_MaximumTrodality;
    unsigned int m_NumberOfNonOmniPlexSources;
    int m_Unused;
    char m_ReprocessorComment[256];
    char m_ReprocessorSoftwareName[64];
    char m_ReprocessorSoftwareVersion[16];
    tm m_ReprocessorDateTime;
    int m_ReprocessorDateTimeMilliseconds;
    unsigned long long    m_StartRecordingTime;
    unsigned long long    m_DurationOfRecording;
};

struct PL2AnalogChannelInfo {
    PL2AnalogChannelInfo() { memset( this, 0, sizeof( *this ) ); }
    char m_Name[64];
    unsigned int m_Source;
    unsigned int m_Channel;
    unsigned int m_ChannelEnabled;
    unsigned int m_ChannelRecordingEnabled;
    char m_Units[16];
    double m_SamplesPerSecond;
    double m_CoeffToConvertToUnits;
    unsigned int m_SourceTrodality;
    unsigned short m_OneBasedTrode;
    unsigned short m_OneBasedChannelInTrode;
    unsigned long long m_NumberOfValues;
    unsigned long long m_MaximumNumberOfFragments;
};

//Other stuff here

#define PL2_BLOCK_TYPE_SPIKE (1)
#define PL2_BLOCK_TYPE_ANALOG (2)
#define PL2_BLOCK_TYPE_DIGITAL_EVENT (3)
#define PL2_BLOCK_TYPE_STARTSTOP_EVENT (4)

#define PL2_STOP (0)
#define PL2_START (1)
#define PL2_PAUSE (2)
#define PL2_RESUME (3)

I've done some research and I found this library called JNA (Java Native Access).

JNA Documentation

For the most part I'm able to follow the documentation on how to access the methods but accessing the structures is kind of confusing and not well documented. This is what I have written out for Java so far:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.PointerType;
import com.sun.jna.ptr.IntByReference;

public interface IPL2Reader extends Library {
    public static class PL2_FileInfo extends Structure {
        // This part I just copied and pasted from the header file
        //I know I will have to do some modification but this is just a proof of concept
        char m_CreatorComment[256];
        char m_CreatorSoftwareName[64];
        char m_CreatorSoftwareVersion[16];
        tm m_CreatorDateTime;
        int m_CreatorDateTimeMilliseconds;
        double m_TimestampFrequency;
        unsigned int m_NumberOfChannelHeaders;
        unsigned int m_TotalNumberOfSpikeChannels;
        unsigned int m_NumberOfRecordedSpikeChannels;
        unsigned int m_TotalNumberOfAnalogChannels;
        unsigned int m_NumberOfRecordedAnalogChannels;
        unsigned int m_NumberOfDigitalChannels;
        unsigned int m_MinimumTrodality;
        unsigned int m_MaximumTrodality;
        unsigned int m_NumberOfNonOmniPlexSources;
        int m_Unused;
        char m_ReprocessorComment[256];
        char m_ReprocessorSoftwareName[64];
        char m_ReprocessorSoftwareVersion[16];
        tm m_ReprocessorDateTime;
        int m_ReprocessorDateTimeMilliseconds;
        unsigned long long   m_StartRecordingTime;
        unsigned long long   m_DurationOfRecording;
    }

    IPL2Reader myO = (IPL2Reader) Native.loadLibrary("*file path to dll*", IPL2Reader.class); 

    int PL2_OpenFile(String filepath, IntByReference filehandle);
    void PL2_CloseFile(int filehandle);
    void PL2_CloseAllFiles();
    int PL2_GetLastError(char buffer[], int bufferSize);
    int PL2_GetFileInfo(int fileHandle,  PL2FileInfo info);
}  

Anyway the question I have is how do I map the structures from the C SDK to the Java SDK? Then how do I call and use them in functions especially since they are pointers? I've worked with python mostly and I know Python has the ctypes module which makes it easy to load and call functions and structures in a dll. Is there a ctypes module like Python for Java or an easier way to load and use c++ dll functions and structures in Java? Any help is appreciated thank you!

1

There are 1 answers

0
halochief996 On

So after working out all the weird bugs and unsatisfied link errors I was able to use JNI. What I basically ended up doing is first building a class that loaded the dll and had the native functions like so:

public class JPL2FileReader
{

static   //static initializer code
{
    System.loadLibrary("JavaPL2FileReader");
} 

public native int JPL2_OpenFile(String filepath);
public native void JPL2_CloseFile(int fileHandle);
public native void JPL2_CloseAllFile();
public native String JPL2_GetLastError();
public native int JPL2_GetFileInfo(int fileHandle, PL2FileInfo info);
}

Also to deal with structs, I built java classes (like above PL2FileInfo) that had the same fields as the structs.

Then I ran these commands in the comman prompt:

javac JPL2FileReader.java
javah JPL2FileReader

After that I imported the resulting .h file into a visual studio 2015 project and created a .cpp file that would initialize the functions in the .h file as a wrapper calling the functions in the C++ header file listed above that is linked to the dll I was given and setting fields in the java objects as the results (note the function definitions are in the JPL2FileReader.h):

#include "stdafx.h"

#include "PL2FileStructures.h"
#include "PL2FileReader.h"
#include "JPL2FileReader.h"

#pragma comment(lib, "C:\\Users\\alexc.plexoninc\\Documents\\Visual Studio 2015\\Projects\\JavaPL2FileReader\\JavaPL2FileReader\\lib\\PL2FileReader.lib")

JNIEXPORT jint JNICALL Java_JPL2FileReader_JPL2_1OpenFile
(JNIEnv *env, jobject obj, jstring filepath) {
    const char* nativefilepath = env->GetStringUTFChars(filepath, 0);

    int nativeFileHandle = 0;
    int result = PL2_OpenFile(nativefilepath, &nativeFileHandle);
    if (result == 0) {
        return -1;
    }
    return nativeFileHandle;
}

JNIEXPORT void JNICALL Java_JPL2FileReader_JPL2_1CloseFile
(JNIEnv *env, jobject obj, jint fileHandle) {
    PL2_CloseFile((int)fileHandle);
}

JNIEXPORT void JNICALL Java_JPL2FileReader_JPL2_1CloseAllFile
(JNIEnv *, jobject) {
    PL2_CloseAllFiles();
}

JNIEXPORT jstring JNICALL Java_JPL2FileReader_JPL2_1GetLastError
(JNIEnv * env, jobject obj) {
    char error[256];
    PL2_GetLastError(error, 256);
    jstring errorMessage = env->NewStringUTF(error);
    return errorMessage;
}

JNIEXPORT jint JNICALL Java_JPL2FileReader_JPL2_1GetFileInfo
(JNIEnv *env, jobject obj, jint fileHandle, jobject info) {
    PL2FileInfo fileInfo;

    int result = PL2_GetFileInfo((int)fileHandle, &fileInfo);

    jfieldID fid;
    jclass clazz;
    clazz = env->GetObjectClass(info);

    fid = env->GetFieldID(clazz, "CreatorComment", "Ljava/lang/String;");
    jstring name = env->NewStringUTF(fileInfo.m_CreatorComment);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "CreatorSoftwareName", "Ljava/lang/String;");
    name = env->NewStringUTF(fileInfo.m_CreatorSoftwareName);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "CreatorSoftwareVersion", "Ljava/lang/String;");
    name = env->NewStringUTF(fileInfo.m_CreatorSoftwareVersion);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "CreatorDateTime", "Ljava/lang/String;");
    char buffer[45];
    asctime_s(buffer, sizeof buffer, &fileInfo.m_CreatorDateTime);
    name = env->NewStringUTF(buffer);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "CreatorDateTimeMilliseconds", "I");
    env->SetIntField(info, fid, fileInfo.m_CreatorDateTimeMilliseconds);

    fid = env->GetFieldID(clazz, "TimestampFrequency", "D");
    env->SetDoubleField(info, fid, fileInfo.m_TimestampFrequency);

    fid = env->GetFieldID(clazz, "NumberOfChannelHeaders", "I");
    env->SetIntField(info, fid, fileInfo.m_NumberOfChannelHeaders);

    fid = env->GetFieldID(clazz, "TotalNumberOfSpikeChannels", "I");
    env->SetIntField(info, fid, fileInfo.m_TotalNumberOfSpikeChannels);

    fid = env->GetFieldID(clazz, "NumberOfRecordedSpikeChannels", "I");
    env->SetIntField(info, fid, fileInfo.m_NumberOfRecordedSpikeChannels);

    fid = env->GetFieldID(clazz, "TotalNumberOfAnalogChannels", "I");
    env->SetIntField(info, fid, fileInfo.m_TotalNumberOfAnalogChannels);

    fid = env->GetFieldID(clazz, "NumberOfRecordedAnalogChannels", "I");
    env->SetIntField(info, fid, fileInfo.m_NumberOfRecordedAnalogChannels);

    fid = env->GetFieldID(clazz, "NumberOfDigitalChannels", "I");
    env->SetIntField(info, fid, fileInfo.m_NumberOfDigitalChannels);

    fid = env->GetFieldID(clazz, "MinimumTrodality", "I");
    env->SetIntField(info, fid, fileInfo.m_MinimumTrodality);

    fid = env->GetFieldID(clazz, "MaximumTrodality", "I");
    env->SetIntField(info, fid, fileInfo.m_MaximumTrodality);

    fid = env->GetFieldID(clazz, "NumberOfNonOmniPlexSources", "I");
    env->SetIntField(info, fid, fileInfo.m_NumberOfNonOmniPlexSources);

    fid = env->GetFieldID(clazz, "Unused", "I");
    env->SetIntField(info, fid, fileInfo.m_Unused);

    fid = env->GetFieldID(clazz, "ReprocessorComment", "Ljava/lang/String;");
    name = env->NewStringUTF(fileInfo.m_ReprocessorComment);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "ReprocessorSoftwareName", "Ljava/lang/String;");
    name = env->NewStringUTF(fileInfo.m_ReprocessorSoftwareName);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "ReprocessorSoftwareVersion", "Ljava/lang/String;");
    name = env->NewStringUTF(fileInfo.m_ReprocessorSoftwareVersion);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "ReprocessorDateTime", "Ljava/lang/String;");
    asctime_s(buffer, sizeof buffer, &fileInfo.m_CreatorDateTime);
    name = env->NewStringUTF(buffer);
    env->SetObjectField(info, fid, name);

    fid = env->GetFieldID(clazz, "ReprocessorDateTimeMilliseconds", "I");
    env->SetIntField(info, fid, fileInfo.m_ReprocessorDateTimeMilliseconds);

    fid = env->GetFieldID(clazz, "StartRecordingTime", "J");
    env->SetLongField(info, fid, fileInfo.m_StartRecordingTime);

    fid = env->GetFieldID(clazz, "DurationOfRecording", "J");
    env->SetLongField(info, fid, fileInfo.m_DurationOfRecording);

    return result;
}

After that I built this project in Visual Studio and it gave me a new dll file. I copied that dll file along with the original .lib and .dll files into the directory where all my Java code was and it worked. Thanks for telling me about JNI.

Other than that the only other concerns I have is I'm just wondering if the above code is the proper way to set object fields. I know I've been running into problems setting strings for object fields (Java crashes with a fatal exception error or the object field is not even set even though it says it is set). And for arrays this is what I do:

fid = env->GetFieldID(clazz, "UnitCounts", "[J");
jlongArray jUnitCounts = env->NewLongArray(256);
jlong* tempJUnitCounts = new jlong[256];
for (int i = 0; i < 256; i++) {
    tempJUnitCounts[i] = channelInfo.m_UnitCounts[i];
}
env->SetLongArrayRegion(jUnitCounts, 0, 256, tempJUnitCounts);
env->SetObjectField(spikeInfo, fid, jUnitCounts);

If there are better way to write JNI code, please let me know thanks.