在JNI中调用本土带结构体参数的函数
在JNI中调用本地带结构体参数的函数
说起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*,一个结构体指针。
这个 SECURITY_ATTRIBUTES 是自定义的。具体的定义就不给出了。书中也给出了对应的 Java 函数。
但怪的是,作者使用了一个 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 长成这个样子:
引用《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 结构体。
再次,定义 STRUCT 的包装类 StructWrapper。
initialize 函数的作用就是调用 C++ 代码来初始化一个 C++ 下的 STRUCT 对象,并返回这个对象的指针。StructWrapper 在构造时把这个指针保存在 peer 中。这里 peer 其实可以用 int。因为 C++ 下指针是四字节,Java 下 int 也是四字节。destroy 设计为线程安全的原因是因为在 finalize 方法中会自动调用 destroy。而用户也可能会手动销毁对象。有可能出现并发的情况。
还缺一个针对 CppFunc 的 Java 包装函数。
callCppFunc(long) 这个 Java 函数。它提供了一个 CppFunc 的包装。它所需要的参数实际上是 StructWrapper 实例中所保存的 C++ STRUCT 结构体实例的地址。这样就实现了把 Java 对象传给 C++。
大家看到,这里 StructWrapper 用到了几个本地函数。我们一样得在 C++ 中实现它们。
Okay,现在可以定义主函数了。
其对应的 C++ 实现,
如果顺利的话,您现在跑这个 Java 程序,应该输出
long value: 1
char value: a
至此,示例结束。但引出一个问题,如何把 C++ 中的结构传给 Java?一样,通过 Peer Class。我觉得各位肯定会举一反三,想出解决办法的。
说起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。我觉得各位肯定会举一反三,想出解决办法的。
1 楼
hyint
2009-01-07
有没好的代码,发一个给我,谢谢!能完整最好了,hyint@163.com 谢谢
2 楼
yangdong
2009-01-07
对不起,原来的源代码已经不在了。
3 楼
hnzhangshi
2010-06-11
例子写的很好,有个问题请教一下,如果结果体中的变量很多,总不能一个个写出来吧?能解释一下怎么用数组传值吗?谢谢了
4 楼
yangdong
2010-06-11
@hnzhangshi:你是说文章开头的那本书的作者给的示例?那个用数组来做是不对的。JNI 我有一年没碰了,不知道你说的是不是这个意思。。。
5 楼
ihopethatwell
2012-03-12
楼主,能写一个传递数组的结构体?
6 楼
yangdong
2012-05-20
sorry,之后一直没再碰过 JNI,没法再写了。
7 楼
meimei_123abc
2012-07-31
你好,可以把这个完整的代码给我发一个学习下吗,刚开始接触这一块没头绪,谢谢!我的邮箱meimei_123abc@163.com