楼主: lastwinner

[转载] JAVA应用程序设计开发

[复制链接]
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
51#
 楼主| 发表于 2006-6-28 04:57 | 只看该作者
第十二章 脚本语言JavaScript
12.1 认识JavaScript
  12.1.1 简介


  12.1.2 JavaScript和Java


  12.1.3 JavaScript和HTML页面


12.2 JavaScript资源
12.3 尝试使用对象
12.4 利用定时器
12.5 一个较复杂的例子

_______________________________________________________
汗,原文就是这个样子

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
52#
 楼主| 发表于 2006-6-28 04:57 | 只看该作者
第十三章 native方法
  这一章讨论Java与C的接口问题。目前在Java中使用C编写的函数有两种方法,本章将分别予以介绍。

  13.1 引入native方法的原因
  Java作为一种新兴的语言,在短时间内迅速崛起,给人们留下了深刻的印象。然而,它的功能毕竟还很不完善。而C语言不但拥有丰富的函数、众多的熟练程序员,而且有多年应用积累下来的宝贵经验和资源。另外某些工作无论从功能还是效率来考虑用C来做都更合适些。这就带来了Java与C的接口问题。

  诚然,C的引入对Java的多平台适应性是有影响的。使用了native方法的Java程序只能在编译它的平台上运行——这或许就是“native方法”这个名字的由来。“native”的原义是“本地的”,而对native方法,我们可以称为“原生方法”,也即指用C实现的方法。

  在Java这样一种纯面向对象的语言中引用C程序不是件简单的事情。由于C语言的灵活性,Java的设计者们不得不采用一些限制手段来防止产生安全问题。因此,使用native方法的过程可能比你设想的要复杂的多。但是,只要你按我们介绍,按部就班地去做,一样可以轻松地达到目的。

  原生方法技术也在不断地发展。在1.0.2版本中使用的原生方法接口在性能上有所欠缺,不利于垃圾收集的进行。1.1.1版本已经引入了一种新的原生方法接口,叫做Java Native Interface,简称JNI。但旧的接口仍被保留。二者的关系可以归纳为三点:

  (1)可以用新版的JDK按旧的接口办法使用原生方法。

  (2)用旧的JDK生成的程序和动态链接库可以在新版的虚拟机上运行(即可以用新版的解释器来解释执行)。

  (3)新旧两种接口办法在编程细节和动态链接库生成步骤上略有差别。

  本章将对新旧两种办法给予介绍。

  最后需要附带提一句,由微软公司开发的Java软件开发包SDK在native方法的实现上有所改动,与SUN公司的JDK提供的实现有一些差异。本文将以SUN公司的JDK为准。如果读者使用的是微软公司的开发环境,请参阅有关文档。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
