`
yangdong
  • 浏览: 66409 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

在JNI中调用本地带结构体参数的函数

    博客分类:
  • Java
阅读更多
说起JNI,《The Java Native Interface -- Programmer's Guide and Specification》我认为是挺好的入门教程。浅显易懂,而且也附有参考。对很多问题和陷阱也进行了讲解和提示。可以在 Sun 的官网上免费下载到这本书,下载地址:http://java.sun.com/docs/books/jni/download/jni.pdf。

但是我认为这本书在第 9 章 Leveraging Existing Native Libaries 中对开头所讲的一段程序的解释有点潦草。内容大意是有一个 Win32 API, CreateFile。它带了很多参数。有 const char*、DWORD、HANDLE,最重要的是它带了一个 SECURITY_ATTRIBUTES*,一个结构体指针。
HANDLE CreateFile(
	const char *fileName, // file name
	DWORD desiredAccess, // access (read-write) mode
	DWORD shareMode, // share mode
	SECURITY_ATTRIBUTES *attrs, // security attributes
	DWORD creationDistribution, // how to create
	DWORD flagsAndAttributes, // file attributes
	HANDLE templateFile // file with attr. to copy
);

这个 SECURITY_ATTRIBUTES 是自定义的。具体的定义就不给出了。书中也给出了对应的 Java 函数。
public class Win32 {
	public static native int CreateFile(
		String fileName, // file name
		int desiredAccess, // access (read-write) mode
		int shareMode, // share mode
		int[] secAttrs, // security attributes
		int creationDistribution, // how to create
		int flagsAndAttributes, // file attributes
		int templateFile); // file with attr. to copy
	...
}

但怪的是,作者使用了一个 int[] 来对应 SECURITY_ATTRIBUTES*。对此,作者只有一段短小的解释。
引用

Because of potential differences in how fields are laid out in memory, we do
not map C structures to classes in the Java programming language. Instead, we use
an array to store the contents of the C structure SECURITY_ATTRIBUTES. The caller
may also pass null as secAttrs to specify the default Win32 security attributes.
We will not discuss the contents of the SECURITY_ATTRIBUTES structure or how to
encode that in an int array.


我认为这个解释是为了让读者耐心读下去而写的。这里用 int[] 是说不通的。可以理解作者。书中这时的重点还是讲解如何调用一个普通函数。在读完这章的时候,我才明白,这里应该用后面所介绍的 Peer Class 来做。但我后面给出的解决方法并不是针对这个例子的。但是,看完后您可以自已写一个解决方法。因为后面的示例给出一个解决 Java 调用 C/C++ 带结构体参数函数的思路。

Peer Class,用我的话来说就是一个 Java 类,它包含了一个 C/C++ 对象的指针。一个通常的 Peer Class 长成这个样子:
public class PeerClass {

	private long peer;
	...

}

引用《The Java Native Interface -- Programmer's Guide and Specification》,java.io.FileDescriptor 也包含一个 int fd。用来保存对应本地结构体的一个指针。所以说,Peer Class 的使命就是提供一个对 C/C++ 结构体或类的一个包装,使得 Java 可以使用。故,解决上面 CreateFile 函数参数问题的办法就是针对 SECURITY_ATTRIBUTES 结构体定义一个包装类。具体的实现我用另外一个例子。因为 CreateFile 的签名太长,有点吓人 (#o#)

假定,Java 一段程序需要调用 C++ 一个函数 CppFunc(STRUCT* stru)。这个 STRUCT 包含一个 long 成员变量和一个 char 成员变量。CppFunc 会打印出这个结构体实例中变量的值。为了构造一个这样的 C++ 结构体,我们给它做一个包装类。也就是一个 Peer Class。不妨叫 StructWrapper。通过构造 StructWrapper,Java 程序给其对应的 C++ 结构体赋值。再将这个 StructWrapper 实例传给 Java 里对 CppFunc 的包装函数,从而达到目的。

示例中的 C++ 工程是一个 Win32 DLL。下面的代码展示可能为了逻辑的连贯性而把一个文件的内容分开来。读者实践的时候可以自行合并。

首先,定义 C++ 的 CppFunc 和 STRUCT 结构体。
// Entry.cpp

using namespace std;

void CppFunc(STRUCT* stru)
{
	cout << "long value:\t" << stru->l << endl;
	cout << "char value:\t" << stru->c << endl;
}


// Struct.h

#ifndef _Included_STRUCT
#define _Included_STRUCT

typedef struct _STRUCT
{
	long l;
	char c;
} STRUCT;

#endif

再次,定义 STRUCT 的包装类 StructWrapper。
// StructWrapper.java

public class StructWrapper {

	private long peer;
	
	public long getPeer() {
		return peer;
	}
	
	private native long initialize(long l, char c);
	
	private native void destroy(long peer);
	
	public StructWrapper(long l, char c) {
		peer = initialize(l, c);
	}
	
	public synchronized void destroy() {
		if (peer != 0) {
			destroy(peer);
			peer = 0;
		}
	}
	
	protected void finalize() {
		destroy();
	}
	
	static {
		System.loadLibrary("TestJNI");
	}
	
}

initialize 函数的作用就是调用 C++ 代码来初始化一个 C++ 下的 STRUCT 对象,并返回这个对象的指针。StructWrapper 在构造时把这个指针保存在 peer 中。这里 peer 其实可以用 int。因为 C++ 下指针是四字节,Java 下 int 也是四字节。destroy 设计为线程安全的原因是因为在 finalize 方法中会自动调用 destroy。而用户也可能会手动销毁对象。有可能出现并发的情况。

还缺一个针对 CppFunc 的 Java 包装函数。
// Entry.java

public class Entry {
	
	private native void callCppFunc(long structWrapperPeer);

	public static void main(String[] args) {
		...
	}
	
}

callCppFunc(long) 这个 Java 函数。它提供了一个 CppFunc 的包装。它所需要的参数实际上是 StructWrapper 实例中所保存的 C++ STRUCT 结构体实例的地址。这样就实现了把 Java 对象传给 C++。

大家看到,这里 StructWrapper 用到了几个本地函数。我们一样得在 C++ 中实现它们。
// StructWrapper.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class StructWrapper */

#ifndef _Included_StructWrapper
#define _Included_StructWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     StructWrapper
 * Method:    initialize
 * Signature: (JC)J
 */
JNIEXPORT jlong JNICALL Java_StructWrapper_initialize
  (JNIEnv *, jobject, jlong, jchar);

/*
 * Class:     StructWrapper
 * Method:    destroy
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_StructWrapper_destroy
  (JNIEnv *, jobject, jlong);

#ifdef __cplusplus
}
#endif
#endif


// StructWrapper.cpp

#include "StructWrapper.h"
#include "Struct.h"

JNIEXPORT jlong JNICALL
Java_StructWrapper_initialize(JNIEnv* env, jobject self, jlong l, jchar c)
{
	STRUCT* peer = new STRUCT();
	peer->l = (long)l;
	peer->c = (char)c;
	return (jlong)peer;
}

JNIEXPORT void JNICALL
Java_StructWrapper_destroy(JNIEnv* env, jobject self, jlong peer)
{
	delete (STRUCT*)peer;
}

Okay,现在可以定义主函数了。
// Entry.java

public class Entry {
	
	private native void callCppFunc(long strutWrapperPeer);

	public static void main(String[] args) {
		StructWrapper struct = new StructWrapper(1, 'a');
		
		new Entry().callCppFunc(struct.getPeer());
		
		struct.destroy();
	}
	
}

其对应的 C++ 实现,
// Entry.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Entry */

#ifndef _Included_Entry
#define _Included_Entry
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Entry
 * Method:    callCppFunc
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_Entry_callCppFunc
  (JNIEnv *, jobject, jlong);

#ifdef __cplusplus
}
#endif
#endif

// Entry.cpp

JNIEXPORT void JNICALL
Java_Entry_callCppFunc(JNIEnv* env, jobject self, jlong structWrapperPeer)
{
	STRUCT* structPointer = (STRUCT*)structWrapperPeer;
	CppFunc(structPointer);
}

如果顺利的话,您现在跑这个 Java 程序,应该输出
引用

long value: 1
char value: a


至此,示例结束。但引出一个问题,如何把 C++ 中的结构传给 Java?一样,通过 Peer Class。我觉得各位肯定会举一反三,想出解决办法的。
4
0
分享到:
评论
7 楼 meimei_123abc 2012-07-31  
你好,可以把这个完整的代码给我发一个学习下吗,刚开始接触这一块没头绪,谢谢!我的邮箱meimei_123abc@163.com
6 楼 yangdong 2012-05-20  
sorry,之后一直没再碰过 JNI,没法再写了。
5 楼 ihopethatwell 2012-03-12  
楼主,能写一个传递数组的结构体?
4 楼 yangdong 2010-06-11  
@hnzhangshi:你是说文章开头的那本书的作者给的示例?那个用数组来做是不对的。JNI 我有一年没碰了,不知道你说的是不是这个意思。。。
3 楼 hnzhangshi 2010-06-11  
例子写的很好,有个问题请教一下,如果结果体中的变量很多,总不能一个个写出来吧?能解释一下怎么用数组传值吗?谢谢了
2 楼 yangdong 2009-01-07  
对不起,原来的源代码已经不在了。
1 楼 hyint 2009-01-07  
有没好的代码,发一个给我,谢谢!能完整最好了,hyint@163.com 谢谢

相关推荐

    java调用C语言编写的so库中的函数,java调用C语言编写的dll库中的函数

    例如,`java jni调用so中的函数api.txt`中可能包含了具体的JNI函数调用示例。 其次,JNA是一种更高级的接口,它提供了更简洁的方式来调用本地库,无需编写C/C++代码。JNA通过映射Java方法到本地函数,减少了编程...

    java使用JNI调用C++ dll库用法概述

    - 指针在JNI中可以作为参数传递,但需要注意内存管理。Java对象的引用在JNI层必须使用`GlobalRef`或`LocalRef`来保持其生命周期,防止垃圾回收。 6. **编译和链接**: - 编译C++代码为动态链接库(DLL),确保...

    java通过jna返回结构体例子.rar

    在Java中调用返回结构体的C函数,可以使用`PointerByReference`来接收结果,因为JNA无法直接返回结构体实例。例如: ```java Library lib = Library.INSTANCE; // Library是你的JNA接口,里面定义了C函数的签名 ...

    android JNI C 调用Java

    这通常在初始化时完成,例如在`JNI_OnLoad`函数中: ```c JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; (*vm)-&gt;GetEnv(vm, (void **)&env, JNI_VERSION_1_6); jclass ...

    使用C++创建java虚拟机JVM,使用JNI调用java函数.zip

    在提供的资源"使用C++创建java虚拟机JVM,使用JNI调用java函数"中,可能详细介绍了以上步骤的实际应用,包括代码示例和可能遇到的问题及解决方案。通过学习和实践这个教程,开发者可以更好地理解和掌握C++与Java之间...

    Android的jni的调用CC++的几个应用基本类型数组类结构体.pdf

    本文将探讨在Android中使用JNI调用C/C++的基本类型、数组和类(结构体)。 首先,确保你的开发环境配置正确。在本例中,开发者使用的是Android 2.2 SDK和NDK r4b,这在Linux环境下支持C++异常、RTTI(运行时类型...

    Java通过JNI调用C函数Demo

    3. **调用Java方法**: 如果需要在C函数中调用Java方法,可以使用`env`指针提供的函数,如`CallVoidMethod`,`GetObjectField`等。 **动态链接库(DLL)** 1. **TestJni.dll**: 这是编译后的C代码,作为一个动态链接库...

    简单jni实例调用第三方.so库

    总结来说,这个“简单JNI实例调用第三方.so库”的例子展示了如何在Java应用程序中通过JNI调用C/C++代码。这个过程包括:定义Java的native方法、使用javah生成C/C++头文件、编写C/C++实现、编译生成.so库以及在Java...

    jni本地函数注册

    JNI(Java Native Interface)是Java平台提供的一种标准接口,用于让Java代码调用本地(非Java)代码,如C和C++。在Android开发中,JNI常被用来编写高效的底层库,实现性能关键部分或者利用现有C/C++库。本篇文章将...

    android jni调用资料示例

    使用JNI调用本地方法涉及以下步骤: - 在Java类中声明native方法,并使用`System.loadLibrary()`加载对应的本地库。 - 在对应的C/C++头文件中声明JNI函数原型,通常以`JNIEXPORT`和`JNIEnv*`开头。 - 实现C/C++...

    JNI编程(二) —— 让C++和Java相互调用(2)

    `JNIEXPORT`和`JNICALL`宏是JNI约定的调用约定,`JNIEnv*`指向一个包含所有JNI函数指针的结构体,`jobject`参数是Java对象实例的引用。在这个例子中,`Java_HelloWorld_sayHello`是C++函数的特定命名规则,由Java...

    JNI攻略之十一――启动虚拟机调用java类

    ### 二、JNI调用Java类的具体步骤 #### 1. 准备阶段 首先,我们需要一个简单的Java程序,例如上述例子中的`Prog.java`。这个程序定义了一个静态方法`main`,它接收一个字符串数组参数,并输出一段包含参数信息的...

    JNA 转java接口以及指针结构体解析

    JNA允许你在Java中定义回调函数,这些函数可以被本地代码调用。在提供的`CallBackTest`示例中,可能包含了一个如何定义和注册Java回调函数的示例。回调函数通常需要实现`com.sun.jna.Callback`接口,并将适当类型的...

    亲测可用,java 成功调用dll函数。包含调用回调函数,springboot版本。最近由于公司业务需要,要调用dll文件,用JNA调用。

    Java调用DLL函数是跨平台编程中的一种常见需求,特别是在Java与C/C++代码交互时。JNA(Java Native Access)是Java平台上的一个库,它允许Java代码直接调用本机库(如DLL文件)的函数,而无需编写JNI(Java Native ...

    java用JNA调用dll实例,包含各种参数调用

    你需要定义一个接口,该接口的每个方法对应DLL中的一个函数,然后使用`NativeLibrary`类加载DLL,并将接口实例化,这样就可以在Java中直接调用DLL函数了。 1. **基本数据类型**:JNA支持Java的基本数据类型如int、...

    Jni中C++和Java的参数传递.pdf

    在C++中,你可以创建一个函数来处理这种转换,并通过JNI的`NewObject`和`GetObjectField`等函数获取Java对象的字段值。 总的来说,JNI提供了一种灵活的方式来连接Java和C++代码,允许开发者充分利用两种语言的优点...

    Jni中C和Java的参数传递.doc

    Java 中创建一个对应的 Native 类,声明一个接受该结构体指针作为参数的本地方法: ```java public class DiskInfoJava { private native void getDiskInfo(DiskInfo diskInfo); static { System.loadLibrary(...

    在Java程序中使用JNative调用dll文件

    在Java程序中调用DLL(动态链接库)文件通常是通过JNI(Java Native Interface)来实现的,而JNative是JNI的一个封装库,它提供了一种更简洁的方式来调用C/C++编写的本地代码。这篇博客文章可能介绍了如何利用...

    JNI层传递数据

    2. **获取Java对象的JNI引用**:在JNI函数中,使用`env-&gt;GetObjectClass`获取内部类的Java Class对象,然后通过`env-&gt;FindClass`找到对应的JNI类型签名。对于内部类,类型签名需要包含外部类名和内部类名。 3. **...

    java调用C++编写的动态库dll

    在Java编程环境中,有时我们需要利用C++编写的高性能或特定功能的库,这时可以借助Java的JNI(Java Native Interface)技术来调用C++编写的动态链接库(DLL)。本教程将详细介绍如何在Java中调用C++动态库,并通过...

Global site tag (gtag.js) - Google Analytics