
Java Native Access (JNA) 是一个强大的库,它允许Java程序直接调用原生共享库(如DLL或SO文件)中的函数,而无需编写JNI代码。JNA通过在运行时动态生成JNI代码来完成这一任务,极大地简化了Java与原生代码的交互。然而,当涉及到复杂数据类型,特别是包含嵌套结构体(struct)或联合体(union)时,正确地将Java对象映射到原生内存布局是实现无缝集成的关键。理解JNA对这些复杂类型的处理机制,对于避免常见的运行时错误至关重要。
JNA通过com.sun.jna.Structure类来表示C语言中的结构体。当Java类继承自Structure时,JNA会根据其字段的声明顺序和类型,自动计算其在原生内存中的大小和偏移量。Structure类的核心是getFieldOrder()方法,它返回一个字符串列表,定义了结构体字段在内存中的排列顺序。这个顺序必须与C语言结构体中的字段顺序完全一致,否则会导致内存错位,引发不可预测的行为甚至程序崩溃。
在JNA映射过程中,一个常见的错误是java.lang.IllegalArgumentException: The type "..." is not supported: Native size for type "..." is unknown。这个异常通常发生在尝试将一个非Structure或Union类型的自定义类作为Structure的字段时。
考虑以下原生C语言结构体定义:
typedef struct
{
UCHAR ucProtocolType;
UCHAR ucAddReader;
} Install_CD97_GTML_Param; // 假设这是原生中的一个结构体
typedef union
{
Install_CD97_GTML_Param xCd97Param;
// ... 其他参数类型
} InstallCardParam;
typedef struct
{
eTypCardType xCardType;
InstallCardParam iCardParam;
} InstallCard;
short sSmartInstCardEx(const InstallCard *pxInstallCard);现在,我们尝试在Java中进行映射,并遇到了问题:
错误的JNA映射示例:
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
// JNA Structure 基类
public class InstallCard extends Structure {
public int xCardType;
// 注意:这里的getFieldOrder()可能需要调整,取决于最终的嵌套结构
protected List<String> getFieldOrder() {
return Arrays.asList("xCardType", "iCardParam"); // 假设iCardParam是字段
}
}
// 尝试将Install_CD97_GTML作为InstallCard的子类,并包含一个非Structure的字段
public class Install_CD97_GTML extends InstallCard {
// 错误的做法:iCardParam字段的类型CD97_GTML_Parameter不是JNA Structure或Union
public CD97_GTML_Parameter iCardParam;
@Override
protected List<String> getFieldOrder() {
// 当InstallCard被继承时,子类通常需要重新定义getFieldOrder,
// 或者基类包含所有字段,子类只是逻辑上的扩展。
// 在本例中,如果Install_CD97_GTML是最终要传递的结构体,
// 且它有自己的字段,则需要包含这些字段。
// 但根据原生定义,InstallCardParam是一个Union,Install_CD97_GTML_Param是Union的一个成员。
// 这里的映射逻辑一开始就有问题,但为了演示错误,我们先这样构造。
return Arrays.asList("xCardType", "iCardParam");
}
}
// 一个普通的Java类,没有继承Structure
public class CD97_GTML_Parameter {
public byte ucProtocolType;
public byte ucAddReader;
}
// 调用代码
public class Main {
public static void main(String[] args) {
Install_CD97_GTML pxInstallCardCD97 = new Install_CD97_GTML();
CD97_GTML_Parameter pxCD97_GTML_Parameter = new CD97_GTML_Parameter();
pxInstallCardCD97.xCardType = 1;
pxCD97_GTML_Parameter.ucAddReader = 0;
pxCD97_GTML_Parameter.ucProtocolType = 1;
pxInstallCardCD97.iCardParam = pxCD97_GTML_Parameter;
// 假设ReaderThalesApi.INSTANCE是JNA接口实例
// ReaderThalesApi.INSTANCE.sSmartInstCardEx(pxInstallCardCD97);
// 上述调用将抛出异常
}
}当运行上述代码时,JNA会抛出以下异常:
Exception in thread "main" java.lang.IllegalArgumentException: Invalid Structure field in class Install_CD97_GTML, field name 'iCardParam' (class CD97_GTML_Parameter): The type "CD97_GTML_Parameter" is not supported: Native size for type "CD97_GTML_Parameter" is unknown
at com.sun.jna.Structure.validateField(Structure.java:1246)
...
Caused by: java.lang.IllegalArgumentException: The type "CD97_GTML_Parameter" is not supported: Native size for type "CD97_GTML_Parameter" is unknown
at com.sun.jna.Native.getNativeSize(Native.java:1412)
...这个异常清晰地指出,JNA无法确定CD97_GTML_Parameter类型在原生内存中的大小。这是因为CD97_GTML_Parameter没有继承Structure(或Union),JNA无法对其进行内存布局分析。所有作为Structure字段的复杂类型,都必须是Structure或Union的子类。
要正确解决上述问题并实现原生C结构体的精确映射,我们需要确保所有嵌套的结构体和联合体都继承自JNA的Structure或Union类,并正确实现getFieldOrder()方法。
根据原生C代码:
typedef struct
{
UCHAR ucProtocolType;
UCHAR ucAddReader;
} Install_CD97_GTML_Param; // 对应JNA Structure
typedef union
{
Install_CD97_GTML_Param xCd97Param;
// ... 其他参数类型
} InstallCardParam; // 对应JNA Union
typedef struct
{
eTypCardType xCardType;
InstallCardParam iCardParam;
} InstallCard; // 对应JNA Structure正确的JNA映射示例:
首先,定义最小的嵌套结构体:
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
// 对应C语言的 Install_CD97_GTML_Param 结构体
public class CD97_GTML_Parameter extends Structure {
public byte ucProtocolType;
public byte ucAddReader;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("ucProtocolType", "ucAddReader");
}
}接着,定义原生C语言中的联合体InstallCardParam。JNA提供了com.sun.jna.Union类来处理联合体。联合体的字段共享同一块内存,其大小由最大成员决定。
import com.sun.jna.Union;
import java.util.Arrays;
import java.util.List;
// 对应C语言的 InstallCardParam 联合体
public class InstallCardParam extends Union {
// 联合体成员,这里只列出CD97_GTML_Param,根据需要可以添加其他类型
public CD97_GTML_Parameter xCd97Param;
// public Install_CD98_GTML xCd98Param; // 如果原生联合体有更多成员,这里也需要添加
@Override
protected List<String> getFieldOrder() {
// 联合体的getFieldOrder()也需要列出所有成员
return Arrays.asList("xCd97Param"); // 列出所有联合体成员
}
}最后,定义包含联合体的顶层结构体InstallCard:
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
// 对应C语言的 InstallCard 结构体
public class InstallCard extends Structure {
public int xCardType; // 对应eTypCardType,假设为int
public InstallCardParam iCardParam; // 嵌套的联合体
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("xCardType", "iCardParam");
}
// 可以在这里添加一个方便的构造函数或方法来设置特定类型的参数
public void setCD97Param(byte protocolType, byte addReader) {
CD97_GTML_Parameter param = new CD97_GTML_Parameter();
param.ucProtocolType = protocolType;
param.ucAddReader = addReader;
this.iCardParam.xCd97Param = param;
this.iCardParam.setType(CD97_GTML_Parameter.class); // 明确设置联合体当前使用的类型
}
}调用代码示例:
// JNA接口定义
public interface ReaderThalesApi extends com.sun.jna.Library {
ReaderThalesApi INSTANCE = com.sun.jna.Native.load("YourNativeLibrary", ReaderThalesApi.class);
short sSmartInstCardEx(InstallCard pxInstallCard);
}
public class Main {
public static void main(String[] args) {
InstallCard pxInstallCard = new InstallCard();
pxInstallCard.xCardType = 1;
// 设置联合体中的CD97参数
pxInstallCard.setCD97Param((byte)1, (byte)0);
// 确保结构体实例被写入原生内存,特别是当有嵌套结构体或联合体时
pxInstallCard.write();
short res = ReaderThalesApi.INSTANCE.sSmartInstCardEx(pxInstallCard);
System.out.println("Native call result: " + res);
}
}通过这种方式,JNA能够正确解析所有字段的内存布局,从而避免IllegalArgumentException。
直接映射原生结构体和联合体虽然解决了JNA的底层问题,但有时原生结构体的设计可能不符合Java面向对象的习惯,或者过于复杂,导致Java代码难以维护和阅读。在这种情况下,可以采用数据转换(或适配器模式)的策略:在Java层定义一套更“友好”的Java对象,然后在调用原生方法之前,将这些友好对象转换成JNA所需的Structure实例。
“友好”的Java类定义:
// 这是一个普通的Java类,用于在Java应用内部表示数据,不继承JNA Structure
public class Friendly_CD97_GTML_Parameter {
public final byte ucProtocolType;
public final byte ucAddReader;
public Friendly_CD97_GTML_Parameter(byte ucProtocolType, byte ucAddReader) {
this.ucProtocolType = ucProtocolType;
this.ucAddReader = ucAddReader;
}
}
// 这是一个普通的Java类,用于在Java应用内部表示数据,不继承JNA Structure
public class Friendly_Install_CD97_GTML {
public final int xCardType;
public final Friendly_CD97_GTML_Parameter iCardParam;
public Friendly_Install_CD97_GTML(int xCardType, byte ucProtocolType, byte ucAddReader) {
this.xCardType = xCardType;
this.iCardParam = new Friendly_CD97_GTML_Parameter(ucProtocolType, ucAddReader);
}
}数据转换方法:
在调用原生方法之前,需要一个转换方法将Friendly_Install_CD97_GTML对象转换为我们前面定义的JNA InstallCard结构体。
public class JNAConverter {
/**
* 将友好的Java对象转换为JNA所需的InstallCard结构体。
* @param friendlyData 友好的Java数据对象
* @return 映射到原生内存的JNA InstallCard结构体实例
*/
public static InstallCard convertToJNAInstallCard(Friendly_Install_CD97_GTML friendlyData) {
InstallCard jnaInstallCard = new InstallCard();
jnaInstallCard.xCardType = friendlyData.xCardType;
// 设置联合体中的CD97参数
jnaInstallCard.setCD97Param(
friendlyData.iCardParam.ucProtocolType,
friendlyData.iCardParam.ucAddReader
);
// 确保结构体实例被写入原生内存
jnaInstallCard.write();
return jnaInstallCard;
}
}调用代码示例:
// JNA接口定义(同上)
public interface ReaderThalesApi extends com.sun.jna.Library {
ReaderThalesApi INSTANCE = com.sun.jna.Native.load("YourNativeLibrary", ReaderThalesApi.class);
short sSmartInstCardEx(InstallCard pxInstallCard);
}
public class Main {
public static void main(String[] args) {
// 使用友好的Java对象构建数据
Friendly_Install_CD97_GTML friendlyData = new Friendly_Install_CD97_GTML(
1, // xCardType
(byte)1, // ucProtocolType
(byte)0 // ucAddReader
);
// 将友好的Java对象转换为JNA Structure
InstallCard pxInstallCard = JNAConverter.convertToJNAInstallCard(friendlyData);
short res = ReaderThalesApi.INSTANCE.sSmartInstCardEx(pxInstallCard);
System.out.println("Native call result: " + res);
}
}这种方法将JNA的复杂性封装在转换逻辑中,使得业务逻辑层可以使用更符合Java习惯的数据结构,提高了代码的可读性和可维护性。
以上就是JNA高级教程:深入理解原生结构体与联合体映射的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号