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

本文探讨了Java Native Access (JNA) 在实现Java与C语言间的互操作时的细节问题,特别是如何处理结构体(Struct)及内存锁定机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文 http://blog.youkuaiyun.com/shendl/archive/2008/12/26/3599849.aspx

 

2 C 语言中,结构体内部必须进行数组定义。 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 内存锁定机制和 JNI JNA 调用

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

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

 

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

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

 

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

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

 

 

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

 

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

 

但是例 5 中,嵌套的是结构体的指针的数组。   CompanyStruct 2 类的实例 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 程序随便拿来用了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值