如何从Java中的C DLL加载和使用结构和函数?

时间:2016-12-19 20:14:33

标签: java c++ c dll dllimport

我有一个用于打开和读取某种文件类型的C dll。我创建了一个python SDK,可以加载dll并访问函数以读取文件。 ctypes模块还帮助我创建了通过将正确的c类型参数传递给函数来处理和获取数据的结构。

现在我正在尝试构建另一个SDK但在Java中使用endgame能够使用此SDK构建Android应用程序。我已经能够加载jna jar文件并访问只需要原始变量类型的函数。这是C ++头文件,它具有dll中可用的功能(删节一点):

#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

这是一个C ++头文件,其中包含将保存有关文件的信息的结构(也删节):

#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)

我做了一些研究,发现这个库名为JNA(Java Native Access)。

JNA Documentation

在大多数情况下,我能够遵循有关如何访问方法的文档,但访问结构有点混乱,没有很好的文档记录。这是我到目前为止为Java编写的内容:

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);
}  

无论如何,我的问题是如何将结构从C ++映射到Java SDK?然后我如何在函数中调用和使用它们,特别是因为它们是指针?我主要使用python,我知道Python有ctypes模块,可以很容易地加载和调用dll中的函数和结构。是否有类似Python for Java的ctypes模块或在Java中加载和使用c ++ dll函数和结构的更简单方法?任何帮助表示赞赏谢谢!

1 个答案:

答案 0 :(得分:1)

因此,在解决了所有奇怪的错误和不满意的链接错误后,我能够使用JNI。我基本上最终做的是首先构建一个加载dll的类,并具有如下的本机函数:

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);
}

同样为了处理结构,我构建了与结构域具有相同字段的java类(如上面的PL2FileInfo)。

然后我在comman提示符中运行了这些命令:

javac JPL2FileReader.java
javah JPL2FileReader

之后我将生成的.h文件导入到visual studio 2015项目中并创建了一个.cpp文件,该文件将初始化.h文件中的函数作为包装器调用上面列出的C ++头文件中的函数链接给我的dll并在java对象中设置字段作为结果(注意函数定义在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;
}

之后我在Visual Studio中构建了这个项目,它给了我一个新的dll文件。我将该dll文件与原始.lib和.dll文件一起复制到我所有Java代码所在的目录中并且它可以工作。谢谢你告诉我关于JNI的事。

除了我唯一的其他问题之外,我只是想知道上面的代码是否是设置对象字段的正确方法。我知道我在为对象字段设置字符串时遇到了问题(Java崩溃带有致命异常错误,或者甚至没有设置对象字段,即使它设置了它)。对于阵列,这就是我的工作:

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);

如果有更好的方法来编写JNI代码,请告诉我们谢谢。