利用Java配合BCB 4.0制作CPU特征侦测器

  █前言
  笔者最近从事一个利用Java来发展密码模块的工作,由于利用纯Java语言所制作出来的密码模块效率实在不好,最后我们把脑筋动到JNI(Java Native Interface)上.为何会想到使用JNI呢?大家应该都知道Java程序的执行必须透过Java Virtual Machine,透过一层中介的结果,执行的效率必然比C/C++所编译出来的原生码(native code,即专属各处理器的指令集)还要慢.事实上,在JDK内附的java.math这个package里头,许多部分也都应用了JNI来加快运算速度(例如Big Integer运算).
  █硬件优势
  一旦利用的JNI,代表我们将能够连结C/C++或是Assembly所撰写的加密模块.为了加快密码模块的performance,必须运用一些硬件上的优势,例如说当处理区块加密运算时,如果能运用一点并行处理的观念,就能够适当地加快运算速度.以目前的PC上的处理器来说,支持平行处理能力的技术就属大家所熟知的MMX,Streaming SIMD Extension(大家也许比较熟悉的是KNI这个名词),以及3DNOW!.这些技术其实就是实做了SIMD(Single-Instruction,Multiple-Data)的概念,允许处理器在同一时间之内,使用单一指令,就可以同时处理好几组数据.
  另外,在Pentium等级以上的CPU具有利用Pipeline来加快执行速度的能力,只要调整Assembly code的排列顺序,使其符合Intel Scheduling Rules,就可以充分利用CPU里头的U-pipe与V-pipe,加快执行速度.其实,就笔者所使用的Visual C++6.0与Borland C++Builder 4.0来说,虽然都有编译器指令可以针对处理器做最佳化,但是如果您亲自去看看编译出来的结果,能然有很多地方无法尽如人意,因此如果遇到time critical的部分,仍然常常需要我们亲自去调校以改善performance.
  █准备工作及注意事项
  Ok,讲到这边,似乎离主题有点远了,让我们回归正题吧!
  为了让我们可以在执行时期动态地依照CPU的能力来执行最佳化的程序代码,首要的工作就是要写一些函式来侦测CPU的特性,于是笔者选择了JDK 1.2以及BCB 4.0来完成整个由
  Java codeèJNIèPlatform native code
  的完整测试程序.
  如果以图片来表示就如下图
  如果您抓取了笔者提供的原始码,应该可以看到下面三个分别由Java与C++撰写的程序模块:
  CPUTestDll.bpr CPUTestDll.cpp
  实做侦测CPU特性相关函式的模块.
  此为BCB 4.0之项目文件,使用Project/make cputest的指令后,所产生的结果cputest.dll,是我们所要的.
  编译注意事项:
  由于在CPUTestDll.cpp里头我们用到汇编语言指令CPUID,所以请打开Project/Option里头的Advanced Compiler次页,里头有一个叫Instruction Set的地方,请勾选Pentium,否则编译器会因为不支持此指令而产生编译错误.如果您要把编译过的结果给别人使用,建议您将Project/Option/Package次页中的Build with Run-time Package选项以及Linker次页中的Use Dynamic RTL选项通通取消掉.
  请打开Project/Option/Directories Conditionals次页,将JDK所在目录\include
  与JDK所在目录\include\win32加到Include path里头;另外也在Library Path中加入JDK所在目录\lib,否则会造成编译错误.
  CPUTest.java
  这个Java程序是作为其它Java程序透过JNI以呼叫CPUTestDll.dll的接口.笔者把这个接口宣告于my.cpu这个package底下.
  编译注意事项:
  编译Java程序时,请设定环境变量PATH与CLASSPATH
  例如JDK安装在C:\JDK1.2这个目录,而此档案放在c:\jdk1.2\my之下,
  那幺请在提示符号下命令
  PATH c:\jdk1.2\bin
  set CLASSPATH=C:\jdk1.2\classes;c:\JDK1.2\my
  test.java
  这个Java程序将利用CPUTest对象当作接口,来呼叫实做于CPUTestDll.dll内的CPU特征侦测函数
  编译注意事项:
  除了2的注意事项外,请将CPUTest.java放到<JDK安装目录>\classes\my\cpu这个目录之中,否则编译将无法通过.
  █参考文件
  1.JDK 1.2 on-line document
  2.Intel Architecture Optimization/Reference manual Order Number:245127-001
  3.AMD 3DNOW!Technology Manual
  █用JDK实做JNI接口
  首先,为了让所有的Java Code都可以使用我们的CPU特征侦测函数,我们首先必须先制作一个接口类别:
  档案列表CPUTest.java
  /*********************************************************************************
  CPUTest.java
  JNI接口对象
  1999 April 20 by王森
  **********************************************************************************/
  //加入my.cpu这个package之中
  package my.cpu;
  public class CPUTest{
  /*以下定义每种处理器所代表的常数*/
  static public final int i386=0;//不支持CPUID的处理器(可辨识)
  static public final int Pentium=1;//最早期的Pentium处理器(可辨识)
  static public final int Pentium_M=2;//Pentium with MMX处理器(可辨识)
  static public final int Pentium_2=3;//Pentium II处理器(可辨识)
  static public final int Pentium_3=4;//Pentium III处理器(可辨识)
  static public final int Pentium_P=5;//Pentium Pro处理器(可辨识)
  static public final int K6=11;//同Pentium with MMX
  static public final int K6_2=12;//K6-2处理器((可辨识)
  static public final int K6_3=13;//同K6-2
  /*以下定义所有会藉由JNI来叫用的函式*/
  //测试CPU是否支持CPUID指令,如果支持则传回true,否则传回false
  public native boolean CheckCPUID();
  ^^^^^^注意,所有的JNI函式都必须在函式宣告里加上native这个修饰字
  //辨识处理器是否支持MMX,如果支持则传回true,否则传回false
  public native boolean CheckMMX();
  //辨识处理器是否支持Stream SIMD Extension(即KNI),如果支持则传回true,否则传回false
  public native boolean CheckSSIMD();
  //辨识处理器是否支持AMD 3DNow,如果支持则传回true,否则传回false
  public native boolean Check3DNOW();
  //辨识CPU的等级,并传回一个整数代表CPU的等级
  public native int CheckCPUTYPE();
  //印出CPU的相关信息
  public native void PrintCPUInfo();
  note:使用此函数之前,请先呼叫前面的所有函式,因为前面的函式,除了传回真伪之外,也会设定DLL文件之中的全域变量而PrintCPUInfo会利用这些全域变量来做判定的工作.
  static{
  我们把实做CPU侦测函式的模块做成DLL(动态连结函式库)檔,
  取名叫CPUDTestDll.dll,所以在这里要加载此DLL
  System.loadLibrary("CPUTestDll");
  }
  }
  接着我们在提示符号下使用指令
  javac CPUTest.java
  编译此档案,会产生CPUTest.class这个档案.然后我们把这两个档案都移至
  <JDK安装目录>/classes/my/cpu/
  这个目录底下,如果没有做此动作,恐怕下面的步骤都会遇到一些错误.
  最后一个步骤,就是必须产生一个引入档(Include file),我们将会在编译CPUTestDll.dll实用到这个引入档.
  在提示符号下使用指令
  javah my.cpu.CPUTest
  就会在您目前的工作目录下看到
  my_cpu_CPUTest.h
  到此为止,我们已经完成了第一个阶段.
  案列表my_cpu_CPUTest.h
  /*DO NOT EDIT THIS FILE-it is machine generated*/
  #include<jni.h>
  /*Header for class my_cpu_CPUTest*/
  #ifndef _Included_my_cpu_CPUTest
  #define _Included_my_cpu_CPUTest
  #ifdef __cplusplus
  extern"C"{
  #endif
  #undef my_cpu_CPUTest_i386
  #define my_cpu_CPUTest_i386 0L
  #undef my_cpu_CPUTest_Pentium
  #define my_cpu_CPUTest_Pentium 1L
  #undef my_cpu_CPUTest_Pentium_M
  #define my_cpu_CPUTest_Pentium_M 2L
  #undef my_cpu_CPUTest_Pentium_2
  #define my_cpu_CPUTest_Pentium_2 3L
  #undef my_cpu_CPUTest_Pentium_3
  #define my_cpu_CPUTest_Pentium_3 4L
  #undef my_cpu_CPUTest_Pentium_P
  #define my_cpu_CPUTest_Pentium_P 5L
  #undef my_cpu_CPUTest_K6
  #define my_cpu_CPUTest_K6 11L
  #undef my_cpu_CPUTest_K6_2
  #define my_cpu_CPUTest_K6_2 12L
  #undef my_cpu_CPUTest_K6_3
  #define my_cpu_CPUTest_K6_3 13L
  /*
  *Class:my_cpu_CPUTest
  *Method:Check3DNOW
  *Signature:()Z
  */
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_Check3DNOW
  (JNIEnv*,jobject);
  /*
  *Class:my_cpu_CPUTest
  *Method:CheckCPUID
  *Signature:()Z
  */
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckCPUID
  (JNIEnv*,jobject);
  /*
  *Class:my_cpu_CPUTest
  *Method:CheckCPUTYPE
  *Signature:()I
  */
  JNIEXPORT jint JNICALL Java_my_cpu_CPUTest_CheckCPUTYPE
  (JNIEnv*,jobject);
  /*
  *Class:my_cpu_CPUTest
  *Method:CheckMMX
  *Signature:()Z
  */
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckMMX
  (JNIEnv*,jobject);
  /*
  *Class:my_cpu_CPUTest
  *Method:CheckSSIMD
  *Signature:()Z
  */
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckSSIMD
  (JNIEnv*,jobject);
  /*
  *Class:my_cpu_CPUTest
  *Method:PrintCPUInfo
  *Signature:()V
  */
  JNIEXPORT void JNICALL Java_my_cpu_CPUTest_PrintCPUInfo
  (JNIEnv*,jobject);
  #ifdef __cplusplus
  }
  #endif
  #endif
  附带一提,上面这个档案完全是使用javah这个JDK内附的程序所产生,我们不要去修改它,以免发生更多意想不到的麻烦.
  █C++Builder 4.0实做native端
  好,完成了接口部分,接下来就要开始实做部分啦!首先,请先打开您的BCB 4.0,选择File/New里头的New次页中,选择开启一个DLL项目档.开启成功后,请将此项目的档名取名为CPUTestDll.您就会在目录下看到CPUTestDll.bpr与CPUTestDll.cpp两个档案,另外,为了让编译工作顺利,您必须再调校一些编译器选项,请参照前面█准备工作及注意事项.
  首先,请在CPUTestDll.cpp里头将my_cpu_CPUTest.h这个档案引入
  #include"my_cpu_CPUTest.h"
  否则会产生”Undefined Symbol xxxx”的错误
  完整程序代码如下:
  档案列表CPUTestDll.cpp
  #include<vcl.h>
  #pragma hdrstop
  #include<iostream.h>
  #include"my_cpu_CPUTest.h"
  /*底下定义一些代表CPU的变量*/
  const int i386=0;//不支持CPUID的处理器(可辨识)
  const int Pentium=1;//最早期的Pentium处理器(可辨识)
  const int Pentium_M=2;//Pentium with MMX处理器(可辨识)
  const int Pentium_2=3;//Pentium II处理器(可辨识)
  const int Pentium_3=4;//Pentium III处理器(可辨识)
  const int Pentium_P=5;//Pentium Pro处理器(可辨识)
  const int K6=11;//同Pentium with MMX
  const int K6_2=12;//K6-2处理器((可辨识)
  const int K6_3=13;//同K6-2
  /*****************************************************/
  /*以下定义一些辨识CPU能力的变量*/
  bool CPUID_S=false;//测试是否支持CPUID指令
  bool MMX=false;//测试是否支持MMX
  bool SSIMD=false;//测试是否支持Streaming SIMD Extension
  bool _3DNOW=false;//测试是否支持3D!NOW
  int CPUTYPE=i386;//CPU的型态,初始值为i386
  /*****************************************************/
  下面我们将开始介绍这些原生函式的实做方式,但是有些观念我们必须要先知道.首先,在下面的函式里头,我们利用BCB内嵌汇编语言来实做,虽然我们也可以利用Win32 API或其它方法来取得系统信息和硬件信息,可是就复杂度来说,实在不如用汇编语言来的那幺简洁.(网络上常有人在争论汇编语言跟高级语言的优劣,甚至认为汇编语言已经没有存在的必要,不过笔者还是觉得做什幺事就用最适合的语言会比较好).
  第二,在这些inline assembly code里面,我们大量地利用了CPUID这个汇编语言指令.这是一个辨识CPU相当好用的指令,除了辨识一些CPU的特殊能力,也可以提供一些厂方信息.大家可以翻阅Intel的Instruction set reference来看看这个指令的用法.不过呢,这个指令只有在Pentium等级CPU中才提供,换句话说,486,386上这个指令应该无效,严格地说来,即使执行文件在486以下的计算机执行这段程序,应该是没有问题才对.可是大家应该还记得去年在Intel的CPU中一些don’t care的指令集竟然会造成计算机当机的错误吧!所以我想在使用这个指令以前,应该先看看CPU是否支持这个指令,如果不支持,就不要再做下去,以免发生不可预期的错误.因此我们在使用CheckMMX,CheckSSIMD,Check3DNOW,这些函式以前,请务必先执行CheckCPUID这个函式.这个函式会去变更全域变量CPUID_S,因此不论是CheckMMX,CheckSSIMD,Check3DNOW,都会在使用CPUID指令前先检查这个全域变量,如果是false,就不再继续动作下去,以免发生非预期的错误.
  最后一点,就是当C++Builder在编译内嵌组和语言的程序代码时,会在程序目录中产生CPUTestDll.asm这个中间档,请将Project/Option里头的Advanced Compiler次页,里头有一个叫Instruction Set的地方,请勾选Pentium,否则不管您勾选386或是486,编译器会因为不支持此指令而产生组译错误.
  /*以下开始实做所有的JNI函式*/
  /*辨识处理器是否支持AMD 3DNow*/
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_Check3DNOW(JNIEnv*J,jobject O)
  {
  //如果不支持CPUID指令,就不必再做下去以免发生错误
  if(CPUID_S==false)
  return false;
  unsigned long temp;
  asm mov eax,80000001h;
  asm cpuid;
  asm mov temp,edx;
  //第31个bit为3D!NOW的特征值
  if(temp&0x80000000)
  {
  _3DNOW=true;
  return true;
  }
  return false;
  }
  /*测试CPU是否支持CPUID指令*/
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckCPUID(JNIEnv*J,jobject O)
  {
  //以下程序用来测试CPU是否支持CPUID指令
  unsigned int A,B;
  asm pushfd;
  asm pop eax;
  asm mov ebx,eax;
  asm xor eax,00200000h;
  asm push eax;
  asm popfd;
  asm pushfd;
  asm pop eax;
  asm mov A,eax;
  asm mov B,ebx;
  if(A!=B)
  {
  CPUID_S=true;
  return true;
  }
  return false;
  }
  /*辨识CPU的等级*/
  JNIEXPORT jint JNICALL Java_my_cpu_CPUTest_CheckCPUTYPE(JNIEnv*J,jobject O)
  {
  //如果不支持CPUID指令,就不必再做下去以免发生错误
  if(CPUID_S==false)
  return i386;
  unsigned int temp;
  asm mov eax,0;
  asm cpuid;
  asm mov temp,eax;
  if(temp==2)//这是P6家族的情形
  {
  CPUTYPE=Pentium_P;//P6家族的第一颗processor为Pentium Pro
  if(SSIMD)//P6然后又支持SSIMD..一定是Pentium III
  {
  CPUTYPE=Pentium_3;
  return Pentium_3;
  }
  if(MMX)//否则P6然后又支持MMX..一定是Pentium II
  {
  CPUTYPE=Pentium_2;
  if(_3DNOW)//支持MMX又支持3D!NOW,一定是K6-2
  {
  CPUTYPE=K6_2;
  return K6_2;
  }
  return Pentium_2;
  }
  //如果都没有支持以上这些多媒体指令集,那幺应该是Pentium Pro了
  return Pentium_P;
  }
  if(temp==1)//这是P5家族的情形
  {
  CPUTYPE=Pentium;//P5家族的第一颗processor为Pentium
  if(MMX)//P5然后又支持MMX..一定是Pentium with MMX
  {
  CPUTYPE=Pentium_M;
  return Pentium_M;
  }
  return Pentium;
  }
  return i386;
  }
  /*辨识处理器是否支持MMX*/
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckMMX(JNIEnv*J,jobject O)
  {
  //如果不支持CPUID指令,就不必再做下去以免发生错误
  if(CPUID_S==false)
  return false;
  unsigned long temp;
  asm mov eax,1;
  asm cpuid;
  asm mov temp,edx;
  //第23个bit为MMX的特征值
  //p.s bit的编号由0~31
  if(temp&0x00800000)
  {
  MMX=true;
  return true;
  }
  return false;
  }
  /*辨识处理器是否支持Stream SIMD Extension(即KNI)*/
  JNIEXPORT jboolean JNICALL Java_my_cpu_CPUTest_CheckSSIMD(JNIEnv*J,jobject O)
  {
  //如果不支持CPUID指令,就不必再做下去以免发生错误
  if(CPUID_S==false)
  return false;
  unsigned long temp;
  asm mov eax,1;
  asm cpuid;
  asm mov temp,edx;
  //第25个bit为Streaming SIMD Extension的特征位
  //p.s bit的编号由0~31
  if(temp&0x02000000)
  {
  SSIMD=true;
  return true;
  }
  return false;
  }
  /*印出CPU的相关信息*/
  JNIEXPORT void JNICALL Java_my_cpu_CPUTest_PrintCPUInfo(JNIEnv*J,jobject O)
  {
  cout<<"...Verify Some Processor Information..."<<endl;
  cout<<"The Capacity of your Processor:"<<endl;
  if(MMX)
  {
  cout<<"Support Intel MMX Technology"<<endl;
  }else
  {
  cout<<"No Intel MMX Technology Support"<<endl;
  }
  if(SSIMD)
  {
  cout<<"Support Intel Streaming SIMD Extensions"<<endl;
  }else
  {
  cout<<"No Intel Streaming SIMD Extensions Support"<<endl;
  }
  if(_3DNOW)
  {
  cout<<"Support AMD 3D!NOW Technology"<<endl;
  }else
  {
  cout<<"No AMD 3D!NOW Technology Support"<<endl;
  }
  cout<<"CPU Type:";
  switch(CPUTYPE)
  {
  case i386:
  cout<<"General i386 Processor"<<endl;
  break;
  case Pentium:
  cout<<"Intel Pentium Processor"<<endl;
  break;
  case Pentium_M:
  cout<<"Intel Pentium with MMX Processor"<<endl;
  break;
  case Pentium_2:
  cout<<"Intel Pentium II Processor"<<endl;
  break;
  case Pentium_3:
  cout<<"Intel Pentium III Processor"<<endl;
  break;
  case Pentium_P:
  cout<<"Intel Pentium Pro Processor"<<endl;
  break;
  case K6:
  cout<<"AMD K6 Processor"<<endl;
  break;
  case K6_2:
  cout<<"AMD K6 II Processor"<<endl;
  break;
  case K6_3:
  cout<<"AMD K6 III Processor"<<endl;
  break;
  }
  cout<<"...Verify End..."<<endl;
  }
  int WINAPI DllEntryPoint(HINSTANCE hinst,unsigned long reason,void*)
  {
  return 1;
  }
  //---------------------------------------------------------------------------
  大家可能会发现每个函式在回传布尔值以前,都会先去设定一个DLL内部的全域变量,这是因为效率的考量,假如读者有兴趣扩充笔者的程序,让Java程序可以因为CPU的特征去呼叫适当的函式,一旦你遇到两次这种情形,您就必须呼叫CheckXXXX函式两次,十次这种情形,您就要呼叫十次,相当地没有效率,如果照笔者的实做法,只要呼叫一次,就会去设定纪录CPU特征的全域变量,接下来就不必再次呼叫CheckXXXX,直接存取全域变量就可以了.大家可以参考一下PrintCPUInfo函式,大致上可以知道为何笔者这样做的理由.
  还有就是CheckCPUTYPE这个函式的方法并非是最正规的方法,笔者只是假设市面上只有AMD与Intel两家厂商而已,如果要正确的辨认出厂商跟型号,我们还必须利用CPUID取得更多信息才行,这就当作给诸位读者一个练习的机会,相信只要读者有心,去找找各种CPU的System Programming Manual,一定可以找到正确的侦测方法.
  █测试
  终于到了要测试咱们程序正确性的时候了,所以我们撰写了test.java
  档案列表test.java
  import my.cpu.*;
  class test
  {
  /*以下定义每种处理器所代表的常数*/
  static public final int i386=0;//不支持CPUID的处理器(可辨识)
  static public final int Pentium=1;//最早期的Pentium处理器(可辨识)
  static public final int Pentium_M=2;//Pentium with MMX处理器(可辨识)
  static public final int Pentium_2=3;//Pentium II处理器(可辨识)
  static public final int Pentium_3=4;//Pentium III处理器(可辨识)
  static public final int Pentium_P=5;//Pentium Pro处理器(可辨识)
  static public final int K6=11;//同Pentium with MMX
  static public final int K6_2=12;//K6-2处理器((可辨识)
  static public final int K6_3=13;//同K6-2
  //主程序开始
  public static void main(String args[])
  {
  boolean temp;
  //取得JNI接口对象
  CPUTest my=new CPUTest();
  //开始CPU相关信息的初始化工作
  temp=my.CheckCPUID();
  if(temp)
  {
  System.out.println("CPUID support");
  }else
  {
  System.out.println("CPUID not support");
  }
  temp=my.CheckMMX();
  if(temp)
  {
  System.out.println("MMX support");
  }else
  {
  System.out.println("MMX not support");
  }
  temp=my.CheckSSIMD();
  if(temp)
  {
  System.out.println("SSIMD support");
  }else
  {
  System.out.println("SSIMD not support");
  }
  temp=my.Check3DNOW();
  if(temp)
  {
  System.out.println("3DNOW support");
  }else
  {
  System.out.println("3DNOW not support");
  }
  System.out.println("");
  System.out.println("---------Starting Java code Print--------");
  switch(my.CheckCPUTYPE())
  {
  case i386:
  System.out.println("i386");
  break;
  case Pentium:
  System.out.println("Pentium");
  break;
  case Pentium_M:
  System.out.println("Pentium with MMX");
  break;
  case Pentium_2:
  System.out.println("Pentium II");
  break;
  case Pentium_3:
  System.out.println("Pentium III");
  break;
  case Pentium_P:
  System.out.println("Pentium Pro");
  break;
  case K6:
  System.out.println("K6");
  break;
  case K6_2:
  System.out.println("k6-2");
  break;
  case K6_3:
  System.out.println("K6-3");
  break;
  }
  System.out.println("");
  System.out.println("---------Starting native code Print--------");
  my.PrintCPUInfo();
  }//end of main
  }//end of class
  在命令列下打入javac test.java,就可以产生test.class这个档案.接者请打java test来执行程序,不过首先您会先遇到下面的错误讯息:
  C:\jdk1.2\my>java test
  Exception in thread"main"java.lang.UnsatisfiedLinkError:no CPUTestDll in java
  .library.path
  at java.lang.ClassLoader.loadLibrary(Compiled Code)
  at java.lang.Runtime.loadLibrary0(Runtime.java:470)
  at java.lang.System.loadLibrary(System.java:745)
  at my.cpu.CPUTest.<clinit>(CPUTest.java:48)
  这是什幺原因呢?原来是因为Java Virtual Machine找不到CPUTestDll.dll,所以产生了执行时期例外.解决这个问题的方法有两种:第一种就是把CPUTestDll.dll拷贝到跟test.class同一个目录下.第二种方法就是下指令java-Djava.library.path=<DLL所在位置>test,例如:
  java-Djava.library.path=c:\jdk1.2\my\dll test代表CPUTestDll.dll是放置在c:\jdk1.2\my\dll底下.不论您用哪种方法,都可以看到下面的输出结果:
  C:\jdk1.2\my>java test
  CPUID support
  MMX support
  SSIMD not support
  3DNOW not support
  ---------Starting Java code Print--------
  Pentium II
  ---------Starting native code Print--------
  ...Verify Some Processor Information...
  The Capacity of your Processor:
  Support Intel MMX Technology
  No Intel Streaming SIMD Extensions Support
  No AMD 3D!NOW Technology Support
  CPU Type:Intel Pentium II Processor
  ...Verify End...
  以上是在笔者计算机上的执行情形.您的计算机上也成功地输出结果了吗?
  █结语
  其实在本文章中,只应用了JNI帮我们传递一些参数给动态连结函式库,再由动态连结函式库传回一些值给我们而已.其实,利用JNI也可以做到直接更改动态连结函式库里头的变量,动态连结函式库里头的函式也可以直接更改java程序中的变量,只不过本文章重点并非在JNI功能的介绍,所以并没有提及.另外关于CPU特征的侦测,我们只是简单地侦测几个CPU的特性,较复杂的还有侦测CPU的时脉等特征,在Intel的网站上我们也可以找到名为cpuinfo.zip的样本.期望这篇文章可以让目前正在使用java,又想充分利用处理器特性来做最佳化的朋友提供一个踏板.