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

Posted

技术标签:

【中文标题】如何在 Java 中从 C DLL 加载和使用结构和函数?【英文标题】:How to Load and Use Structs and Functions from a C DLL in Java? 【发布时间】:2016-12-19 20:14:32 【问题描述】:

我有一个用于打开和读取特定文件类型的 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 函数和结构的更简单的方法?感谢您提供任何帮助!

【问题讨论】:

学习java native interface @KevinDTimm 谢谢你回来找我。所以我做了一些研究,发现了这两个链接:linklink我浏览了教程并尝试了他们的代码,但是当我运行时遇到了不满意的链接错误,然后当我修复它时,它不能找到我的主要课程。另外根据教程,我必须将 C 代码中的变量类型更改为 j变量类型,并且没有关于结构的明确信息。 有很多例子和教程。不要从你真正的问题开始。从简单的事情开始。然后构建它。 【参考方案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)。

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

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 中从 C DLL 加载和使用结构和函数?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中从 C++ .DLL 加载值始终返回 true

在 C/C++ (Win64) 中从内存中加载 64 位 DLL

在 Eclipse IDE 中从 Java 调用 c/c++ DLL

如何在 C++ 中从 dll(dll 中的构造函数)创建一些类?

如何在 C#/Python 中从 DLL 调用函数

我们如何在 C# 中从 C++ DLL 中获取所有方法?