0

0

JNA高级教程:深入理解原生结构体与联合体映射

碧海醫心

碧海醫心

发布时间:2025-08-25 17:04:01

|

629人浏览过

|

来源于php中文网

原创

jna高级教程:深入理解原生结构体与联合体映射

本教程详细探讨了JNA在与原生库交互时,如何正确映射包含嵌套结构体或联合体的复杂数据类型。文章首先分析了IllegalArgumentException的常见原因——非Structure类型字段导致JNA无法确定原生大小,随后提供了两种解决方案:一是直接通过JNA的Structure和Union类精确映射原生C语言结构,二是采用数据转换策略,优化Java层代码的可读性和维护性,同时确保与原生库的正确交互。

1. 引言:JNA与原生结构体映射

Java Native Access (JNA) 是一个强大的库,它允许Java程序直接调用原生共享库(如DLL或SO文件)中的函数,而无需编写JNI代码。JNA通过在运行时动态生成JNI代码来完成这一任务,极大地简化了Java与原生代码的交互。然而,当涉及到复杂数据类型,特别是包含嵌套结构体(struct)或联合体(union)时,正确地将Java对象映射到原生内存布局是实现无缝集成的关键。理解JNA对这些复杂类型的处理机制,对于避免常见的运行时错误至关重要。

2. JNA映射核心:Structure与内存布局

JNA通过com.sun.jna.Structure类来表示C语言中的结构体。当Java类继承自Structure时,JNA会根据其字段的声明顺序和类型,自动计算其在原生内存中的大小和偏移量。Structure类的核心是getFieldOrder()方法,它返回一个字符串列表,定义了结构体字段在内存中的排列顺序。这个顺序必须与C语言结构体中的字段顺序完全一致,否则会导致内存错位,引发不可预测的行为甚至程序崩溃。

3. 问题分析:IllegalArgumentException的根源

在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:1241246)
    ...
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的子类。

4. 解决方案一:直接映射原生结构体与联合体

要正确解决上述问题并实现原生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映射示例:

首先,定义最小的嵌套结构体:

Loomi
Loomi

全球首个AI社媒内容多智能体系统

下载
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。

5. 解决方案二:通过数据转换优化Java层代码

直接映射原生结构体和联合体虽然解决了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习惯的数据结构,提高了代码的可读性和可维护性。

6. 注意事项与最佳实践

  • 所有嵌套类型必须继承Structure或Union: 这是JNA正确映射复杂数据类型的基本要求。
  • getFieldOrder()的准确性: 结构体字段的顺序必须与C语言定义严格一致。任何不匹配都可能导致内存访问错误。
  • 联合体的使用: 当使用Union时,需要通过setType()方法明确指示当前联合体正在使用哪个成员,以便JNA知道如何正确读写内存。
  • 内存对齐: JNA默认会尝试匹配平台的内存对齐规则。但在某些特定情况下,可能需要使用Structure.ALIGN_NONE、Structure.ALIGN_GNUC等参数或@Field注解来手动调整对齐方式。
  • **Structure.ByReference与`Structure.

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

410

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

641

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

362

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

264

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

638

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

565

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

672

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

618

2023.09.22

chatgpt使用指南
chatgpt使用指南

本专题整合了chatgpt使用教程、新手使用说明等等相关内容,阅读专题下面的文章了解更多详细内容。

0

2026.03.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 11.5万人学习

C 教程
C 教程

共75课时 | 5.5万人学习

C++教程
C++教程

共115课时 | 22.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号