我有一个用于打开和读取某种文件类型的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)。
在大多数情况下,我能够遵循有关如何访问方法的文档,但访问结构有点混乱,没有很好的文档记录。这是我到目前为止为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函数和结构的更简单方法?任何帮助表示赞赏谢谢!
答案 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代码,请告诉我们谢谢。