53#
 楼主| 发表于 2006-6-28 04:58 | 只看该作者
  13.2 使用native方法的步骤
  这一节介绍旧的原生方法接口办法。在1.0x版本和1.1版本中此办法均适用。

  在程序中使用native方法,大致需要5个步骤。为了说明问题,我们举一个很简单的例子。这个例子的功能是用C语言的printf()函数打印一个字符串常量。下面我们依次序进行。

  13.2.1 创建Java源文件和.class文件
  这一步与我们做普通Java程序的工作相似。native方法不是直接嵌入在Java代码中的,在Java源码中只列出原型。具体的实现(C文件)将被编译成动态链接库的一部分,由Java解释器装入并调用其中的native方法。因此,在程序中要做两件事:一是声明native方法,二是装入动态链接库(在Unix系统下应叫共享库)。下面我们来看看具体就怎样做(例13.1)。

  例13.1 DispDemo.java。

  1:class DispDemo{
  2:  static{
  3:   Runtime.getRuntime().loadLibrary("dispdll";
  4:  }
  5:  public native void display();
  6: }

  DispDemo是我们定义的一个类。在这个类中,有一个方法display()是用C语言实现的,也就是native方法。我们在第5行对它进行了声明。

  第2至4行是一个语句,它的功能是装入动态链接库。它被static修饰符所修饰,因而它只能在DispDemo类被装入执行时执行一次。即使我们需要创建多个DispDemo类的对象,也只需装入动态链接库一次。装入动态链接库的工作由方法loadLibrary()完成。在语言类库java.lang中,System类和Runtime类中都定义了这个方法。这里用Runtime类的对象来调用它。我们也可以把第三行的语句改为

  System.loadLibrary("dispdll";

一样可以达到目的。

  为了调用native方法,我们又创建了一个类DispDemoMain(例13.2)。它包含Java解释器入口——main方法。它的功能是创建一个DispDemo对象并调用后者的native方法。

  例 13.2 DispDemoMain.java。

  1:class DispDemoMain{
  2: public static void main(String args[]){
  3:  ispDemo().display();
  4: }
  5:}

然后我们把两个.java文件编译成.class文件。由于DispDemoMain中使用了DispDemo类,只需作:

  javac DispDemoMain.java

就可以翻译好前述两个文件。编译器会自动寻找DispDemo.java并编译之。当然,手工一个个编译也可以。

  13.2.2 生成头文件和存根文件
  在第三章中,我们简单介绍了JDK中包含工具。其中之一是C头文件生成器javah(可参看节3.7)。它的主要功能是生成头文件和存根文件。头文件就是C程序员熟悉的.h文件,存根文件指由相应.class文件自动生成的.c文件。

  在第一步得到了两个.class文件:DispDemoMain.class和DispDemo.class文件。前者与native方法实现没什么关联,这一步暂时不去管它。我们来为DispDemo.class生成头文件和存根文件(以Win95/NT平台为例,Uinx类似)。

  用命令(“目录”指.class文件所在目录)
  C:\目录>javah DispDemo
可以得到DispDemo.h文件,再用命令
  C:\目录>javah -stubs DispDemo
可以得到DispDemo.c文件。
  如果打了上述命令后出现DispDemo:no such class的提示信息,说明类路径设置不对。类路径必须包括.class文件所在目录。比如,当我们这个例子的文件都在C:\bookDemo\Ch13之下时,可用
  C:\bookDemo\Ch13>set classpath=\java\lib\classes.zip;.;
(其中\java是JDK的主目录)设置类路径,再运用上述两个命令,即得到头文件和存根文件。

  我们这个例子生成的头文件见例13.3。

  例13.3 DispDemo.h文件。

  1:/* DO NOT EDIT THIS FILE - it is machine generated */
  2:#include <native.h>
  3:/*Header for class DispDemo */
  4:
  5:#ifndef _Included _DispDemo
  6:#define _Included _DispDemo
  7:
  8:#pragma pach(4)
  9:
  10:typedef struct ClassDispDemo{
  11:  char PAD;/* ANSI C requires structures to have a least one member */
  12:}ClassDispDemo;
  13:HandleTo(DispDemo);
  14:
  15ragma pack()
  16:
  17:#ifdef _cplusplus
  18:extern "C"{
  19:#endif
  20:extern void DispDemo_display(struct HDispDemo *);
  21:#ifdef _cplusplus
  22:}
  23:#endif
  24:#endif

  存根文件内容为(见例13.4):

  例13.4 DispDemo.c文件。

  1:/*DO NOT EDIT THIS FILE - it is machine generated */
  2:#include <StubPreamble.h>
  3:
  4:/*Stubs for class DispDemo*/
  5:/* SYMBOL: "DispDemo/display()V",Java_DispDemo_display_stub */
  6:__declspec(dllexport) stack_item *Java_DispDemo_display_stub(stack_item * _P_,struct execenv *_EE_){
  7: extern void DispDemo_display(void *);
  8: (void) DispDemo_display(_P_[0].p);
  9: return _P_;
  10:}

  这两个文件是由机器生成的,不要修改它们。将这两个文件与DispDemo.java相对比,可以发现一些联系。在头文件DispDemo.h中,第20行是:
  20:extern void DispDemo_display(struct HDispDemo *);
这显然是与我们在DispDemo.java中声明的native方法有关的。原文是:
  public native void display();
两相对比可以发现,display()这一方法的名字在头文件中变成了“DispDemo_display”。由此可以得到一个结论:在实现相应方法时,应该用的名字是<类名>_<方法名>。另外,原本无参的方法多了一个参数struct HDispDemo *,这是一个指向该方法所在类的指针(这个指针的用法有些待殊,我们以后还将另外说明)。因此,实现该方法时,这个参数也要添上。

  对这两个机器生成的文件,我们只需关心其中与实现native方法有关的细节。对我们这个例子,记住上一段所说的两点就足够了(名字和参数)。

  13.2.3 创建C源文件
  在上一节的基础上,我们来处理native方法的具体实现。

  创建实现文件的第一个要点是包括应具备的文件。除了完成功能必需的C语言头文件之外,不要包括“StubPreamble.h”和刚才机器生成的类的头文件。在我们这个例子中,需要包括三个头文件,如例13.5所示。

  例 13.5 DispImp.c

  1:#include <stdio.h>
  2:#include <StubPreamble.h>
  3:#include "DispDemo.h"
  4:void DispDemo_display(struct HDispDemo *this){
  5: printf("Native method executing...\n";
  6:}

  在例13.5的程序中,我们实现了native方法display(),或者更确切地说是:

  void DispDemo_display(struct HDispDemo *this)

  只要记住节13.2.2中所提到的两点——名字和增加的一个参数,不难写出这样一段程序。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
54#
 楼主| 发表于 2006-6-28 04:58 | 只看该作者
  13.2.4 生成动态链接库(共享库)
  这是一个机械的过程,只需要用适当的工具去编译、链接即可。

  在Unix下,用cc来编译:

  %cc -G DispDemo.c DispImp.c -o libdispdll.so

  假如这样不能产生预期结果,一般的原因是头文件的路径找不到。这样的情况可以加-I参数指明路径:

  %cc -I<Java主目录>/include -I<Java主目录>/include/solaris DispDemo.c DispImp.c -o libdispdll.so

  其中,<Java主目录>指安装Java JDK的目录,dispdll.so是要生成的共享库的名称。

  在Win95/NT平台下,需使用VC++2.0或更晚的版本来生成动态链接库。下面给出了VC++4.2版本生成动态链接库dispdll.dll的过程。

  C:\bookDemo\ch13>cl DispDemo.c DispImp.c -Fedispdll.dll -MD -LD javai.lib
  Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 10.20.6166 for 80x86
  Copyright(C) Microsoft Corp 1984-1996. All rights reserved.

  DispDemo.c
  DispImp.c
  Generating Code...
  Microsoft (R) 32-Bit Incremental Linker Version 4.20.6164
  Copyright (C) Microsoft Corp 1992-1996. All rights reserved.

  /out:dispdll.dll
  /dll
  /implib:dispdll.lib
  DispDemo.obj
  DispImp.obj
  javai.lib
   Creating library dispdll.lib and object dispdll.exp

  C:\bookDemo\ch13>java DispDemoMain
  Native method executing...

  C:\bookDemo\ch13>

  命令中各参数含义可参看相应命令的帮助信息。如果出现问题,一般也是路径没设置好,需用以下命令设置:

  set INCLUDE=<VC原先的INCLUDE路径>;<Java主目录>\include;<Java主目录>\include\win32;

  set LIB=<VC原先的LIB路径>;<Java主目录>\lib;

如:

  set INCLUDE=\msdev\include;\msdev\mfc\include;\jdk1.0.2\java\include;\jdk1.0.2\java\include\win32;
  set LIB=\msdev\lib;\msdev\mfc\lib;\jdk\1.0.2\java\lib


  使用它(节13.2.5中介绍)时,不要忘记将生成的.dll文件拷贝到.class文件所在目录。

  13.2.5 装入动态链接库,调用native方法
  这一节只要像往常一样运行Java程序即可。运行结果如下:

  C:\bookDemo\ch13>java DispDemoMain
  Native method executing...

  C:\bookDemo\ch13>
  如果出现
  java.lang.NullPointerExeption或Java.lang.UnsatisfiedLinkError,可能是动态链接库路径有问题。最简单的办法是拷贝.dll(或.so)文件至.class文件所在目录。在Unix中,可以用

  %setenv LD_LIBRARY_PATH<路径>

来指明.so文件的路径。

  以上我们结合例子讲解了native方法的使用过程。读者不妨按这一节所述的步骤做一遍,这样可以尽快地掌握使用native方法的一般过程。

  在下一节中,我们还将举两个例子,说明使用native方法的一些细节。本节中的例子不能涵盖所有要注意的问题。但读者可以学到遇到问题的基本对策。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
55#
 楼主| 发表于 2006-6-28 04:59 | 只看该作者
  13.3 要注意的问题
  13.3.1 类型转换
  在第二章中,我们讨论了Java的类型。可见它与C语言的类型是有差异的。这一差异体现在native方法的使用中,其现象就是类型发生了转换。下面我们来看一个例子。

  这个例子的功能是这样的:从键盘读入5个整数,把它们存入一个链表,再遍历此链表,打印存储其中的值。指针是C的一大特点。我们这个例子中有意使用链表来演示native方法中指针的使用。

  首先来看包含原生方法的Java类ListDemo的源程序(例13.6)。

  例13.6 ListDemo.java

  1: import java.io.*;
  2:
  3: public class ListDemo{
     //原生方法原型
  4:  public native void createList(int[] ints);
  5:  public native void showList();
     //装入动态链接库
  6:  static{
  7:   System.loadLibrary("listdll";
  8:  }
  9:  static int intArray[]={0,0,0,0,0};
  10: public void run() throws IOException{
     //读五个整数,存入整型数组
  11: DataInputStream dataIn=
  12: new DataInputStream(System.in);
  13: System.out.println("Enter 5 integers:";
  14: for(int i=0;i<5;i++)
  15:  intArray=Integer.valueOf(data.readLine()).intValue();
     //调用原生方法
  16: createList(intArray);
  17:  showList();
  18:  }
  19: }

  这个程序的第4、第5行声明了两个native方法。createList()用以创建一个链表,而showList()遍历链表并显示其值。run()方法中创建了一个数据输入流,用它读入了5个整数,存入整数数组intArray中(15行)。然后以intArray为参数调用createList(),最后调用showList()显示链表内容。

  为了Java解释器能找到运行入口,我们又创建一个ListDemoMain类,源码见例13.7。

  例13.7 ListDemoMain.java。

  1:import java.io.*;
  2: public class ListDemo{
  3:  public static void main(String args[]) throws IOException{
  4:   ListDemo ld = new ListDemo();
  5:   ld.run();
  6:  }
  7:}

  将两个文件编译成.class文件,再用javah生成头文件和存根文件(例13.8、13.9)。

  例13.8 ListDemo.h。

  1: /*DO NOT EDIT THIS FILE - it is machine generated */
  2: #include <natiove.h>
  3: /* Header for class ListDemo */
  4:
  5: #ifndef _Included _ListDemo
  6: #define _Included _ListDemo
  7:
  8: #pragma pack(4)
  9:
  10: typdef struct ClassListDemo{
  11:/*Inaccessible static:intArray*/
  12: char PAD;/* ANSI C requires structures to have a least one member */
  13:}ClassListDemo;
  14:HandleTo(ListDemo);
  15:
  16:#pragma pack()
  17:
  18:#ifdef __cplusplus
  19:extern "C"{
  20:#endif
  21:extern void ListDemo_createList(struct HListDemo *,HArrayOfInt *);
  22:extern void ListDemo_showList(struct HListDemo *);
  23:#ifdef __cplusplus
  24:}
  25:#endif
  26:#endif

  例13.9 ListDemo.c。

  1:/*DO NOT EDIT THIS FILE - it is machine generated */
  2:#include <StubPreamble.h>
  3:
  4:/*Stubs for class ListDemo*/
  5:/*SYMBOL:"ListDemo/createList([I)V",Java_ListDemo_createList_stub*/
  6:__declspec(dllexport)stack_item *Java_ListDemo_createList_stub(stack_item *_P_,struct execenv *_EE_){
  7: extern void ListDemo_createList(void *,void *);
  8: (void)ListDemo_createList(_P_[0].p,((_P_[1].p)));
  9:return _P_;
  10:}
  11:/*SYMBOL:"ListDemo/showList()V",Java_ListDemo_showList_stub*/
  12:__declspec(dllexport) stack_item *Java_ListDemo_showList_stub(stack_item *_P_,struct execenv *_EE_){
  13: extern void ListDemo_showList(void *);
  14: (void)ListDemo_showList(_P_[0].p);
  15: return _P_;
  16:}

  我们只看一看ListDemo.h文件。第10行至第13行定义了一个结构,对应于前面定义的ListDemo类。从第11行可知,ListDemo类中定义的类变量intArray在native方法中是无法访问的,不过这并不妨碍它作为native方法的参数。

  第21、22行对应于在例13.6中声明的两个native方法。像第二节中所提到的那样,两个方法都增加了一个参数struct HListDemo *。另外,我们要传递的整数数组参数变成了“HArrayOfInt *”类型。那么,我们如何去取其中的值呢?

  打开你的Java安装目录,在include子目录下找到文件oobj.h。打开它,找到这样一段(在不同的JDK版本中,这一段位于文件oobj.h中的行号未必相同)。

  134:typedef sturct{
  135:long body[1]
  136:}ArrayOfInt
  137:typedef ArrayOfInt ClassArrayOfInt;
  138:HandleTo(ArrayOfInt);

其中,HandleTo(T)是一个宏。它的定义可在47行找到:
#define HandleTo(T) typedef struct H##T
  Class##T *obj;
  struct methodtable *methods;
}H##T

  从中可以看出,ArrayOfInt对应了整数数组,其值用body[]数组来表示。135行用body[1]是指认为数组中至少有一个元素,因为标准C需要结构中必须至少有一个元素。由此的启发是,我们可能要用

  p->body

来引用整数数组的元素,其中p是一个指向结构ArrayOfInt的指针。

  另外要注意的是,body是一个long型数组。这是由Java中的int型转化来的。基本类型之间的转换可参见下表(表13.1)。

  表13.1 类型转换

Java类型 对应C类型
boolean long
byte char
short short
int long
long int 64_t
float float
double double
char unicode
object struct Hjava_lang_object*

  下面再来看看HArrayOfInt。从宏定义来看,我们可把HandleTo(ArrayOfInt)展开为:

  struct HArrayOfInt{
    ClassArrayOfInt *obj;
    struct methodtable *methods;
  }HAarrayOfInt;

其中,ClassArrayOfInt不过是ArrayOfInt的一个别名(137行)。因此,使用HArrayOfInt* 指针的方法就很清楚了。设有这样一个指针p,则需做以下工作:
  (1)得到p所对应的ArrayOfint* 指针,即结构定义中的“obj”。这一步用
  unhand(p);
可以做到。unhand的定义见<Java主目录>\include\interpretor.h中的第213行。
  (2)用p->body可以取对应整数数组中的值。
  读者或许会问:为什么不直接交待一下类型转换的规则呢?原因在于,一是我们想向读者介绍一下native方法的内部机制,二是为读者提供一个思路:假如你遇到怪模怪样的指针,手头又无书本可查,怎么办(当然如果有现成资料可查就不必这么费劲了)?在第三个例子中读者可能检验一下自己的能力。

  下面我们来看一下native方法的实现文件(例13.10)。有了前面的说明,读者理解也就不困难了。

  例 13.10 ListImp.c

#include <StubPreamble.h>
#include "ListDemo.h"
#include <stdio.h>
struct Nodes{//定义链表结点的结构
long value;
struct Nodes *next;
}
static struct Nodes *List;
void ListDemo_createList(struct HListDemo *this,HArrayOfInt *Array){
int i;
ArrayOfInt *p=unhand(Array);
struct Nodes *temp;
temp=(struct Nodes *)malloc(sizeof(struct Nodes));
temp->value=p->body[0];
temp->next=NULL;
List=temp;
for(i=1;i<5;I++){
temp->next=(struct Nodes*)malloc(sizeof(struct Nodes));
temp-=temp->next;
temp->value =p->body;
temp->next=NULL;
}
}
void ListDemo_showList(struct HListDemo *this){
struct Nodes *tmp;
tmp=List;
printf("The content of list:";
while(tmp!=NULL){
printf("%4d",tmp->value);
tmp = tmp->next;
}
}
  程序第4~7行定义了链表结点的结构。对应于Java中的整数,链表的结点中存储的值为long型。

  函数ListDemo_createList()用来创建链表。注意第11行和14、20行。这三行是引用参数数据的部分。

  函数ListDemo_showList()打印链表内容。

  这个例子给我们的最后一点启示是可以在native方法的实现程序中定义全局变量,如List。

  最后看一下库的生成和程序结果。

  生成动态链接库Listdll.dll的过程如下:

  C:\bookDemo\ch13>cl ListDemo.c ListImp.c -Felispdll.dll -MD -LD javai.lib
  Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 10.20.6166 for 80x86
  Copyright (C) Microsoft Corp 1984-1996.Allrights reserved.

  ListDemo.c
  ListImp.c
  Generating Code...
  Microsoft (R) 32-Bit Incremental Linker Version 4.20.6164
  Copyright (C) Microsoft Corp 1992-1996. All rights reserved.

  /out:lispdll.dll
  /dll
  /implib:lispdll.lib
  ListDemo.obj
  ListImp.obj
  javai.lib
   Creating library lispdll.lib and object lispdll.exp

  例13.10的运行结果如下:
  C:\bookDemo\ch13>java ListDemoMain
  Enter 5 integers:
  12
  34
  45
  678
  234
  The content of list:12 34 45 678 234
  C:\bookDemo\ch13>

  本节介绍了Java与C接口上的类型转换规则,以及使用对象作为参数的方法。附带说一句,当使用对象作参数时,如果不了解如何引用对象中变量的值,则需查找有关文件(如前面提到的oobj.h和interpretor.h)。一个快捷的途径是用VC集成环境中的查找功能(适用于Win95/NT平台)。对于已编译链接过的项目文件,还可用鼠标右键点击字串寻找这一字符串的定义/引用点。这属于VC的使用技巧,在此不多述。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
56#
 楼主| 发表于 2006-6-28 05:00 | 只看该作者
  13.3.2 复杂情况处理——一个测试
  这一节中我们再用一个例子进一步说明一些细节问题。

  这个例子的功能是:在Java主执行类上定义了一个字符串,用native方法打印该字符串的内容和长度。

  下面我们给出这个例子的Java源文件(见例13.11)。读者可以试着编写一个实现文件ObjectImp.c,并用前面所学到的方法生成动态链接库。

  例13.11 ObjectDemoMain.java。

import java.lang.*;
public class ObjectDemoMain{
public String message="Can it work?";
public static void main(String args[]){
ObjectDemo objDemo = new ObjectDemo();
objDemo.run();
}
}
class ObjectDemo{
private native void display(ObjectDemoMain obj);
static{
System.loadLibrary("objdll";
}
public void run(){
ObjectDemoMain obj=new ObjectDemoMain();
display(obj);
}
}
  下面我们就本例中一些注意点提出解释。如果读者在某一方面遇到了问题,可以按下面的办法去修改。

  问题一:生成什么类的头文件和存根文件?

  这个例子中我们得到两个.class文件:objectdemoMain.class 和ObjectDemo.class。其中,第一个类中未声明native方法,因此可以不生成存根文件(生成也可以,见例13.12)。但两个类对应的头文件都必须生成,见例13.13。

  例13.12 存根文件ObjectDemoMain.c和ObjectDemo.c。

  //ObjectDemoMain.c
  1:/* DO NOT EDIT THIS FILE - it is machine generated */
  2:#include <StubPreamble.h>
  3:
  4:/* Stubs for class ObjectDemoMain*/

  //ObjectDemo.c

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <StubPreamble.h>

/* Stubs for class ObjectDemo*/
/*SYMBOL:"ObjectDemo/display(LObjectDemoMainV",Java_ObjectDemo_display_stub */
__declspec(dllexport) stack_item *Java_ObjectDemo_display_stub(stack_item *_P_,struct execenv *__EE_){
extern void ObjectDemo_display(void *,void *);
(void)ObjectDemo_display(_P_[0].p,((_P_[1].p)));
return _P_;
}
例13.13 头文件ObjectDemoMain.h和objectDemo.h。

  //ObjectDemoMain.h

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

#ifndef _Included_ObjectDemoMain
#define _Included_ObjectDemoMain
struct Hjava_lang_String;

typedef sturct ClassObjectDemoMain{
struct Hjava_lang_String *message;
}ClassObjectDemoMain;
HandleTo(ObjectDemoMain);

#ifdef __cplusplus
extern "C"{
#endif
#ifdef __cplusplus
}
#endif
#endif
  //ObjectDemo.h

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

#ifndef _Included_ObjectDemo
#define _Included_ObjectDemo

#pragma pach(4)

typdef struct ClassObjectDemo{
char PAD; /* ANSI C requires structures to have a least one member */
}ClassObjectDemo;
HandleTo(ObjectDemo);

#pragma pack()

#ifdef __cplusplus
extern "C"{
#endif
sturct HObjectDemoMain;
extern void ObjectDemo_display(struct HObjectDemo*,struct HObjectDemoMain*);
#ifdef __cplusplus
}
#endif
#endif

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
57#
 楼主| 发表于 2006-6-28 05:01 | 只看该作者
  问题二:如何引用对象参数的值?

  先请看一下native方法display()的实现文件(例13.14)。

  例13.14 ObjectImp.c

#include <stdio.h>
#include "StubPreamble.h"
#include "ObjectDemo.h"
#include "ObjectDemoMain.h"

void ObjectDemo_display(struct HObjectDemo *this,stuct HObjectDemoMain* ptr){
printf("The message is %S\n",unhand(unhand(unhand(ptr)->message)->value)->body);
printf("And the length is %d",unhand(unhand(ptr)->message)->count);
}

  看一下例子中第7行。用了三个“unhand”。如果读者在取值上出现问题,那么说明读者没有查找到相应的结构定义。

  下面让我们重复一下设法取message内容和长度的过程。

  从ObjectDemoMain.h中找到(第9行~13行)HObjectDemoMain的定义。知道message的类型转换后变为指向结构Hjava_lang_String的指针。

  从<Java安装目录>\include\java_lang_String.h文件中找到(第8~14行):

  typedef struct Classjava_lang_String{
    sturct HArrayOfChar *value;
    long offset;
    long count;
  /* Inaccessible static:InternSet*/
  }Classjava_lang_String;
  HandleTo(java_lang_String);

于是知道字符串值放在value指向的“HArrayOfChar”结构中,长度(count)为long型。

  从oobj.h中找到ArrayOfChar的定义(第122~124):

  typedef struct{
   unicode body[1];
  }ArrayOfChar;

于是可知用body访问ArrayOfchar中存放的字符数组元素。

  由此,例13.14中第7行三个unhand的来历便解释清楚了:最内层的定位到结构ClassObjectDemoMain;第二个unhand定位到结构Classjava_laong_String,最外层一个定位到结构ArrayOfChar。可见,看似简单的一个字符串却要经过许多步骤才能访问到。

  如果已经做了上面的“定位”工作后程序仍然运行不正常,请留意看一下ObjectImp.c的第7行打印语句中的格式标识:是“%S”而非“%s”。这是因为后者是用来打印单字节字符的,用它来打印是达不到预期效果的。

  另外要提醒的是,例13.13中的两个头文件都必须在实现文件中包括。否则,有些结构定义就找不到了。

  问题三:用哪些文件生成动态链接库?

  答案是:ObjectDemoMain.c是可选的,加不加都可以。而ObjectDemo.c与ObjectImp.c是必要的。为了简单起见,不妨把他们都加进动态链接库。

  下面是动态链接库的生成过程和运行结果。

  生成动态链接库objdll.dll的过程如下:

  C:\bookDemo\ch13>cl ObjectDemo.c ObjectImp.c -Feobjdll.dll -MD -LD javai.lib
  Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 10.20.6166 for 80x86
  Copyright (C) Microsoft Corp 1984-1996. All rights reserved.

  ObjectDemo.c
  ObjectImp.c
  Generating Code...
  Microsoft (R) 32-Bit Incremental Linker Version 4.20.6164
  Copyright (C) Microsoft Corp 1992-1966. All rights reserved.

  /outbjdll.dll
  /dll
  /implibbjdll.lib
  ObjectDemo.obj
  ObjectImp.obj
  javai.lib
    Creating library.lib and object objdll.exp

  ObjectDemoMain的运行结果如下:
  C:\bookDemo\ch13>java ObjectDemoMain
  The message is Can work?
  And the length is 12
  C:\bookDemo\ch13>

  读者不妨按照上面介绍的几个问题对照一下自己的实践。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
58#
 楼主| 发表于 2006-6-28 05:01 | 只看该作者
 13.4 新的原生方法接口——JNI (以下尚未校对)
  Java风行后,数家生产商如Netscapt,Microsoft都提出了自己的原生方法接口方案。SUN公司为了统一各种方案,提出了一套完整的原生方法接口规范,并在1.1版本的JDK中增加了相关功能。新的原生方法接口比原来的效率更高,功能也较强。这一节将介绍新的接口,并给出前文中三个例子的新版本。

  13.4.1生成动态链接库的步骤
  新方法中生成动态链接库的步骤与旧方法类似,但略徽简单一点。我们仍以节13.2中的例子DispDemo为例为说明。

  (1)创建Java源文件和.class文件

  这一步的工作与旧方法相同。我们得到的DispDemo.java和DispDemoMain.java也是一样的。用javac把它们编译成.class文件。

  (2)生成头文件

  这一步用javah生成一个JNI风格的头文件。新方法中不再需要存根文件。具体的做法是:注意命令行参数的变化。

  由此可以得到如下头文件(例13.15)。

  例13.15 JNI风格的DispDemo.h。

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

#ifndef _Included _DispDemo
#define _Included _DispDemo
#ifdef __cplusplus
extern "C"{
#endif
/*
* ClassispDemo
* Method:display
* Signature)V
*/
JNIEXPORT void JNICALL Java _DispDemo _display
(JNIEnv *,jobject);

#ifdef __cplusplus
}
#endif
#endif
  注意这个文件的第15、16行,它说明了实现文件中应编写的方法的原型。

  (3)创建C源文件

  这一步用C语言编写方法的具体实现。我们这个例子的功能很简单,只要编写一个很短的函数。源码见例13.16。

  例13.16 DispImp.c。

#include <stdo.h>
#include <jni.h>
#include "DispDemo.h"
JNIEXPORT void JNICALL
Java _DispDemo _display(JNIEnv *env,jobject obj)
{
printf("Native method exceuting...\n";
}
  由示例可见,程序由引用的头文件与老方法不同,用“jni.h”代替了“StubPreamble.h”。总的说来,程序中需要引用的头文件有三类:jni.h,第三步中生成的头文件,以及实现功能所需的头文件。另外,函数原型也有变化。编程时,按头文件中的方法原型来编写即可。

  (4)生成动态链接库(共享库)

  对WIN95/NT平台,可以用下面的命令来生成例子所需的动态链接库:

  cl -Ic:\java\include -Ic:\java\include\win32 -LD DispImp.c -Fedispdll.dll或者,先设定路径,再编译。如先用一个批文件native.bat设定所有路径:

  C:\bookDemo\ch13>native

  C:\bookDemo\ch13>set path=c:\msdev\bin;c:\jdk1.1.1\bin

  C:\bookDemo\ch13>set include=c:\msdev\include;c:\msdev\mfc\include;

  c:\jdk1.1.1\include;c:\jdk1.1.1\include\win32;

  C:\bookDemo\ch13>set lib=c:\msdev\lib;c:\msdev\mfc\lib;c:\jdk1.1.1\lib

  C:\bookDemo\ch13>

  再编译链接:

  C:\bookDemo\ch13>cl -LD DispImp.c -Fedispdll.dll

  Microsoft (R) 32 -bit C/C++ Optimizing Compiler Version 10.20.6166 for 80x86

  Copyright (C) Microsoft Corp 1984-1996. All rights reserved.

  DispImp.c

  Microsoft (R) 32-Bit Incremental Linker Version 4.20.6164

  Copyright (C) Microsoft Corp 1992-1996. All rights reserved.

  /dll

  /implib:dispdll.lib

  /out:dispdll.dll

  DispImp.obj

  Creating library dispdll.lib and object dispdll.exp

  就得到了我们需要的动态链接库。

  类似地,在Solaris系统中,用下面的命令生成共享库:

  cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris DispImp.c -o libDispdll.so

  (5)装入动态链接库,调用native方法

  用java解释器运行程序,得:

  C:\bookDemo\ch13>java DispDemoMain

  Native method executing...

  C:\bookDemo\ch13>

  整个过程就完成了。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
59#
 楼主| 发表于 2006-6-28 05:02 | 只看该作者
13.4.2 传参与数组对象访问
  本节给出例子ListDemo的新版本,并藉此说明原生方法中参数的传递和数组元素的访问。

  1、类型转换

  JNI规范规定了类型间的对应关系,表13.2说明了基本类型的转换关系。

  表13.2 JNI中基本类型的转换

  表13.2 JNI中基本类型的转换

Java语言中的类型 原生方法中的类型 描述
boolean jboolean 无符号,8位
byte jbyte 有符号,8位
char jchar 无符号,16位
short jshort 有符号,16位
int  jint 有符号,32位
long jlong 有符号,64位
float jfloat 32位
double jdouble 64位
void void   

  对于类和数组,原生方法也有类似的转换规则,表13.3给出其对应关系。

  表13.3 Java语言与原生方法中类与数组的类型转换

原生方法中类型 对应Java语言中的对象
jobject 所有Java对象
jclass java.lang.Class对象
jstring java.lang.String对象
jarray 数组
jobjectArray 对象数组
jbooleanArray 布尔数组
jbyteArray 字节数组
jcharArray 字符数组
jshortArray 短整数数组
jintArray 整数数组
jlongArray 长整数数组
jfloatArray 浮点数数组
jdoubleArray 双精度浮点数数组
jthrowable java.lang.Throwable对象

  下面结合例子ListDemo,讲解类型转换与数组元素的访问。

  该例的Java源文件几乎可以沿用旧的,但由于在新API中,DataInputStream类的readLine()方法已经过时,故在ListDemo.java中改用了BufferedReader类。另一个程序ListDemoMain.java与前面给出的相同。这两个程序没有什么新内容,这里不再解释。

  例13.17 ListDemo.java。

import java.io.*;

public class ListDemo{
public native void createList(int[] ints);
public native void showList();
static{
System.loadLibrary("listdll";
}
static int intArray[]={0,0,0,0,0};
public void run() throws IOException{
Bufferedreader bufReader=
new BufferedReader(new InputStreamreader(system.in));
System.out.println("Enter 5 integers:";
for(int i=0;i<5;i++)
intArray=Integer.valueOf(bufReader.readLine()).intValue();
createList(intArray);
showList();
}
}
  按照节13.4.1中的做法,生成头文件如下(例13.18)。

例13.18 ListDemo.h

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

#ifndef _Included _ListDemo
#define _Included _ListDemo
#ifdef __cplusplus
extern "C"{
#endif
/*
*Class:ListDemo
*Method:createList
*Signature[I)V
*/
JNIEXPORT void JNICALL Java_ListDemo_createList
(JNIEnv *,jobject,jintArray);

/*
* Class:ListDemo
* Method:showList
* Signature)V
*/
JNIEXPORT void JNICALL Java_ListDemo_showList
(JNIEnv *,jobject);

#ifdef __cplusplus
}
#endif
#endif
  程序的15、16和23、24行给出了两个方法的原型,其参数符合类型转换的规则。

  头文件中还有一处难理解的地方:13行和21行的注释中有一个新名词“Signature”,这是什么意思呢?原来,JNI中用了Java虚拟机中对类型的表示。所谓“Signature”的意思是“签名”,读者可以把它理解为对类型、类甚至方法的类型的表示。由于本例用不到它,我们把它搁置起来,到下一节再讲。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
60#
 楼主| 发表于 2006-6-28 05:02 | 只看该作者
  下面继续看ListDemo示例。例13.19是原生方法的实现程序。

  例13.19 ListImp.c。

1: #include <jni.h>

2: #include "Listdemo.h"

3: #include <stdio.h>

4: struct Nodes{

5: jint value;

6: struct Nodes *next;

7: };

8: static struct Nodes *List;

9:




10: JNIEXPORT void JNICALL Java_ListDemo_createList

11: (JNIEnv *env,jobject obj,jintArray Array){

12: int i;

//得到数组长度

13: jsize len=(*env)->GetArrayLength(env,Array);

//得到数组指针

14: jint *p=(*env)->GetIntArrayElements(env,array,0);

15: struct Nodes *temp;

16: temp=(struct Nodes *)malloc(sizeof(struct Nodes));

//取数组头元素

17: temp->value=p[0];

18: temp->next=NULL;

19: List=temp;

//取数组的元素

20: for(i=1;i<len;i++){

21: temp->text=(struct Nodes*)malloc(sizeof(struct Nodes));

22: temp=temp->next;

23: temp->value=p;

24: temp->next=NULL;

25: }

//释放数组指针

26: (*env)->ReleaseIntArrayElements(env,Array,p,0);

27: }

28: JNIEXPORT void JNICALL

29: Java_ListDemo_showList(JINEnv *env,jobject obj)

30: {

31: struct Nodes *tmp;

32: tmp=List;

33: printf("The content of list:";

34: while(tmp!=NULL){

35: printf("%4d",tmp->value);

36: tmp=tmp->next;

37: }

38: }

  对比例13.10中的程序,我们可以发现以下几点:

  (1)程序引用了jni.h,取代了原先的StubPreamble.h。

  (2)函数的原型有变化(这两点在节13.4.1中已述及)。

  (3)取数组元素的方法有变化。没有使用unhand操作,代之以JNI提供的函数。

  让我们把注意力集中在第三点上。

  例子对数组的操作使用了三个函数。它们是:

  13:jsize len = (*env)->GetArrayLength(env,Array);

  14: jint *p=(*env)->GetIntarrayElements(env,Array,0);

  26*env)->ReleaseIntArrayelements(env,array,p,0);

  它们的原型分别为:

  ☆jsize GetArrayLength(JINEnv *env,jarray array);

  ☆<指向数组元素的指针>Get<Java基本类型>ArrayElements(JNIEnv *env,jarray array,jboolean *isCopy);

  ☆void Release <Java基本类型>ArrayElements(JNIEnv *env,jarray array,<指向数组元素的指针>elems,jint mode);

  下面分别讲解。

  ☆jsize GetArrayLength(JNIEnv *env,jarray array);

  返回数组元素的个数。env是JNI接口指针,array是数组对象。返回值类型jsize实际是一种无符号整数,取值为地址空间的大小范围。

  ☆<指向数组元素的指针>Get<Java基本类型>ArrayElements(JNIEnv *env,jarray array,jboolean *isCopy);

  这实际上是一族函数,功能是取基本类型(表13.2中的类型,但不包括void)的数组元素,返回值是一个数组(或者说是指针)。从这个原型可以得到诸如GetBooleanArrayElements()、GetShortArrayElements()等函数。参数中的isCopy表明返回的数组指针是指向数据的真正存区(值为1时)还是指向拷贝了数组元素值的另一存区(值为0)。

  ☆void Release<Java基本类型>ArrayElements(JNIEnv *env,jarray array,<指向数组元素的指针>elems,jint mode);

  该函数与上一函数可以说是对应的。它完成的功能是释放资源和数据更新。由于Javar的垃圾收集具有可能改变内存中对象的位置,如不采取必要措施,被访问的数组指针就可能不再指向正确的存区。因此,对于数组,要么把它“钉”在固定的存区,要么把它拷贝至固定的存区,总之在访问它的期间要使数组元素总在原地。作完操作之后,再调用这个函数,解除对它的固定。另外,该函数还反对数组元素的所有屐按程序要求进行更新(在调用这个函数之前,所有更新都没有作用在数组本身上)。参数mode就是决定更新与否的。它取0时,更新数组并释放elems;取JNI——COMMIT时,更新但不释放elems;取JNI——ABORT时,释放elems,不作更新。

  对数组的操作是多种多样的。如果数组元素是对象,用上面的函数就不行了,而要用

  jobject GetObjectArrayElement(JNIEnv *env,jarray array,jsize index);

得到一个给定元素(index是下标)。如果要改变一个对象数组元素的值,就要用

  void SetObjectArrayelement(JNIEnv *env,jarray array,jsize index,jobject value);

如果要创建对象数组,用

  jarray NewObjectArray(JNIEnv *env,jsize length,jclass elementClass,jobject initialElement);

  其中elementClass是对象的类,initialElement是所有数组元素的初值。创建失败返回NULL。

创建基本类型的数组,用

  jarray New<java基本类型>Array(JNIEnv *env,jsize length);

  上面讲了访问整个数组的做法,如果数组很大,只需读一个相对较小的数组段,还可以用下面的函数:

  void Get<Java基本类型>ArrayRegion(JNIEnv *env,jarray array,jsize start,jsizelen,NativeType *buf);

  其中start指明起始位置,len是长度,buf是得到的缓冲区指针,可用于操作数组段中的元素。对应地,有函数

  void Set<Java基本类型>ArrayRegion(JNIEnv *env,jarray array,jsize start,jsize len,NativeType *buf);

把缓冲区中的内容拷贝回原数组。

  对数组的操作已经介绍完了。最后要注意的是上述函数往往会抛出越界异常,在编程时要加以注意。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表