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

标签 java c++ c dll dllimport

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

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

#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 SDK 映射到 Java SDK?那么我如何在函数中调用和使用它们,尤其是因为它们是指针?我主要使用 python,我知道 Python 有 ctypes 模块,它可以很容易地加载和调用 dll 中的函数和结构。是否有类似 Python for Java 的 ctypes 模块或更简单的方法来加载和使用 Java 中的 c++ dll 函数和结构?感谢您的帮助,谢谢!

最佳答案

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

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

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

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 代码,请告诉我,谢谢。

关于java - 如何在 Java 中加载和使用 C DLL 中的结构和函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41230304/

相关文章:

java插件缓存和动态IP主机

java - libGDX : change Scroll position of scroll pane

java - 如何使用 Java 运行 .sh 文件并获取其输出?

java - com.sun.jersey.server.impl.application.RootResourceUriRules.<init> ResourceConfig 实例不包含任何根资源类

C - 编译器错误 : dereferencing pointer to incomplete type

c - 我如何找出哪个 header 正在使用哪些功能?

c++ - 为什么线程过程应该是静态的或成员函数

c++ - OpenCV Otsu 的阈值 : Calculate single threshold value for multiple Mat objects

c++ - 将元素插入到 C++ 映射中 - 插入方法的差异

c - 如何将从函数获得的结果传输到 C 中的 char 数组?