`
longgangbai
  • 浏览: 7377775 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

【转】深入解析JNA—模拟C语言结构体

阅读更多

转载自http://blog.csdn.net/shendl/article/details/3599849

深入解析JNA—模拟C语言结构体

 

 

 

前言

前几天写《JNA--JNI终结者》一文介绍JNA框架。写完之后才发现,忘了写比较有难度的C语言Struct的模拟了。

今天就补上这篇文章,介绍Struct

不写怎样模拟C语言结构体,就不能算是真正解决了调用动态链接库的问题。

C语言的结构体用得实在是太广泛了。

 

 

首先说明一点,本文中大量把模拟Struct的类写作为接口的内部类。

这不是JNA规定的,而是一个编程习惯。

因为这些结构体(Structure类的子类),一般没有重用的价值,因此写成内部类比较方便。自然,你也可以把结构体写成一般的类。

 

 

 

 

3   使用JNA调用使用StructC函数

C语言开发

继续使用例2中的那个VSC++dll项目。

增加一个结构和使用该结构的函数。

头文件增加如下:

 

#define MYLIBAPI  extern   "C"     __declspec( dllexport ) 

struct UserStruct{

   long id;

   wchar_t*  name;

   int age;

 

};

 

MYLIBAPI void sayUser(UserStruct* pUserStruct);

 

JNA程序

对应的Java程序中,在例2  接口

 

/*

        * 定义一个类,模拟C语言的结构

        * */

       public static class UserStruct extends Structure{

          

          

 

           public NativeLong id;

           public WString name;

           public int age;

       }

      

       public void sayUser(UserStruct.ByReference struct);

 

 

Java中的调用代码:

UserStruct userStruct=new UserStruct ();

       userStruct.id=new NativeLong(100);

       userStruct.age=30;

       userStruct.name=new WString("沈东良");

       TestDll1.INSTANCE.sayUser(userStruct);

 

Struct说明

    现在,我们就在Java中实现了对C语言的结构的模拟。

这里,我们继承了Structure类,用这个类来模拟C语言的结构。

 

必须注意,Structure子类中的公共字段的顺序,必须与C语言中的结构的顺序一致。否则会报错!

 

因为,Java调用dll中的C函数,实际上就是一段内存作为函数的参数传递给dll

Dll以为这个参数就是C语言传过来的参数。

同时,C语言的结构是一个严格的规范,它定义了内存的次序。因此,JNA中模拟的结构的变量顺序绝对不能错。

如,一个Struct2int变量。  Int a, int b 

如果JNA中的次序和C中的次序相反,那么不会报错,但是得到的结果是相反的!

 

 

 

4   使用JNA调用使用嵌套Struct数组的C函数

如果C语言中的结构体是复杂的嵌套的结构体,该怎么办呢?

继续在上面例3的基础上扩充。

 

C语言开发

头文件增加如下:

struct CompanyStruct{

    long id;

   wchar_t*  name;

   UserStruct   users[100];

   int count;

 

};

 

MYLIBAPI void sayCompany(CompanyStruct* pCompanyStruct);

 

源文件:

   

void sayCompany(CompanyStruct* pCompanyStruct){

 

 std::wcout.imbue(std::locale("chs"));

 

   std::wcout<<L"ID:"<<pCompanyStruct->id<<std::endl;

   std::wcout<<L"公司名称:"<<pCompanyStruct->name<<std::endl;

    std::wcout<<L"员工总数:"<<pCompanyStruct->count<<std::endl;

      

     for(int i=0;i<pCompanyStruct->count;i++){

        sayUser(&pCompanyStruct->users[i]);

    

     }

  

 

 

}

 

JNA程序

Java程序中,在原来的接口上加上如下代码:

 

    public static class CompanyStruct extends Structure{

                     public NativeLong id;

           public WString  name;

                    

           public UserStruct.ByValue[] users=new UserStruct.ByValue[100];

           public int count;

          

          

       }

      

      public   void sayCompany(CompanyStruct pCompanyStruct);

 

对原来的UserStruct类进行改写:

/*

        * 定义一个类,模拟C语言的结构

        * */

       public static class UserStruct extends Structure{

          

            public static class ByReference extends UserStruct implements Structure.ByReference { }

            public static class ByValue extends UserStruct implements Structure.ByValue

 { }

 

           public NativeLong id;

           public WString name;

           public int age;

       }

 

调用JNA程序:

 

CompanyStruct companyStruct=new CompanyStruct();

       companyStruct.id=new NativeLong(1);

       companyStruct.name=new WString("Google");

       companyStruct.count=9;

   

       UserStruct.ByValue userStructValue=new UserStruct.ByValue();

       userStructValue.id=new NativeLong(100);

       userStructValue.age=30;

       userStructValue.name=new WString("沈东良");

       for(int i=0;i<companyStruct.count;i++){

           companyStruct.users[i]=userStructValue;

              }

       TestDll1.INSTANCE.sayCompany(companyStruct);

说明

可以看到,程序正确输出了。

    读者也许会有一些疑问。

 

 

1,为什么我们要给UserStruct 这个结构添加2个内部类呢?

Structure类的API说明,我们知道,这个类内部有2个接口:

static interface

Structure.ByReference
          Tagging interface to indicate the address of an instance of the Structure type is to be used within a
Structure definition rather than nesting the full Structure contents.

static interface

Structure.ByValue
          Tagging interface to indicate the value of an instance of the
Structure type is to be used in function invocations rather than its address.

2个内部接口是标记,内部什么都没有。

在运行时,JNA的执行框架会使用反射查看你是否实现了这2个接口,然后进行特定的处理。

(这种技术在java标注Annotation之前很流行。现在可以使用运行时Annotation实现同样的效果。

JNA项目据说1999年就启动了,使用这样的老技术不足为奇。只是很奇怪,为什么国内都没怎么听说过。我也是最近偶然在国外的网站上发现它的。一试之下,爱不释手,令我又对Java的桌面应用信心百倍!

 

 

如果你的Struct实现Structure.ByReference接口,那么JNA认为你的Struct是一个指针。指向C语言的结构体。

如果你的Struct实现Structure.ByValue接口,那么JNA认为你的Struct是值类型,就是C语言的结构体。

如果你不实现这2个接口,那么就相当于你实现了Structure.ByReference接口。

因此,在例3中,我没有实现这2个接口。

 

 

2C语言中,结构体内部必须进行数组定义。Java中最好也这样做。

C语言的结构体是一段连续的内存,内存的大小是编译时确定的。

因此,数组必须定义。否则编译不会成功。

 

对应的Java类中,我们也应该在类定义时为数组定义。尽管实际上在使用时再赋值也可以。

但是,强烈不建议你这样做。

如,上面

public UserStruct.ByValue[] users=new UserStruct.ByValue[100];

定义100个元素的数组,如果你不再类内部定义。

而在使用时定义,如果你没有正确赋值,没有定义为100个元素,就会出错。

 

从表面上看,CompanyStruct占用的内存是:

NativeLong  id

WString  name;

                    

           public UserStruct.ByValue[] users=new UserStruct.ByValue[100];

           public int count;

4个元素占用的内存的总和。

 

由于Java的数组是一个对象,users中实际保存的也应该是一个引用,也就是指针,32bit

 

那么CompanyStruct类占用的内存就比对应的C结构体:

struct CompanyStruct{

    long id;

   wchar_t*  name;

   UserStruct   users[100];

   int count;

};

 

小很多。内存少用很多。

我在例3的说明中曾经说过:

Java调用dll中的C函数,实际上就是一段内存作为函数的参数传递给dll

Dll以为这个参数就是C语言传过来的参数。

那么,JNA怎么还能正确调用C函数呢。

 

事实上,在调用C函数时,JNA会把UserStruct类的实例的所有数据全部*100倍。(我的例子里数组是100个,你的例子当然可以有不一样的数值)

这样,Java中的结构体的内存量就和C语言的CompanyStruct结构体占据相同大小和结构的内存,从而正确调用C函数。

 

5   使用JNA调用使用嵌套Struct的指针的数组的C函数

现在给大家看看最复杂的Struct的例子。

Struct中嵌套的是一个结构体的指针的数组。

 

C语言代码

struct CompanyStruct2{

    long id;

   wchar_t*  name;

  UserStruct*  users[100];

  // UserStruct   users[100];

   int count;

 

};

MYLIBAPI void sayCompany2(CompanyStruct2* pCompanyStruct);

 

这里,把刚才使用的UserStruct数组换成UserStruct指针的数组。

 

 

JNA代码

   

  public static class CompanyStruct2 extends Structure{

         public static class ByReference extends CompanyStruct2 implements Structure.ByReference { }

           public NativeLong id;

           public WString  name;

      

           public UserStruct.ByReference[] users=new UserStruct.ByReference[100];

           public int count;

          

          

       }

      public void sayCompany2(CompanyStruct2.ByReference  pCompanyStruct);

 

   

测试代码:

 

CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();

       companyStruct2.id=new NativeLong(2);

       companyStruct2.name=new WString("Yahoo");

       companyStruct2.count=10;

      

       UserStruct.ByReference pUserStruct=new UserStruct.ByReference();

       pUserStruct.id=new NativeLong(90);

       pUserStruct.age=99;

       pUserStruct.name=new WString("良少");

        pUserStruct.write();

    //  TestDll1.INSTANCE.sayUser(pUserStruct);

       for(int i=0;i<companyStruct2.count;i++){

           companyStruct2.users[i]=pUserStruct;

              }

      

       TestDll1.INSTANCE.sayCompany2(companyStruct2);

 

 

程序说明----Pin锁住Java对象:

因为是结构体的指针的数组,所以,我们使用了

public UserStruct.ByReference[] users=new UserStruct.ByReference[100];

来对应C语言中的

UserStruct*  users[100];

 

但是,有问题,如果去除

pUserStruct.write();

这一行代码,就会报错。

如果去除pUserStruct.write();

但是使用    TestDll1.INSTANCE.sayUser(pUserStruct);

也不会有问题。

 

这是怎么回事?

 

    原来,错误的原因就是内存锁定!

 

java内存锁定机制和JNIJNA调用

我们知道,java的内存是GC管理的。它会自动管理JVM使用的堆内存。删除不再使用的内存,并把Java对象使用的内存自动移动,以防止内存碎片增多。

因此,虽然Java的引用实际上就是指针,但还是和指针不同。Java中不能直接使用指针。因为对象在内存中的地址都不是固定的。说不准什么时候GC就把它给移位了。

 

如果使用JNIJNA等技术调用C函数。那么就会有问题。因此,C语言使用的是指针。如果想要获取C函数的返回值。那么我们必须提供一块内存给C语言访问。而C语言是不知道你Java的引用的。它只能访问固定的内存地址。

如果GCJava对象移来移去,那么C函数就没办法和Java交互了。

 

因此,在使用JNIJNA时,都会把Java对象锁住。GC不再管理。不删除,也不移动位置。由此出现的内存碎片,也不管了!

这种技术的术语是PIN  .NET也有同样的概念。

 

 

上面TestDll1.INSTANCE.sayUser(pUserStruct);这个调用是JNA调用。这个操作就把pUserStruct这个Java对象锁住了。

 

4中,嵌套的是结构体,因此也会直接把CompanyStruct类的实例锁住。

 

但是例5中,嵌套的是结构体的指针的数组。  CompanyStruct2类的实例companyStruct2在执行

TestDll1.INSTANCE.sayCompany2(companyStruct2);

时是被锁住了,可以companyStruct2 users 指针数组 的成员:

pUserStruct  都没有被锁住。

 

怎么办呢? 

难道每一个UserStruct.ByReference 的实例都先调用一遍不需要的

TestDll1.INSTANCE.sayUser(pUserStruct);  方法?

 

没事!JNA开发人员早已想到了:

Structure  类中有方法:

write

public void write()

Writes the fields of the struct to native memory


writeField

public void writeField(String name)

Write the given field to native memory. The current value in the Java field will be translated into native memory.

Throws:

IllegalArgumentException - if no field exists with the given name

 

    这些write方法,会把Java的内存Pin住。   就是C语言可以使用。GC不再管理它们。

 

现在只要调用

pUserStruct.write();

java模拟结构体实例给Pin住就可以了。

 

 

题外话,C#定义了语法,可以使用关键字 pin 锁住.NET对象。

 

 

 

结论:

结构体是C语言模拟OOP开发中经常使用的一种数据组织形式。搞定了结构体Struct,我们就可以放心大胆、轻轻松松地把C程序随便拿来用了!

 

分享到:
评论

相关推荐

    深入解析JNA—模拟C语言结构体

    在Java中使用JNA模拟C语言结构体的基本步骤如下: 1. **定义结构体类**:在Java中,我们需要创建一个类来模拟C语言中的结构体。这个类通常继承自`Structure`类,这是JNA提供的一种特殊类,用于表示原生C/C++中的...

    基于MATLAB GUI与CNN的模糊车牌识别系统:从图像预处理到字符识别全流程解析

    内容概要:本文详细介绍了基于MATLAB GUI界面和卷积神经网络(CNN)的模糊车牌识别系统。该系统旨在解决现实中车牌因模糊不清导致识别困难的问题。文中阐述了整个流程的关键步骤,包括图像的模糊还原、灰度化、阈值化、边缘检测、孔洞填充、形态学操作、滤波操作、车牌定位、字符分割以及最终的字符识别。通过使用维纳滤波或最小二乘法约束滤波进行模糊还原,再利用CNN的强大特征提取能力完成字符分类。此外,还特别强调了MATLAB GUI界面的设计,使得用户能直观便捷地操作整个系统。 适合人群:对图像处理和深度学习感兴趣的科研人员、高校学生及从事相关领域的工程师。 使用场景及目标:适用于交通管理、智能停车场等领域,用于提升车牌识别的准确性和效率,特别是在面对模糊车牌时的表现。 其他说明:文中提供了部分关键代码片段作为参考,并对实验结果进行了详细的分析,展示了系统在不同环境下的表现情况及其潜在的应用前景。

    嵌入式八股文面试题库资料知识宝典-计算机专业试题.zip

    嵌入式八股文面试题库资料知识宝典-计算机专业试题.zip

    嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_3.zip

    嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_3.zip

    开关磁阻电机技术参数与建模技术深度解析:4kW电机性能详述

    内容概要:本文深入探讨了一款额定功率为4kW的开关磁阻电机,详细介绍了其性能参数如额定功率、转速、效率、输出转矩和脉动率等。同时,文章还展示了利用RMxprt、Maxwell 2D和3D模型对该电机进行仿真的方法和技术,通过外电路分析进一步研究其电气性能和动态响应特性。最后,文章提供了基于RMxprt模型的MATLAB仿真代码示例,帮助读者理解电机的工作原理及其性能特点。 适合人群:从事电机设计、工业自动化领域的工程师和技术人员,尤其是对开关磁阻电机感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解开关磁阻电机特性和建模技术的研究人员,在新产品开发或现有产品改进时作为参考资料。 其他说明:文中提供的代码示例仅用于演示目的,实际操作时需根据所用软件的具体情况进行适当修改。

    少儿编程scratch项目源代码文件案例素材-剑客冲刺.zip

    少儿编程scratch项目源代码文件案例素材-剑客冲刺.zip

    少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip

    少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip

    四象限直流电机速度驱动控制系统PID控制仿真模型设计与实现

    内容概要:本文详细介绍了基于PID控制器的四象限直流电机速度驱动控制系统仿真模型及其永磁直流电机(PMDC)转速控制模型。首先阐述了PID控制器的工作原理,即通过对系统误差的比例、积分和微分运算来调整电机的驱动信号,从而实现转速的精确控制。接着讨论了如何利用PID控制器使有刷PMDC电机在四个象限中精确跟踪参考速度,并展示了仿真模型在应对快速负载扰动时的有效性和稳定性。最后,提供了Simulink仿真模型和详细的Word模型说明文档,帮助读者理解和调整PID控制器参数,以达到最佳控制效果。 适合人群:从事电力电子与电机控制领域的研究人员和技术人员,尤其是对四象限直流电机速度驱动控制系统感兴趣的读者。 使用场景及目标:适用于需要深入了解和掌握四象限直流电机速度驱动控制系统设计与实现的研究人员和技术人员。目标是在实际项目中能够运用PID控制器实现电机转速的精确控制,并提高系统的稳定性和抗干扰能力。 其他说明:文中引用了多篇相关领域的权威文献,确保了理论依据的可靠性和实用性。此外,提供的Simulink模型和Word文档有助于读者更好地理解和实践所介绍的内容。

    嵌入式八股文面试题库资料知识宝典-2013年海康威视校园招聘嵌入式开发笔试题.zip

    嵌入式八股文面试题库资料知识宝典-2013年海康威视校园招聘嵌入式开发笔试题.zip

    少儿编程scratch项目源代码文件案例素材-驾驶通关.zip

    少儿编程scratch项目源代码文件案例素材-驾驶通关.zip

    小区开放对周边道路通行能力影响的研究.pdf

    小区开放对周边道路通行能力影响的研究.pdf

    冷链物流路径优化:基于NSGA-2遗传算法与软硬时间窗策略的研究

    内容概要:本文探讨了冷链物流车辆路径优化问题,特别是如何通过NSGA-2遗传算法和软硬时间窗策略来实现高效、环保和高客户满意度的路径规划。文中介绍了冷链物流的特点及其重要性,提出了软时间窗概念,允许一定的配送时间弹性,同时考虑碳排放成本,以达到绿色物流的目的。此外,还讨论了如何将客户满意度作为路径优化的重要评价标准之一。最后,通过一段简化的Python代码展示了遗传算法的应用。 适合人群:从事物流管理、冷链物流运营的专业人士,以及对遗传算法和路径优化感兴趣的科研人员和技术开发者。 使用场景及目标:适用于冷链物流企业,旨在优化配送路线,降低运营成本,减少碳排放,提升客户满意度。目标是帮助企业实现绿色、高效的物流配送系统。 其他说明:文中提供的代码仅为示意,实际应用需根据具体情况调整参数设置和模型构建。

    少儿编程scratch项目源代码文件案例素材-恐怖矿井.zip

    少儿编程scratch项目源代码文件案例素材-恐怖矿井.zip

    基于STM32F030的无刷电机高压FOC控制方案:滑膜无感FOC技术及保护机制

    内容概要:本文详细介绍了基于STM32F030的无刷电机控制方案,重点在于高压FOC(磁场定向控制)技术和滑膜无感FOC的应用。该方案实现了过载、过欠压、堵转等多种保护机制,并提供了完整的源码、原理图和PCB设计。文中展示了关键代码片段,如滑膜观测器和电流环处理,以及保护机制的具体实现方法。此外,还提到了方案的移植要点和实际测试效果,确保系统的稳定性和高效性。 适合人群:嵌入式系统开发者、电机控制系统工程师、硬件工程师。 使用场景及目标:适用于需要高性能无刷电机控制的应用场景,如工业自动化设备、无人机、电动工具等。目标是提供一种成熟的、经过验证的无刷电机控制方案,帮助开发者快速实现并优化电机控制性能。 其他说明:提供的资料包括详细的原理图、PCB设计文件、源码及测试视频,方便开发者进行学习和应用。

    基于有限体积法Godunov格式的管道泄漏检测模型研究.pdf

    基于有限体积法Godunov格式的管道泄漏检测模型研究.pdf

    嵌入式八股文面试题库资料知识宝典-CC++笔试题-深圳有为(2019.2.28)1.zip

    嵌入式八股文面试题库资料知识宝典-CC++笔试题-深圳有为(2019.2.28)1.zip

    少儿编程scratch项目源代码文件案例素材-几何冲刺 V1.5.zip

    少儿编程scratch项目源代码文件案例素材-几何冲刺 V1.5.zip

    Android系统开发_Linux内核配置_USB-HID设备模拟_通过root权限将Android设备转换为全功能USB键盘的项目实现_该项目需要内核支持configFS文件系统.zip

    Android系统开发_Linux内核配置_USB-HID设备模拟_通过root权限将Android设备转换为全功能USB键盘的项目实现_该项目需要内核支持configFS文件系统

    C# WPF - LiveCharts Project

    C# WPF - LiveCharts Project

    少儿编程scratch项目源代码文件案例素材-恐怖叉子 动画.zip

    少儿编程scratch项目源代码文件案例素材-恐怖叉子 动画.zip

Global site tag (gtag.js) - Google Analytics