⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 appa.htm

📁 一本Java由初级到高级的编程书籍
💻 HTM
📖 第 1 页 / 共 5 页
字号:
作为访问JNI函数的一个例子,请思考上述的代码。在这里,我们利用JNIEnv的自变量jEnv来访问一个Java字串。Java字串采取的是Unicode格式,所以假若收到这样一个字串,并想把它传给一个非Unicode函数(如printf()),首先必须用JNI函数GetStringUTFChars()将其转换成ASCII字符。该函数能接收一个Java字串,然后把它转换成UTF-8字符(用8位宽度容纳ASCII值,或用16位宽度容纳Unicode;若原始字串的内容完全由ASCII构成,那么结果字串也是ASCII)。<br>
GetStringUTFChars是JNIEnv间接指向的那个结构里的一个字段,而这个字段又是指向一个函数的指针。为访问JNI函数,我们用传统的C语法来调用一个函数(通过指针)。利用上述形式可实现对所有JNI函数的访问。<br>
<br>
A.1.3 传递和使用Java对象<br>
在前例中,我们将一个字串传递给固有方法。事实上,亦可将自己创建的Java对象传递给固有方法。<br>
在我们的固有方法内部,可访问已收到的那些对象的字段及方法。<br>
为传递对象,声明固有方法时要采用原始的Java语法。如下例所示,MyJavaClass有一个public(公共)字段,以及一个public方法。UseObjects类声明了一个固有方法,用于接收MyJavaClass类的一个对象。为调查固有方法是否能控制自己的自变量,我们设置了自变量的public字段,调用固有方法,然后打印出public字段的值。<br>
<br>
1004页上程序<br>
<br>
编译好代码,并将.class文件传递给javah后,就可以实现固有方法。在下面这个例子中,一旦取得字段和方法ID,就会通过JNI函数访问它们。<br>
<br>
1004-1005页程序<br>
<br>
除第一个自变量外,C++函数会接收一个jobject,它代表Java对象引用“固有”的那一面——那个引用是我们从Java代码里传递的。我们简单地读取aValue,把它打印出来,改变这个值,调用对象的divByTwo()方法,再将值重新打印一遍。<br>
为访问一个字段或方法,首先必须获取它的标识符。利用适当的JNI函数,可方便地取得类对象、元素名以及签名信息。这些函数会返回一个标识符,利用它可访问对应的元素。尽管这一方式显得有些曲折,但我们的固有方法确实对Java对象的内部布局一无所知。因此,它必须通过由JVM返回的索引访问字段和方法。这样一来,不同的JVM就可实现不同的内部对象布局,同时不会对固有方法造成影响。<br>
若运行Java程序,就会发现从Java那一侧传来的对象是由我们的固有方法处理的。但传递的到底是什么呢?是指针,还是Java引用?而且垃圾收集器在固有方法调用期间又在做什么呢?<br>
垃圾收集器会在固有方法执行期间持续运行,但在一次固有方法调用期间,我们的对象可保证不会被当作“垃圾”收集去。为确保这一点,事先创建了“局部引用”,并在固有方法调用之后立即清除。由于它们的“生命期”与调用过程息息相关,所以能够保证对象在固有方法调用期间的有效性。<br>
由于这些引用会在每次函数调用的时候创建和破坏,所以不可在static变量中制作固有方法的局部副本(本地拷贝)。若希望一个引用在函数存在期间持续有效,就需要一个全局引用。全局引用不是由JVM创建的,但通过调用特定的JNI函数,程序员可将局部引用扩展为全局引用。创建一个全局引用时,需对引用对象的“生存时间”负责。全局引用(以及它引用的对象)会一直留在内存里,直到用特定的JNI函数明确释放了这个引用。它类似于C的malloc()和free()。<br>
<br>
A.1.4 JNI和Java异常<br>
利用JNI,可丢弃、捕捉、打印以及重新丢弃Java异常,就象在一个Java程序里那样。但对程序员来说,需自行调用专用的JNI函数,以便对异常进行处理。下面列出用于异常处理的一些JNI函数:<br>
■Throw():丢弃一个现有的异常对象;在固有方法中用于重新丢弃一个异常。<br>
■ThrowNew():生成一个新的异常对象,并将其丢弃。<br>
■ExceptionOccurred():判断一个异常是否已被丢弃,但尚未清除。<br>
■ExceptionDescribe():打印一个异常和堆栈跟踪信息。<br>
■ExceptionClear():清除一个待决的异常。<br>
■FatalError():造成一个严重错误,不返回。<br>
在所有这些函数中,最不能忽视的就是ExceptionOccurred()和ExceptionClear()。大多数JNI函数都能产生异常,而且没有象在Java的try块内的那种语言特性可供利用。所以在每一次JNI函数调用之后,都必须调用ExceptionOccurred(),了解异常是否已被丢弃。若侦测到一个异常,可选择对其加以控制(可能时还要重新丢弃它)。然而,必须确保异常最终被清除。这可以在自己的函数中用ExceptionClear()来实现;若异常被重新丢弃,也可能在其他某些函数中进行。但无论如何,这一工作是必不可少的。<br>
我们必须保证异常被彻底清除。否则,假若在一个异常待决的情况下调用一个JNI函数,获得的结果往往是无法预知的。也有少数几个JNI函数可在异常时安全调用;当然,它们都是专门的异常控制函数。<br>
<br>
A.1.5 JNI和线程处理<br>
由于Java是一种多线程语言,几个线程可能同时发出对一个固有方法的调用(若另一个线程发出调用,固有方法可能在运行期间暂停)。此时,完全要由程序员来保证固有调用在多线程的环境中安全进行。例如,要防范用一种未进行监视的方法修改共享数据。此时,我们主要有两个选择:将固有方法声明为“同步”,或在固有方法内部采取其他某些策略,确保数据处理正确地并发进行。<br>
此外,绝对不要通过线程传递JNIEnv,因为它指向的内部结构是在“每线程”的基础上分配的,而且包含了只对那些特定的线程才有意义的信息。<br>
<br>
A.1.6 使用现成代码<br>
为实现JNI固有方法,最简单的方法就是在一个Java类里编写固有方法的原型,编译那个类,再通过javah运行.class文件。但假若我们已有一个大型的、早已存在的代码库,而且想从Java里调用它们,此时又该如何是好呢?不可将DLL中的所有函数更名,使其符合JNI命名规则,这种方案是不可行的。最好的方法是在原来的代码库“外面”写一个封装DLL。Java代码会调用新DLL里的函数,后者再调用原始的DLL函数。这个方法并非仅仅是一种解决方案;大多数情况下,我们甚至必须这样做,因为必须面向对象引用调用JNI函数,否则无法使用它们。<br>
<br>
A.2 微软的解决方案<br>
到本书完稿时为止,微软仍未提供对JNI的支持,只是用自己的专利方法提供了对非Java代码调用的支持。这一支持内建到编译器Microsoft 
JVM以及外部工具中。只有程序用Microsoft Java编译器编译,而且只有在Microsoft 
Java虚拟机(JVM)上运行的时候,本节讲述的特性才会有效。若计划在因特网上发行自己的应用,或者本单位的内联网建立在不同平台的基础上,就可能成为一个严重的问题。<br>
微软与Win32代码的接口为我们提供了连接Win32的三种途径:<br>
(1) J/Direct:方便调用Win32 DLL函数的一种途径,具有某些限制。<br>
(2) 本原接口(RNI):可调用Win32 DLL函数,但必须自行解决“垃圾收集”问题。<br>
(3) Java/COM集成:可从Java里直接揭示或调用COM服务。<br>
后续的小节将分别探讨这三种技术。<br>
写作本书的时候,这些特性均通过了Microsoft SDK for Java 2.0 beta 2的支持。可从微软公司的Web站点下载这个开发平台(要经历一个痛苦的选择过程,他们叫作“Active 
Setup”)。Java SDK是一套命令行工具的集合,但编译引擎可轻易嵌入Developer 
Studio环境,以便我们用Visual J++ 1.1来编译Java 1.1代码。<br>
<br>
A.3 J/Direct<br>
J/Direct是调用Win32 DLL函数最简单的方式。它的主要设计目标是与Win32API打交道,但完全可用它调用其他任何API。但是,尽管这一特性非常方便,但它同时也造成了某些限制,且降低了性能(与RNI相比)。但J/Direct也有一些明显的优点。首先,除希望调用的那个DLL里的代码之外,没有必要再编写额外的非Java代码,换言之,我们不需要一个封装器或者代理/存根DLL。其次,函数自变量与标准数据类型之间实现了自动转换。若必须传递用户自定义的数据类型,那么J/Direct可能不按我们的希望工作。第三,就象下例展示的那样,它非常简单和直接。只需少数几行,这个例子便能调用Win32 
API函数MessageBox(),它能弹出一个小的模态窗口,并带有一个标题、一条消息、一个可选的图标以及几个按钮。<br>
<br>
1008页程序<br>
<br>
令人震惊的是,这里便是我们利用J/Direct调用Win32 DLL函数所需的全部代码。其中的关键是位于示范代码底部的MessageBox()声明之前的@dll.import引导命令。它表面上看是一条注释,但实际并非如此。它的作用是告诉编译器:引导命令下面的函数是在USER32 
DLL里实现的,而且应相应地调用。我们要做的全部事情就是提供与DLL内实现的函数相符的一个原型,并调用函数。但是毋需在Java版本里手工键入需要的每一个Win32 
API函数,一个Microsoft Java包会帮我们做这件事情(很快就会详细解释)。为了让这个例子正常工作,函数必须“按名称”由DLL导出。但是,也可以用@dll.import引导命令“按顺序”链接。举个例子来说,我们可指定函数在DLL里的入口位置。稍后还会具体讲述@dll.import引导命令的特性。<br>
用非Java代码进行链接的一个重要问题就是函数参数的自动配置。正如大家看到的那样,MessageBox()的Java声明采用了两个字串自变量,但原来的C方案则采用了两个char指针。编译器会帮助我们自动转换标准数据类型,同时遵照本章后一节要讲述的规则。<br>
最好,大家或许已注意到了main()声明中的UnsatisfiedLinkError异常。在运行期的时候,一旦链接程序不能从非Java函数里解析出符号,就会触发这一异常(事件)。这可能是由多方面的原因造成的:.dll文件未找到;不是一个有效的DLL;或者J/Direct未获您所使用的虚拟机的支持。为了使DLL能被找到,它必须位于Windows或Windows\System目录下,位于由PATH环境变量列出的一个目录中,或者位于和.class文件相同的目录。J/Direct获得了Microsoft 
Java编译器1.02.4213版本及更高版本的支持,也获得了Microsoft JVM 4.79.2164及更高版本的支持。为了解自己编译器的版本号,请在命令行下运行JVC,不要加任何参数。为了解JVM的版本号,请找到msjava.dll的图标,并利用右键弹出菜单观察它的属性。<br>
<br>
A.3.1 @dll.import引导命令<br>
作为使用J/Direct唯一的途径,@dll.import引导命令相当灵活。它提供了为数众多的修改符,可用它们自定义同非Java代码建立链接关系的方式。它亦可应用于类内的一些方法,或应用于整个类。也就是说,我们在那个类内声明的所有方法都是在相同的DLL里实现的。下面让我们具体研究一下这些特性。<br>
<br>
1. 别名处理和按顺序链接<br>
为了使@dll.import引导命令能象上面显示的那样工作,DLL内的函数必须按名字导出。然而,我们有时想使用与DLL里原始名字不同的一个名字(别名处理),否则函数就可能按编号(比如按顺序)导出,而不是按名字导出。下面这个例子声明了FinestraDiMessaggio()(用意大利语说的“MessageBox”)。正如大家看到的那样,使用的语法是非常简单的。<br>
<br>
1010页上程序<br>
<br>
下面这个例子展示了如何同DLL里并非按名字导出的一个函数建立链接,那个函数事实是按它们在DLL里的位置导出的。这个例子假设有一个名为MYMATH的DLL,这个DLL在位置编号3处包含了一个函数。那个函数获取两个整数作为自变量,并返回两个整数的和。<br>
<br>
1010页下程序<br>
<br>
可以看出,唯一的差异就是entrypoint自变量的形式。<br>
<br>
2. 将@dll.import应用于整个类<br>
@dll.import引导命令可应用于整个类。也就是说,那个类的所有方法都是在相同的DLL里实现的,并具有相同的链接属性。引导命令不会由子类继承;考虑到这个原因,而且由于DLL里的函数是自然的static函数,所以更佳的设计方案是将API函数封装到一个独立的类里,如下所示:<br>
<br>
1011页程序<br>
<br>
由于MessageBeep()和MessageBox()函数已在不同的类里被声明成static函数,所以必须在调用它们时规定作用域。大家也许认为必须用上述的方法将所有Win32 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -