Feeling

    三人行,必有我师焉

   ::  :: 新随笔 :: 联系 ::  :: 管理 ::
  185 随笔 :: 0 文章 :: 392 评论 :: 0 Trackbacks

2007年12月7日 #

     摘要: 很多网上下载的PDF文件都包含各种形式的水印,本文主要阐述如何使用易转换一键删除PDF文件中的各种图片水印和文字水印  阅读全文
posted @ 2021-03-09 20:29 三人行,必有我师焉 阅读(394) | 评论 (0)编辑 收藏

Spark源代码下载地址: http://spark.apache.org/downloads.html

下载后,直接用 Scala IDE 通过已存在的项目导入到Eclipse workspace中去,然后Eclipse会自动进行编译。第一次编译会报很多错误,不过总的来说,导致编译错误的源头有三个:
1、Scala编译器版本错误
2、Eclipse Maven插件不能自动识别spark project的一些pom,报Plugin execution not covered by lifecycle configuration异常
3、一些项目,maven会自动生成scala和java文件,但是这些自动生成的代码文件没有配置在eclipse项目的classpath里。

针对第一种错误,比较简单,对于每个scala项目,右键属性选择spark对应的scala编译器版本。



当然spark代码里的项目有几十个,只能手工一个个设置了,比较傻,没办法,还不停的弹出对话框,不停地回车吧。

编译的难点主要在第二种错误上,比如spark-sql项目的pom, 里面有个build-helper-maven-plugin,它下面的execution,eclipse maven插件无法识别,报Plugin execution not covered by lifecycle configuration异常,解决方案参见 https://www.eclipse.org/m2e/documentation/m2e-execution-not-covered.html,先使用 Eclipse quick-fix选项自动修复,忽略此 maven goal,Eclipse 会为 pom.xml自动添加一段xml代码,包含在 pluginManagement section中,里面有一段 <action><ignore/></action>,此处手动修改成
<action>
    <execute>
        <runOnIncremental>false</runOnIncremental>
    </execute>
</action>
然后右键 maven update project 就OK了。

一共有5个project需要修改pom,如图


修改pom后重新编译,依旧会报一些错误,这些错误都是由于maven自动生成的java和scala代码没有添加到classpath里导致的编译错误,只需要手工添加一下即可,需要手工添加项目有 spark-streaming-flume-sink 的 src_managed\main\compiled_avro 目录 和 spark-sql 项目的 test\gen-java 目录。

全部编译好以后的截图:


修改完以后,Spark代码全部编译下来大概耗时25分钟左右(CPU 双核 I7 4600)

原文地址:http://www.blogjava.net/cnfree/archive/2016/11/08/431965.html
posted @ 2016-11-08 13:12 三人行,必有我师焉 阅读(2232) | 评论 (0)编辑 收藏

  Spark简介

  Spark是整个BDAS的核心组件,是一个大数据分布式编程框架,不仅实现了MapReduce的算子map 函数和reduce函数及计算模型,还提供更为丰富的算子,如filter、join、groupByKey等。是一个用来实现快速而同用的集群计算的平台。

  Spark将分布式数据抽象为弹性分布式数据集(RDD),实现了应用任务调度、RPC、序列化和压缩,并为运行在其上的上层组件提供API。其底层采用Scala这种函数式语言书写而成,并且所提供的API深度借鉴Scala函数式的编程思想,提供与Scala类似的编程接口

  Sparkon Yarn

  

  从用户提交作业到作业运行结束整个运行期间的过程分析。

  一、客户端进行操作

  1. 根据yarnConf来初始化yarnClient,并启动yarnClient

  2. 创建客户端Application,并获取Application的ID,进一步判断集群中的资源是否满足executor和ApplicationMaster申请的资源,如果不满足则抛出IllegalArgumentException;

  3. 设置资源、环境变量:其中包括了设置Application的Staging目录、准备本地资源(jar文件、log4j.properties)、设置Application其中的环境变量、创建Container启动的Context等;

  4. 设置Application提交的Context,包括设置应用的名字、队列、AM的申请的Container、标记该作业的类型为Spark;

  5. 申请Memory,并最终通过yarnClient.submitApplication向ResourceManager提交该Application。

  当作业提交到YARN上之后,客户端就没事了,甚至在终端关掉那个进程也没事,因为整个作业运行在YARN集群上进行,运行的结果将会保存到HDFS或者日志中。

  二、提交到YARN集群,YARN操作

  1. 运行ApplicationMaster的run方法;

  2. 设置好相关的环境变量。

  3. 创建amClient,并启动;

  4. 在Spark UI启动之前设置Spark UI的AmIpFilter;

  5. 在startUserClass函数专门启动了一个线程(名称为Driver的线程)来启动用户提交的Application,也就是启动了Driver。在Driver中将会初始化SparkContext;

  6. 等待SparkContext初始化完成,最多等待spark.yarn.applicationMaster.waitTries次数(默认为10),如果等待了的次数超过了配置的,程序将会退出;否则用SparkContext初始化yarnAllocator;

  7. 当SparkContext、Driver初始化完成的时候,通过amClient向ResourceManager注册ApplicationMaster

  8. 分配并启动Executeors。在启动Executeors之前,先要通过yarnAllocator获取到numExecutors个Container,然后在Container中启动Executeors。

      那么这个Application将失败,将Application Status标明为FAILED,并将关闭SparkContext。其实,启动Executeors是通过ExecutorRunnable实现的,而ExecutorRunnable内部是启动CoarseGrainedExecutorBackend的。

  9. 最后,Task将在CoarseGrainedExecutorBackend里面运行,然后运行状况会通过Akka通知CoarseGrainedScheduler,直到作业运行完成。

  Spark节点的概念

  一、Spark驱动器是执行程序中的main()方法的进程。它执行用户编写的用来创建SparkContext(初始化)、创建RDD,以及运行RDD的转化操作和行动操作的代码。

  驱动器节点driver的职责:

  1. 把用户程序转为任务task(driver)

      Spark驱动器程序负责把用户程序转化为多个物理执行单元,这些单元也被称之为任务task(详解见备注)

  2. 为执行器节点调度任务(executor)

      有了物理计划之后,Spark驱动器在各个执行器节点进程间协调任务的调度。Spark驱动器程序会根据当前的执行器节点,把所有任务基于数据所在位置分配给合适的执行器进程。当执行任务时,执行器进程会把缓存的数据存储起来,而驱动器进程同样会跟踪这些缓存数据的位置,并利用这些位置信息来调度以后的任务,以尽量减少数据的网络传输。(就是所谓的移动计算,而不移动数据)。

  二、执行器节点

  作用:

  1. 负责运行组成Spark应用的任务,并将结果返回给驱动器进程;

  2. 通过自身的块管理器(blockManager)为用户程序中要求缓存的RDD提供内存式存储。RDD是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加快运算。

  驱动器的职责:

  所有的Spark程序都遵循同样的结构:程序从输入数据创建一系列RDD,再使用转化操作派生成新的RDD,最后使用行动操作手机或存储结果RDD,Spark程序其实是隐式地创建出了一个由操作组成的逻辑上的有向无环图DAG。当驱动器程序执行时,它会把这个逻辑图转为物理执行计划。

  这样 Spark就把逻辑计划转为一系列步骤(stage),而每个步骤又由多个任务组成。这些任务会被打包送到集群中。

  Spark初始化

  1. 每个Spark应用都由一个驱动器程序来发起集群上的各种并行操作。驱动器程序包含应用的main函数,并且定义了集群上的分布式数据集,以及对该分布式数据集应用了相关操作。

  2. 驱动器程序通过一个SparkContext对象来访问spark,这个对象代表对计算集群的一个连接。(比如在sparkshell启动时已经自动创建了一个SparkContext对象,是一个叫做SC的变量。(下图,查看变量sc)

      

  3. 一旦创建了sparkContext,就可以用它来创建RDD。比如调用sc.textFile()来创建一个代表文本中各行文本的RDD。(比如vallinesRDD = sc.textFile(“yangsy.text”),val spark = linesRDD.filter(line=>line.contains(“spark”),spark.count())

      执行这些操作,驱动器程序一般要管理多个执行器,就是我们所说的executor节点。

  4. 在初始化SparkContext的同时,加载sparkConf对象来加载集群的配置,从而创建sparkContext对象。

      从源码中可以看到,在启动thriftserver时,调用了spark- daemon.sh文件,该文件源码如左图,加载spark_home下的conf中的文件。

      

      (在执行后台代码时,需要首先创建conf对象,加载相应参数, val sparkConf = newSparkConf().setMaster("local").setAppName("cocapp").set("spark.executor.memory","1g"), val sc: SparkContext = new SparkContext(sparkConf))

  RDD工作原理:

  RDD(Resilient DistributedDatasets)[1] ,弹性分布式数据集,是分布式内存的一个抽象概念,RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,只能通过在其他RDD执行确定的转换操作(如map、join和group by)而创建,然而这些限制使得实现容错的开销很低。对开发者而言,RDD可以看作是Spark的一个对象,它本身运行于内存中,如读文件是一个RDD,对文件计算是一个RDD,结果集也是一个RDD ,不同的分片、数据之间的依赖、key-value类型的map数据都可以看做RDD。

  主要分为三部分:创建RDD对象,DAG调度器创建执行计划,Task调度器分配任务并调度Worker开始运行。

  SparkContext(RDD相关操作)→通过(提交作业)→(遍历RDD拆分stage→生成作业)DAGScheduler→通过(提交任务集)→任务调度管理(TaskScheduler)→通过(按照资源获取任务)→任务调度管理(TaskSetManager)

  Transformation返回值还是一个RDD。它使用了链式调用的设计模式,对一个RDD进行计算后,变换成另外一个RDD,然后这个RDD又可以进行另外一次转换。这个过程是分布式的。

  Action返回值不是一个RDD。它要么是一个Scala的普通集合,要么是一个值,要么是空,最终或返回到Driver程序,或把RDD写入到文件系统中

  转换(Transformations)(如:map, filter, groupBy, join等),Transformations操作是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations操作时只会记录需要这样的操作,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。

  操作(Actions)(如:count, collect, save等),Actions操作会返回结果或把RDD数据写到存储系统中。Actions是触发Spark启动计算的动因。

  它们本质区别是:Transformation返回值还是一个RDD。它使用了链式调用的设计模式,对一个RDD进行计算后,变换成另外一个RDD,然后这个RDD又可以进行另外一次转换。这个过程是分布式的。Action返回值不是一个RDD。它要么是一个Scala的普通集合,要么是一个值,要么是空,最终或返回到Driver程序,或把RDD写入到文件系统中。关于这两个动作,在Spark开发指南中会有就进一步的详细介绍,它们是基于Spark开发的核心。

  RDD基础

  1. Spark中的RDD就是一个不可变的分布式对象集合。每个RDD都被分为多个分区,这些分区运行在集群的不同节点上。创建RDD的方法有两种:一种是读取一个外部数据集;一种是在群东程序里分发驱动器程序中的对象集合,不如刚才的示例,读取文本文件作为一个字符串的RDD的示例。

  2. 创建出来后,RDD支持两种类型的操作:转化操作和行动操作

      转化操作会由一个RDD生成一个新的RDD。(比如刚才的根据谓词筛选)

      行动操作会对RDD计算出一个结果,并把结果返回到驱动器程序中,或把结果存储到外部存储系统(比如HDFS)中。比如first()操作就是一个行动操作,会返回RDD的第一个元素。

      注:转化操作与行动操作的区别在于Spark计算RDD的方式不同。虽然你可以在任何时候定义一个新的RDD,但Spark只会惰性计算这些RDD。它们只有第一个在一个行动操作中用到时,才会真正的计算。之所以这样设计,是因为比如刚才调用sc.textFile(...)时就把文件中的所有行都读取并存储起来,就会消耗很多存储空间,而我们马上又要筛选掉其中的很多数据。

      这里还需要注意的一点是,spark会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个RDD,那么可以使用RDD.persist()或RDD.collect()让Spark把这个RDD缓存下来。(可以是内存,也可以是磁盘)

  3. Spark会使用谱系图来记录这些不同RDD之间的依赖关系,Spark需要用这些信息来按需计算每个RDD,也可以依靠谱系图在持久化的RDD丢失部分数据时用来恢复所丢失的数据。(如下图,过滤errorsRDD与warningsRDD,最终调用union()函数)

      

  RDD计算方式

  

  RDD的宽窄依赖

  

  窄依赖 (narrowdependencies) 和宽依赖 (widedependencies) 。窄依赖是指 父 RDD 的每个分区都只被子 RDD 的一个分区所使用 。相应的,那么宽依赖就是指父 RDD 的分区被多个子 RDD 的分区所依赖。例如, map 就是一种窄依赖,而 join 则会导致宽依赖

  这种划分有两个用处。首先,窄依赖支持在一个结点上管道化执行。例如基于一对一的关系,可以在 filter 之后执行 map 。其次,窄依赖支持更高效的故障还原。因为对于窄依赖,只有丢失的父 RDD 的分区需要重新计算。而对于宽依赖,一个结点的故障可能导致来自所有父 RDD 的分区丢失,因此就需要完全重新执行。因此对于宽依赖,Spark 会在持有各个父分区的结点上,将中间数据持久化来简化故障还原,就像 MapReduce 会持久化 map 的输出一样。

  SparkExample

  

  步骤 1 :创建 RDD 。上面的例子除去最后一个 collect 是个动作,不会创建 RDD 之外,前面四个转换都会创建出新的 RDD 。因此第一步就是创建好所有 RDD( 内部的五项信息 ) 。

  步骤 2 :创建执行计划。Spark 会尽可能地管道化,并基于是否要重新组织数据来划分 阶段 (stage) ,例如本例中的 groupBy() 转换就会将整个执行计划划分成两阶段执行。最终会产生一个 DAG(directedacyclic graph ,有向无环图 ) 作为逻辑执行计划。

  步骤 3 :调度任务。 将各阶段划分成不同的 任务 (task) ,每个任务都是数据和计算的合体。在进行下一阶段前,当前阶段的所有任务都要执行完成。因为下一阶段的第一个转换一定是重新组织数据的,所以必须等当前阶段所有结果数据都计算出来了才能继续。

  假设本例中的 hdfs://names 下有四个文件块,那么 HadoopRDD 中 partitions 就会有四个分区对应这四个块数据,同时 preferedLocations 会指明这四个块的最佳位置。现在,就可以创建出四个任务,并调度到合适的集群结点上。

  Spark数据分区

  1. Spark的特性是对数据集在节点间的分区进行控制。在分布式系统中,通讯的代价是巨大的,控制数据分布以获得最少的网络传输可以极大地提升整体性能。Spark程序可以通过控制RDD分区方式来减少通讯的开销。

  2. Spark中所有的键值对RDD都可以进行分区。确保同一组的键出现在同一个节点上。比如,使用哈希分区将一个RDD分成了100个分区,此时键的哈希值对100取模的结果相同的记录会被放在一个节点上。

      (可使用partitionBy(newHashPartitioner(100)).persist()来构造100个分区)

  3. Spark中的许多操作都引入了将数据根据键跨界点进行混洗的过程。(比如:join(),leftOuterJoin(),groupByKey(),reducebyKey()等)对于像reduceByKey()这样只作用于单个RDD的操作,运行在未分区的RDD上的时候会导致每个键的所有对应值都在每台机器上进行本地计算。

  SparkSQL的shuffle过程

  

  Spark SQL的核心是把已有的RDD,带上Schema信息,然后注册成类似sql里的”Table”,对其进行sql查询。这里面主要分两部分,一是生成SchemaRD,二是执行查询。

  如果是spark-hive项目,那么读取metadata信息作为Schema、读取hdfs上数据的过程交给Hive完成,然后根据这俩部分生成SchemaRDD,在HiveContext下进行hql()查询。

  SparkSQL结构化数据

  1. 首先说一下ApacheHive,Hive可以在HDFS内或者在其他存储系统上存储多种格式的表。SparkSQL可以读取Hive支持的任何表。要把Spark SQL连接已有的hive上,需要提供Hive的配置文件。hive-site.xml文件复制到spark的conf文件夹下。再创建出HiveContext对象(sparksql的入口),然后就可以使用HQL来对表进行查询,并以由行足证的RDD的形式拿到返回的数据。

  2. 创建Hivecontext并查询数据

      importorg.apache.spark.sql.hive.HiveContext

      valhiveCtx = new org.apache.spark.sql.hive.HiveContext(sc)

      valrows = hiveCtx.sql(“SELECT name,age FROM users”)

      valfitstRow – rows.first()

      println(fitstRow.getSgtring(0)) //字段0是name字段

  3. 通过jdbc连接外部数据源更新与加载

      Class.forName("com.mysql.jdbc.Driver")

      val conn =DriverManager.getConnection(mySQLUrl)

      val stat1 =conn.createStatement()

      stat1.execute("UPDATE CI_LABEL_INFO set DATA_STATUS_ID = 2 , DATA_DATE ='" + dataDate +"' where LABEL_ID in ("+allCreatedLabels.mkString(",")+")")

      stat1.close()

      //加载外部数据源数据到内存

      valDIM_COC_INDEX_MODEL_TABLE_CONF =sqlContext.jdbc(mySQLUrl,"DIM_COC_INDEX_MODEL_TABLE_CONF").cache()

      val targets =DIM_COC_INDEX_MODEL_TABLE_CONF.filter("TABLE_DATA_CYCLE ="+TABLE_DATA_CYCLE).collect

  SparkSQL解析

  

  首先说下传统数据库的解析,传统数据库的解析过程是按Rusult、Data Source、Operation的次序来解析的。传统数据库先将读入的SQL语句进行解析,分辨出SQL语句中哪些词是关键字(如select,from,where),哪些是表达式,哪些是Projection,哪些是Data Source等等。进一步判断SQL语句是否规范,不规范就报错,规范则按照下一步过程绑定(Bind)。过程绑定是将SQL语句和数据库的数据字典(列,表,视图等)进行绑定,如果相关的Projection、Data Source等都存在,就表示这个SQL语句是可以执行的。在执行过程中,有时候甚至不需要读取物理表就可以返回结果,比如重新运行刚运行过的SQL语句,直接从数据库的缓冲池中获取返回结果。在数据库解析的过程中SQL语句时,将会把SQL语句转化成一个树形结构来进行处理,会形成一个或含有多个节点(TreeNode)的Tree,然后再后续的处理政对该Tree进行一系列的操作。

  Spark SQL对SQL语句的处理和关系数据库对SQL语句的解析采用了类似的方法,首先会将SQL语句进行解析,然后形成一个Tree,后续如绑定、优化等处理过程都是对Tree的操作,而操作方法是采用Rule,通过模式匹配,对不同类型的节点采用不同的操作。SparkSQL有两个分支,sqlContext和hiveContext。sqlContext现在只支持SQL语法解析器(Catalyst),hiveContext支持SQL语法和HiveContext语法解析器。

原文地址:http://mt.sohu.com/20160522/n450849016.shtml

posted @ 2016-09-08 13:11 三人行,必有我师焉 阅读(246) | 评论 (0)编辑 收藏

spark中有partition的概念(和slice是同一个概念,在spark1.2中官网已经做出了说明),一般每个partition对应一个task。在我的测试过程中,如果没有设置spark.default.parallelism参数,spark计算出来的partition非常巨大,与我的cores非常不搭。我在两台机器上(8cores *2 +6g * 2)上,spark计算出来的partition达到2.8万个,也就是2.9万个tasks,每个task完成时间都是几毫秒或者零点几毫秒,执行起来非常缓慢。在我尝试设置了 spark.default.parallelism 后,任务数减少到10,执行一次计算过程从minute降到20second。

参数可以通过spark_home/conf/spark-default.conf配置文件设置。

eg.

spark.master  spark://master:7077 

spark.default.parallelism  10 

spark.driver.memory  2g 

spark.serializer  org.apache.spark.serializer.KryoSerializer 

spark.sql.shuffle.partitions  50

 

下面是官网的相关描述:

from:http://spark.apache.org/docs/latest/configuration.html

Property NameDefaultMeaning
spark.default.parallelism For distributed shuffle operations like reduceByKey and join, the largest number of partitions in a parent RDD. For operations likeparallelize with no parent RDDs, it depends on the cluster manager:
  • Local mode: number of cores on the local machine
  • Mesos fine grained mode: 8
  • Others: total number of cores on all executor nodes or 2, whichever is larger
Default number of partitions in RDDs returned by transformations like joinreduceByKey, and parallelize when not set by user.

from:http://spark.apache.org/docs/latest/tuning.html

Level of Parallelism

Clusters will not be fully utilized unless you set the level of parallelism for each operation high enough. Spark automatically sets the number of “map” tasks to run on each file according to its size (though you can control it through optional parameters to SparkContext.textFile, etc), and for distributed “reduce” operations, such as groupByKey and reduceByKey, it uses the largest parent RDD’s number of partitions. You can pass the level of parallelism as a second argument (see the spark.PairRDDFunctions documentation), or set the config propertyspark.default.parallelism to change the default. In general, we recommend 2-3 tasks per CPU core in your cluster.


原文地址:http://www.cnblogs.com/wrencai/p/4231966.html

posted @ 2016-09-08 13:07 三人行,必有我师焉 阅读(2192) | 评论 (0)编辑 收藏

Eclipse Class Decompiler是一款Eclipse插件,整合了多种反编译器,和Eclipse Class Viewer无缝集成,能够很方便的使用插件查看类库源码,进行Debug调试。
同时还提供了在线自动查找源代码,查看Class二进制字节码的功能。
 

Eclipse Class Decompiler对JDK的最低要求为JDK1.6, 能反编译和debug各版本的Class文件,支持JDK8的Lambda语法,同时支持中文等非Ascii码字符集的解析,支持Eclipse 3.6及以上所有版本的Eclipse。

本插件支持Windows,Linux,Macosx 32位及64位操作系统。

Github项目地址为:https://github.com/cnfree/Eclipse-Class-Decompiler

请通过以下地址选择一个可用的源在线安装:

http://cnfree.github.io/Eclipse-Class-Decompiler/update
http://raw.githubusercontent.com/cnfree/eclipse/master/decompiler/update/
http://www.cpupk.com/decompiler/update/

离线包下载地址:
https://github.com/cnfree/Eclipse-Class-Decompiler/releases/download/v2.10.0/eclipse-class-decompiler-update_v2.10.0.zip

 
插件使用说明:

下图为Eclipse Class Decompiler的首选项页面,可以选择缺省的反编译器工具,并进行反编译器的基本设置。缺省的反编译工具为JD-Core,JD-Core更为先进一些,支持泛型、Enum、注解等JDK1.5以后才有的新语法。

首选项配置选项:
1.重用缓存代码:只会反编译一次,以后每次打开该类文件,都显示的是缓存的反编译代码。
2.忽略已存在的源代码:若未选中,则查看Class文件是否已绑定了Java源代码,如果已绑定,则显示Java源代码,如果未绑定,则反编译Class文件。若选中此项,则忽略已绑定的Java源代码,显示反编译结果。
3.显示反编译器报告:显示反编译器反编译后生成的数据报告及异常信息。
4.使用Eclipse代码格式化工具:使用Eclipse格式化工具对反编译结果重新格式化排版,反编译整个Jar包时,此操作会消耗一些时间。
5.使用Eclipse成员排序:使用Eclipse成员排序对反编译结果重新格式化排版,反编译整个Jar包时,此操作会消耗大量时间。
6.以注释方式输出原始行号信息:如果Class文件包含原始行号信息,则会将行号信息以注释的方式打印到反编译结果中。
7.根据行号对齐源代码以便于调试:若选中该项,插件会采用AST工具分析反编译结果,并根据行号信息调整代码顺序,以便于Debug过程中的单步跟踪调试。
8.设置类反编译查看器作为缺省的类文件编辑器:默认为选中,将忽略Eclipse自带的Class Viewer,每次Eclipse启动后,默认使用本插件提供的类查看器打开Class文件。



插件提供了系统菜单,工具栏,当打开了插件提供的类反编译查看器后,会激活菜单和工具栏选项,可以方便的进行首选项配置,切换反编译工具重新反编译,以及导出反编译结果。






类反编译查看器右键菜单包含了Eclipse自带类查看器右键菜单的全部选项,并增加了一个“导出反编译源代码”菜单项。



打开项目路径下的Class文件,如果设置类反编译查看器为缺省的查看器,直接双击Class文件即可,如果没有设置为缺省查看器,可以使用右键菜单进行查看。




同时插件也支持直接将外部的Class文件拖拽到Eclipse编辑器中进行反编译。


Eclipse Class Decompiler插件也提供了反编译整个Jar文件或者Java包的反编译。该操作支持Package Explorer对包显示布局的操作,如果是平铺模式布局,则导出的源代码不包含子包,如果是层级模式布局,则导出选中的包及其所有的子包。




Debug调试:可以在首选项选中对齐行号进行单步跟踪调试,和普通的包含源代码时的调试操作完全一致,同样的也可以设置断点进行跟踪。当透视图为Debug时,插件自动生成行号并进行对齐方便调试代码,无需进行任何设置。


博文地址:http://www.blogjava.net/cnfree/archive/2012/10/30/390457.html
posted @ 2016-05-13 14:23 三人行,必有我师焉 阅读(1299) | 评论 (5)编辑 收藏

Java应用定制工厂(以下简称为JCB,Java Customization Builder)是一个针对Java轻量级桌面应用进行精简优化的小工具,使用它可以精简你的jar包,并自动生成一个精简的JRE,也可以使用它生成一个Exe启动引导程序,并且能够对你的Java应用自动做Pack200和Unpack200处理。使用本工具定制的Java桌面应用通常不会超过10M(包含JRE),SWT客户端程序相对于Swing客户端程序更小,一般不会超过5M。

JCB是一个Java应用,所以目标机器上必须安装1.5以上版本的JDK用以启动JCB,但是JCB可以用来精简1.4版的JRE,并且JRE1.4精简后的体积远小于1.5以上的版本。

1.新建JCB项目
精简JRE的步骤比较繁琐,有可能精简失败,为了不重复之前的步骤,JCB提供一个项目文件用来保存精简配置信息,扩展名为jcprj。这里我们创建一个项目,名为JCB


Wizard需要输入一个工程名和指定工程位置,至于下面的应用程序位置和定制JRE位置由JCB自动指定,这儿显示出来仅供参考。

此时最好Ctrl+S保存一下项目,否则退出后你之前的配置信息会全部丢失,因为你并没有制定一个可用的项目配置文件。

2. 配置JCB项目


首先指定项目需要的jar文件,然后依次选择项目的main class,启动路径默认为空,一般来说无需指定。然后设定应用程序参数和虚拟机参数。最后选定需要精简的JRE,JCB当前支持1.4-1.7版本的JRE,未来可能会支持更高版本的JRE。

右下角有2个单选按钮:全部重新运行和增量运行。全部重新运行就会放弃之前的运行结果,增量运行就是会保留以前的运行结果。

然后点击“以Verbose模式运行”按钮。Verbose模式运行Java程序,会显示JVM加载的全部类信息,JCB需要这些类信息进行JRE的精简,所以请尽可能的把应用所有的功能尽可能的跑一遍,跑的越全面,导致精简出错的可能性就越低。



Verbose运行结果,这个页面的显示信息仅供参考,无实际用处。

3. 分析项目的类依赖项


分析类依赖模式有2个选项:重新完全分析和增量分析。完全分析会花费较多的时间。当使用verbose模式增量运行后,可以使用增量模式分析类依赖项,这样可以节约大量的时间。类依赖分析会反编译所有运行的类,分析类引用关系,但是无法获取Class.forName这类动态类加载信息,所以需要Verbose模式运行的尽量全面,以避免这些动态加载的类的缺失。

为什么需要分析类依赖关系呢?因为不同的操作系统,不同的硬件配置,JRE可能会采取策略模式加载不同的类,或者一些异常,Verbose模式一般不会加载,这样换个硬件环境,仅仅使用Verbose模式的类可能会导致ClassNotFound这样的异常,导致Java程序崩溃。


4. 精简JRE


精简JRE有两种模式:使用Verbose运行结果和使用类依赖分析结果。前者只包含Verbose分析出来的类,精简出来的JRE包很小,但是基本不具备跨平台性。所以一般来说推荐选择后者。

如果你的程序包含Swing的客户端,并且比较复杂的话,最好选中包含Swing选项。因为Swing的设计完全是动态化的加载,全部使用Class.forName方式,类依赖分析对Swing是无效的。当然选中该选项后,JRE的体积会增加许多。比较好的解决方案,是使用SWT替代Swing进行开发,或者尽量把你的程序跑全面,包括各种异常界面都跑出来。

右下角有两个按钮,是用来自定义类和资源文件的,比如移除JAR包的MD5文件或者无用的文件。或者测试运行发现ClassNotFound异常,手动把缺少的类加进去,然后JCB会自动运行增量类依赖分析加载所有可能需要的类。

选择左上角的“精简Jar包”按钮,就可以对JRE进行精简了,精简完毕后可以点击“查看精简结果”按钮进行查看。

5.定制JRE


上图显示了JRE精简结果,JCB会自动分析所有的Class,生成精简版JRE,包括需要的JAR,DLL和资源文件。一般来说精简出来的JRE,普通功能都能正确完成,但是不排除有些功能不能正常使用,比如缺少某个资源文件或者DLL,需要手工添加。

为了保证精简的正确性,你需要进行运行测试,这一步是必须的,而且最好和Verbose运行模式一样,把所有的功能都跑一遍,确认精简无误。



如果测试运行有误的话,请根据运行错误报告进行分析,如果缺少类,请使用Verbose模式重新运行相应的功能,或者在步骤四手工添加需要的类,然后重新生成依赖的JRE。如果缺少相关的DLL或者资源文件,也请手工添加,并且取消步骤四的“清理工作区选项”,否则每次精简JRE都需要重新手工添加。

到此为止,精简JRE部分就算全部完成了,你最好使用Ctrl+S保存一下结果,以避免下次重做项目。

JCB项目下载地址:http://www.sourceforge.net/projects/jcb
posted @ 2013-03-03 17:25 三人行,必有我师焉 阅读(5202) | 评论 (13)编辑 收藏

1. 40亿个无符号整数,找出一个不在这40亿个整数中的数。可以换个方向思考, 99个小于100的数,找出一个不在这99个数中的小于100的数。
首先把这99个数分为10组,按高位为0-9分,然后计算每组的数量,数量最少的那个肯定就是缺失的那个,然后递归……找最少的那个,组合起来的数肯定是缺失的。答案是按位运算找,和这个类似。

2. 43亿个无符号整数,找出一个重复的整数。也就是101个小于100的数,找出重复的那个数来。
首先把这99个数分为10组,按高位为0-9分,然后计算每组的数量,数量最多的那组,肯定有重复的,一次类推找第二位……
posted @ 2012-11-24 22:21 三人行,必有我师焉 阅读(393) | 评论 (0)编辑 收藏

When a object creates a new object, please use the dependency.

When a object just uses a object, please use the association. 
posted @ 2012-11-19 13:16 三人行,必有我师焉 阅读(277) | 评论 (0)编辑 收藏

comparator 

Decorator Pattern and Adapter Pattern have the same alias name: wrapper. But they face different aspects. Decorator pattern changes the object function, but the adapter pattern changes the interface.

The typical decorator pattern is the java OutputStream, you can use the BufferedOutputStream to wrap it, then get the extra function.
The typical adapter pattern in the BIRT is the ElementAdapter, it can convert any object to an other object.

Decorator pattern must extend the class which you want to wrap, but the adapter class must implements the interface using by the client.


FlyWeight pattern extracts the same part of some different objects, and the part doesn't be changed when these objects changed. String class uses the FlyWeight pattern, jface 
ImageRegistry also uses it. 
FlyWeight can have a interface to get external data, and change the external data's status, but FlyWeight internal status shouldn't be changed.

The Collections.sort() method implementation contains template method design pattern and strategy design pattern, but it doesn't contain the visitor design pattern. The Collections.sort() method uses the merge sort algorithm, you can't change it, but you can change the comparator logic, it's one step of the sort algorithm. So it's a template method pattern, but not a classic implementation, it uses the callback method to implement the pattern, but not extending the parent template class. The comparator class use the strategy design pattern, it not a visitor pattern, visitor pattern have a accept method to operate the element to deal some logic. 



posted @ 2012-11-14 00:22 三人行,必有我师焉 阅读(317) | 评论 (0)编辑 收藏

1 归并排序(MergeSort)

归并排序最差运行时间是O(nlogn),它是利用递归设计程序的典型例子。

归并排序的最基础的操作就是合并两个已经排好序的序列。

假设我们有一个没有排好序的序列,那么首先我们使用分割的办法将这个序列分割成一个一个已经排好序的子序列。然后再利用归并的方法将一个个的子序列合并成排序好的序列。分割和归并的过程可以看下面的图例。



从上图可以看出,我们首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。

如何把两个已经排序好的子序列归并成一个排好序的序列呢?可以参看下面的方法。

假设我们有两个已经排序好的子序列。
序列A:1 23 34 65
序列B:2 13 14 87
那么可以按照下面的步骤将它们归并到一个序列中。

(1)首先设定一个新的数列C[8]。
(2)A[0]和B[0]比较,A[0] = 1,B[0] = 2,A[0] < B[0],那么C[0] = 1
(3)A[1]和B[0]比较,A[1] = 23,B[0] = 2,A[1] > B[0],那么C[1] = 2
(4)A[1]和B[1]比较,A[1] = 23,B[1] = 13,A[1] > B[1],那么C[2] = 13
(5)A[1]和B[2]比较,A[1] = 23,B[2] = 14,A[1] > B[2],那么C[3] = 14
(6)A[1]和B[3]比较,A[1] = 23,B[3] = 87,A[1] < B[3],那么C[4] = 23
(7)A[2]和B[3]比较,A[2] = 34,B[3] = 87,A[2] < B[3],那么C[5] = 34
(8)A[3]和B[3]比较,A[3] = 65,B[3] = 87,A[3] < B[3],那么C[6] = 65
(9)最后将B[3]复制到C中,那么C[7] = 87。归并完成。

如果我们清楚了上面的分割和归并过程,那么我们就可以用递归的方法得到归并算法的实现。

    public class MergeSorter
    {
        
private static int[] myArray;
        
private static int arraySize;

        
public static void Sort( int[] a )
        {
            myArray 
= a;
            arraySize 
= myArray.Length;
            MergeSort();
        }

        
/// <summary>
        
/// 利用归并的方法排序数组,首先将序列分割
        
/// 然后将数列归并,这个算法需要双倍的存储空间
        
/// 时间是O(nlgn)
        
/// </summary>
        private static void MergeSort()
        {
            
int[] temp = new int[arraySize];
            MSort( temp, 
0, arraySize - 1);
        }

        
private static void MSort(int[] temp, int left, int right)
        {
            
int mid;

            
if (right > left)
            {
                mid 
= (right + left) / 2;
                MSort( temp, left, mid); 
//分割左边的序列
                MSort(temp, mid+1, right);//分割右边的序列
                Merge(temp, left, mid+1, right);//归并序列
            }
        }

        
private static void Merge( int[] temp, int left, int mid, int right)
        {
            
int i, left_end, num_elements, tmp_pos;

            left_end 
= mid - 1;
            tmp_pos 
= left;
            num_elements 
= right - left + 1;

            
while ((left <= left_end) && (mid <= right)) 
            {
                
if (myArray[left] <= myArray[mid]) //将左端序列归并到temp数组中
                {
                    temp[tmp_pos] 
= myArray[left];
                    tmp_pos 
= tmp_pos + 1;
                    left 
= left +1;
                }
                
else//将右端序列归并到temp数组中
                {
                    temp[tmp_pos] 
= myArray[mid];
                    tmp_pos 
= tmp_pos + 1;
                    mid 
= mid + 1;
                }
            }

            
while (left <= left_end) //拷贝左边剩余的数据到temp数组中
            {
                temp[tmp_pos] 
= myArray[left];
                left 
= left + 1;
                tmp_pos 
= tmp_pos + 1;
            }
            
while (mid <= right) //拷贝右边剩余的数据到temp数组中
            {
                temp[tmp_pos] 
= myArray[mid];
                mid 
= mid + 1;
                tmp_pos 
= tmp_pos + 1;
            }

            
for (i=0; i < num_elements; i++//将所有元素拷贝到原始数组中
            {
                myArray[right] 
= temp[right];
                right 
= right - 1;
            }
        }
    }


归并排序算法是一种O(nlogn)的算法。它的最差,平均,最好时间都是O(nlogn)。但是它需要额外的存储空间,这在某些内存紧张的机器上会受到限制。

归并算法是又分割和归并两部分组成的。对于分割部分,如果我们使用二分查找的话,时间是O(logn),在最后归并的时候,时间是O(n),所以总的时间是O(nlogn)。

2 堆排序(HeapSort)

堆排序属于百万俱乐部的成员。它特别适合超大数据量(百万条记录以上)的排序。因为它并不使用递归(因为超大数据量的递归可能会导致堆栈溢出),而且它的时间也是O(nlogn)。还有它并不需要大量的额外存储空间。

堆排序的思路是:

(1)将原始未排序的数据建成一个堆。
(2)建成堆以后,最大值在堆顶,也就是第0个元素,这时候将第零个元素和最后一个元素交换。
(3)这时候将从0到倒数第二个元素的所有数据当成一个新的序列,建一个新的堆,再次交换第一个和最后一个元素,依次类推,就可以将所有元素排序完毕。

建立堆的过程如下面的图所示:


堆排序的具体算法如下:

public class HeapSorter 
    {
        
private static int[] myArray;
        
private static int arraySize;

        
public static void Sort( int[] a )
        {
            myArray 
= a;
            arraySize 
= myArray.Length;
            HeapSort();
        }

        
private static void HeapSort()
        {
            BuildHeap();            
//将原始序列建成一个堆

            
while ( arraySize > 1 )
            {
                arraySize
--;
                Exchange ( 
0, arraySize );//将最大值放在数组的最后
                DownHeap ( 0 );  //将序列从0到n-1看成一个新的序列,重新建立堆
            } 
        }

        
private static void BuildHeap()
        {
            
for (int v=arraySize/2-1; v>=0; v--)
                DownHeap ( v );
        }

        
//利用向下遍历子节点建立堆
        private static void DownHeap( int v )
        {
            
int w = 2 * v + 1;                     // 节点w是节点v的第一个子节点

            
while (w < arraySize)
            {
                
if ( w+1 < arraySize )        // 如果节点v下面有第二个字节点
                    if ( myArray[w+1> myArray[w] ) 
                        w
++;                        // 将子节点w设置成节点v下面值最大的子节点

                 
// 节点v已经大于子节点w,有了堆的性质,那么返回
                if ( myArray[v] >= myArray[w] ) 
                    
return;   
                
                Exchange( v, w );     
// 如果不是,就交换节点v和节点w的值
                v = w;        
                w 
= 2 * v + 1;            // 继续向下找子节点
            }
        }

        
//交换数据
        private static void Exchange( int i, int j )
        {
            
int t = myArray[i];
            myArray[i] 
= myArray[j];
            myArray[j] 
= t;
        }
    }    


 

堆排序主要用于超大规模的数据的排序。因为它不需要额外的存储空间,也不需要大量的递归。

3 几种O(nlogn)算法的初步比较

我们可以从下表看到几种O(nlogn)算法的效率的区别。所有的数据都使用.Net的Random类产生,每种算法运行100次,时间的单位为毫秒。


500随机整数5000随机整数20000随机整数
合并排序0.31251.56257.03125
 Shell排序0.31251.256.875
堆排序0.468752.18756.71875
快速排序0.156250.6252.8125

从上表可以明显地看出,快速排序是最快的算法。这也就给了我们一个结论,对于一般的应用来说,我们总是选择快速排序作为我们的排序算法,当数据量非常大(百万数量级)我们可以使用堆排序,如果内存空间非常紧张,我们可以使用Shell排序。但是这意味着我们不得不损失速度。 

/******************************************************************************************
 *【Author】:flyingbread
 *【Date】:2007年2月2日
 *【Notice】:
 *1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
 *2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
 *3、本声明为文章一部分,转载和引用必须包括在原文中。
 ******************************************************************************************/
posted @ 2012-11-10 23:18 三人行,必有我师焉 阅读(591) | 评论 (2)编辑 收藏

1 快速排序(QuickSort)

快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。

(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。

快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。 

2 归并排序(MergeSort)

归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。

3 堆排序(HeapSort)

堆排序适合于数据量非常大的场合(百万数据)。

堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。

堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。

4 Shell排序(ShellSort)

Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。

Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。

5 插入排序(InsertSort)

插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。

6 冒泡排序(BubbleSort)

冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。

7 交换排序(ExchangeSort)和选择排序(SelectSort)

这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。

8 基数排序(RadixSort)

基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。

9 总结

下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 平均时间最差情形稳定度额外空间备注
冒泡 O(n2)  O(n2) 稳定O(1)n小时较好
交换  O(n2)  O(n2)不稳定O(1)n小时较好
选择 O(n2) O(n2)不稳定O(1)n小时较好
插入 O(n2) O(n2)稳定O(1)大部分已排序时较好
基数O(logRB)O(logRB)稳定O(n)

B是真数(0-9),

R是基数(个十百)

ShellO(nlogn)O(ns) 1<2不稳定O(1)s是所选分组
快速O(nlogn)O(n2)不稳定O(nlogn)n大时较好
归并O(nlogn)O(nlogn)稳定O(1)n大时较好
O(nlogn)O(nlogn)不稳定O(1)n大时较好

posted @ 2012-11-10 22:30 三人行,必有我师焉 阅读(331) | 评论 (0)编辑 收藏

Eclipse Class Decompiler是一款Eclipse插件,整合了多种反编译器,和Eclipse Class Viewer无缝集成,能够很方便的使用插件查看类库源码,进行Debug调试。
同时还提供了在线自动查找源代码,查看Class二进制字节码的功能。 


Eclipse Class Decompiler对JDK的最低要求为JDK1.6, 能反编译和debug各版本的Class文件,支持JDK8的Lambda语法,同时支持中文等非Ascii码字符集的解析,支持Eclipse 3.6及以上所有版本的Eclipse。

本插件支持Windows,Linux,Macosx 32位及64位操作系统。

Github项目地址为:https://github.com/cnfree/Eclipse-Class-Decompiler

请通过以下地址选择一个可用的源在线安装:

http://cnfree.github.io/Eclipse-Class-Decompiler/update
http://raw.githubusercontent.com/cnfree/eclipse/master/decompiler/update/
http://www.cpupk.com/decompiler/update/

离线包下载地址:
https://github.com/cnfree/Eclipse-Class-Decompiler/releases/download/v2.10.0/eclipse-class-decompiler-update_v2.10.0.zip
 
插件使用说明:

下图为Eclipse Class Decompiler的首选项页面,可以选择缺省的反编译器工具,并进行反编译器的基本设置。缺省的反编译工具为JD-Core,JD-Core更为先进一些,支持泛型、Enum、注解等JDK1.5以后才有的新语法。

首选项配置选项:
1.重用缓存代码:只会反编译一次,以后每次打开该类文件,都显示的是缓存的反编译代码。
2.忽略已存在的源代码:若未选中,则查看Class文件是否已绑定了Java源代码,如果已绑定,则显示Java源代码,如果未绑定,则反编译Class文件。若选中此项,则忽略已绑定的Java源代码,显示反编译结果。
3.显示反编译器报告:显示反编译器反编译后生成的数据报告及异常信息。
4.使用Eclipse代码格式化工具:使用Eclipse格式化工具对反编译结果重新格式化排版,反编译整个Jar包时,此操作会消耗一些时间。
5.使用Eclipse成员排序:使用Eclipse成员排序对反编译结果重新格式化排版,反编译整个Jar包时,此操作会消耗大量时间。
6.以注释方式输出原始行号信息:如果Class文件包含原始行号信息,则会将行号信息以注释的方式打印到反编译结果中。
7.根据行号对齐源代码以便于调试:若选中该项,插件会采用AST工具分析反编译结果,并根据行号信息调整代码顺序,以便于Debug过程中的单步跟踪调试。
8.设置类反编译查看器作为缺省的类文件编辑器:默认为选中,将忽略Eclipse自带的Class Viewer,每次Eclipse启动后,默认使用本插件提供的类查看器打开Class文件。



插件提供了系统菜单,工具栏,当打开了插件提供的类反编译查看器后,会激活菜单和工具栏选项,可以方便的进行首选项配置,切换反编译工具重新反编译,以及导出反编译结果。






类反编译查看器右键菜单包含了Eclipse自带类查看器右键菜单的全部选项,并增加了一个“导出反编译源代码”菜单项。



打开项目路径下的Class文件,如果设置类反编译查看器为缺省的查看器,直接双击Class文件即可,如果没有设置为缺省查看器,可以使用右键菜单进行查看。




同时插件也支持直接将外部的Class文件拖拽到Eclipse编辑器中进行反编译。


Eclipse Class Decompiler插件也提供了反编译整个Jar文件或者Java包的反编译。该操作支持Package Explorer对包显示布局的操作,如果是平铺模式布局,则导出的源代码不包含子包,如果是层级模式布局,则导出选中的包及其所有的子包。




Debug调试:可以在首选项选中对齐行号进行单步跟踪调试,和普通的包含源代码时的调试操作完全一致,同样的也可以设置断点进行跟踪。当透视图为Debug时,插件自动生成行号并进行对齐方便调试代码,无需进行任何设置。


博文地址:http://www.blogjava.net/cnfree/archive/2012/10/30/390457.html
posted @ 2012-10-30 13:48 三人行,必有我师焉 阅读(92441) | 评论 (43)编辑 收藏

The original author of the JadClipse project maintains it no more, and the latest build 3.3.0 doesn't support eclipse 4.x, so I download the source code and update it.

JadClipse for Eclipse 4.x also support Eclipse 3.x, and provides several new features:
 
1. Integrate jad.exe into the plugin, don't need to set jad path in the preference page again.
2. Add two options in the JadClipse main preference page:
    (1) Use Eclipse member sorter
    (2) Show decompiler report
3. Update the formatting preference default settings, the "Output fields before methods" setting's default value changes to true.

JadClipse for Eclipse 4.x Update Site: http://feeling.sourceforge.net/update

Offline Archive Update File Download: 
1. http://feeling.sourceforge.net/downloads/org.sf.feeling.decompiler_1.0.3.zip
2. http://www.blogjava.net/Files/cnfree/org.sf.feeling.decompiler_1.0.3.zip
posted @ 2012-10-15 17:45 三人行,必有我师焉 阅读(7436) | 评论 (10)编辑 收藏

http://www.open-open.com/lib/view/1326265166952

http://blog.chinaunix.net/uid-22342564-id-3183018.html
posted @ 2012-05-31 02:42 三人行,必有我师焉 阅读(469) | 评论 (0)编辑 收藏

com\maxmpz\audioplayer\widget\listwrappers\D800DC00 这个package 主要用于 PlayList 模型的操作。

com\maxmpz\audioplayer\widget\listwrappers\D800DC00\D803DC04.java

里面有个
    private static int D801DC01(Activity activity, int i)
    {
        TypedArray typedarray 
= activity.obtainStyledAttributes(null, com.maxmpz.audioplayer.j.true.p, 00);
        
int j = typedarray.getResourceId(i, 0);
        typedarray.recycle();
        
return j;
    }

Context.obtainStyledAttributes 实现控件属性与XML定义绑定的代码。 

TypedArray其实就是一个存放资源的Array,首先从上下文中获取到R.styleable。。。这个属性资源的资源数组。 attrs是构造函数传进来,应该就是对应attrs.xml文件。 a.getString(R.styleable。。。);这句代码就是获取attrs.xml中定义的属性,并将这个属性的值传给本控件的mValue.最后,返回一个绑定结束的信号给资源:a.recycle();绑定结束

相关学习文章:
http://blog.csdn.net/aomandeshangxiao/article/details/7449973

com.maxmpz.audioplayer.widget.listwrappers.0xE9 这个类,用于显示文件夹列表,右上方有2个自定义的RadioButton,用来设置是平铺模式显示还是层级显示。
定义了一个ID为:flat_hier_group 的RadioGroup,里面有个2个自定义的RadioButton。

<RadioGroup android:gravity="right" android:orientation="horizontal" android:id="@id/flat_hier_group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true"
  xmlns:android
="http://schemas.android.com/apk/res/android">
    
<RadioButton android:id="@id/flat_folders_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="0.0dip" android:button="@drawable/matte_flat_folders_selector" />
    
<RadioButton android:id="@id/hier_folders_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="-8.0dip" android:button="@drawable/matte_hier_folders_selector" />
</RadioGroup>

matte_flat_folders_selector的XML定义为:
 

<selector
  
xmlns:android="http://schemas.android.com/apk/res/android">
    
<item android:state_checked="true" android:drawable="@drawable/matte_flat_folders_selected" />
    
<item android:state_checked="false" android:drawable="@drawable/matte_flat_folders" />
</selector>

自定义的RadioButton实际上就是张背景透明的图片罢了。


播放器列表的Layout布局文件为 list_with_big_header.xml。

里面有个android:ellipsize属性:
EidtText和textview中内容过长的话自动换行,使用android:ellipsize与android:singleine可以解决,使只有一行。EditText不支持marquee

关于android:ellipsize属性更详细的文章:http://www.cnblogs.com/chiao/archive/2011/08/20/2147250.html


里面还有个 android:textAppearance 属性,这里涉及到了Android的theme和style设置了,更详细的文章参见:http://jiayanjujyj.iteye.com/blog/1392541
posted @ 2012-05-23 11:59 三人行,必有我师焉 阅读(419) | 评论 (0)编辑 收藏

获取屏幕Display: Activity.getWindowManager().getDefaultDisplay();
获取扩展存储目录:Environment.getExternalStorageDirectory()
通过文件获取Uri:Uri.fromFile(File)
根据文件路径获取图片:BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions)
获取相机Intent:new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
获取相机拍照后的图片:
Bundle extras = intent.getExtras();
Bitmap bmp = (Bitmap) extras.get("data");
触摸事件:onTouchEvent(MotionEvent ev)
媒体播放器:android.media.MediaPlayer
媒体控制器:android.widget.MediaController(和播放器不在同一个package下)
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface,类似于Canvas,但感觉比Canvas更高级。
android.provider.MediaStore里包含了相关的Image,Video,Audio信息,可通过managedQuery方法来查询和遍历。
Android中的AdapterView使用Adapter来获取数据,和JFace中的ContentProvider对应。
根据字符串路径获取Uri:
Uri.parse((String)Path)
封装好的视频View:android.widget.VideoView
视频录制:android.media.MediaRecorder
相机高画质:CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);


设置透明度(这是窗体本身的透明度,非背景)

WindowManager.LayoutParams lp=getWindow().getAttributes();
                lp.alpha=0.3f;
              getWindow().setAttributes(lp);
                
alpha在0.0f到1.0f之间。1.0完全不透明,0.0f完全透明


设置黑暗度

                WindowManager.LayoutParams lp=getWindow().getAttributes();
                lp.dimAmount=0.5f;
                getWindow().setAttributes(lp);
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

dimAmount在0.0f和1.0f之间,0.0f完全不暗,1.0f全暗


设置背景模糊

getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,     
           WindowManager.LayoutParams.FLAG_BLUR_BEHIND);



//调用浏览器 
Uri uri = Uri.parse(""); 
Intent it = new Intent(Intent.ACTION_VIEW,uri); 
startActivity(it); 

//显示某个坐标在地图上 
Uri uri = Uri.parse("geo:38.899533,-77.036476"); 
Intent it = new Intent(Intent.Action_VIEW,uri); 
startActivity(it); 

//显示路径 
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en"); 
Intent it = new Intent(Intent.ACTION_VIEW,URI); 
startActivity(it); 

//拨打电话 
Uri uri = Uri.parse("tel:10086"); 
Intent it = new Intent(Intent.ACTION_DIAL, uri); 
startActivity(it); 

Uri uri = Uri.parse("tel.10086"); 
Intent it =new Intent(Intent.ACTION_CALL,uri); 
//需要添加 <uses-permission id="android.permission.CALL_PHONE" /> 这个权限到androidmanifest.xml 

//发送短信或彩信 
Intent it = new Intent(Intent.ACTION_VIEW); 
it.putExtra("sms_body", "The SMS text"); 
it.setType("vnd.android-dir/mms-sms"); 
startActivity(it); 

//发送短信 
Uri uri = Uri.parse("smsto:10086"); 
Intent it = new Intent(Intent.ACTION_SENDTO, uri); 
it.putExtra("sms_body", "cwj"); 
startActivity(it); 

//发送彩信 
Uri uri = Uri.parse("content://media/external/images/media/23"); 
Intent it = new Intent(Intent.ACTION_SEND); 
it.putExtra("sms_body", "some text"); 
it.putExtra(Intent.EXTRA_STREAM, uri); 
it.setType("image/png"); 
startActivity(it); 

//发送邮件 
Uri uri = Uri.parse("mailto:android123@163.com"); 
Intent it = new Intent(Intent.ACTION_SENDTO, uri); 
startActivity(it); 

Intent it = new Intent(Intent.ACTION_SEND); 
it.putExtra(Intent.EXTRA_EMAIL, android123@163.com); 
it.putExtra(Intent.EXTRA_TEXT, "The email body text"); 
it.setType("text/plain"); 
startActivity(Intent.createChooser(it, "Choose Email Client")); 

Intent it=new Intent(Intent.ACTION_SEND); 
String[] tos={"me@abc.com"}; 
String[] ccs={"you@abc.com"}; 
it.putExtra(Intent.EXTRA_EMAIL, tos); 
it.putExtra(Intent.EXTRA_CC, ccs); 
it.putExtra(Intent.EXTRA_TEXT, "The email body text"); 
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); 
it.setType("message/rfc822"); 
startActivity(Intent.createChooser(it, "Choose Email Client")); 

//播放媒体文件 
Intent it = new Intent(Intent.ACTION_VIEW); 
Uri uri = Uri.parse("file:///sdcard/cwj.mp3"); 
it.setDataAndType(uri, "audio/mp3"); 
startActivity(it); 

Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1"); 
Intent it = new Intent(Intent.ACTION_VIEW, uri); 
startActivity(it); 

//卸载APK 
Uri uri = Uri.fromParts("package", strPackageName, null); 
Intent it = new Intent(Intent.ACTION_DELETE, uri); 
startActivity(it); 

//卸载apk 2 
Uri uninstallUri = Uri.fromParts("package", "xxx", null); 
returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri); 

//安装APK 
Uri installUri = Uri.fromParts("package", "xxx", null); 
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri); 

//播放音乐 
Uri playUri = Uri.parse("file:///sdcard/download/sth.mp3"); 
returnIt = new Intent(Intent.ACTION_VIEW, playUri); 

//发送附近 
Intent it = new Intent(Intent.ACTION_SEND); 
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); 
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/cwj.mp3"); 
sendIntent.setType("audio/mp3"); 
startActivity(Intent.createChooser(it, "Choose Email Client")); 

//market上某个应用信,pkg_name就是应用的packageName 
Uri uri = Uri.parse("market://search?q=pname:pkg_name"); 
Intent it = new Intent(Intent.ACTION_VIEW, uri); 
startActivity(it); 

//market上某个应用信息,app_id可以通过www网站看下 
Uri uri = Uri.parse("market://details?id=app_id"); 
Intent it = new Intent(Intent.ACTION_VIEW, uri); 
startActivity(it); 

//调用搜索 
Intent intent = new Intent(); 
intent.setAction(Intent.ACTION_WEB_SEARCH); 
intent.putExtra(SearchManager.QUERY,"android123") 
startActivity(intent); 

//调用分享菜单 
Intent intent=new Intent(Intent.ACTION_SEND); 
intent.setType("text/plain"); //分享的数据类型 
intent.putExtra(Intent.EXTRA_SUBJECT, "subject"); //主题 
intent.putExtra(Intent.EXTRA_TEXT, "content"); //内容 
startActivity(Intent.createChooser(intent, "title")); //目标应用选择对话框的标题


获取Location:
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_COARSE);
criteria.setPowerRequirement(Criteria.POWER_LOW);
LocationManager locManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if(locManager.getBestProvider(criteria, true) != null)
myLocation = locManager.getLastKnownLocation(locManager.getBestProvider(criteria, true));
else {
myLocation = new Location("gps");
myLocation.setLatitude(47.100301);
myLocation.setLongitude(-119.982465);
}
posted @ 2012-05-05 19:41 三人行,必有我师焉 阅读(843) | 评论 (0)编辑 收藏


3.1以下版本升级到最新版最好先还原到初始档,然后重新备份一下初始档。

如果不希望重新开局,以下提供正确的操作步骤:
1、进入备份与还原界面。
2、选择“备份当前游戏数据“进行备份。
3、选择“ 还原游戏至初始档状态“进行还原。
4、选择“设置当前游戏数据作为游戏初始档”进行备份。
5、在还原备份数据的列表中选择步骤2备份的数据存档进行还原

就可以正常进入游戏,并且更新了原始档。


3.9版更新内容:
1、修正了若干已知Bug。
2、象兵及战车数据使用兵种第二防御数据。
3、全面更新地图坐标算法,防止无法进入游戏或者武将位于山地无法移动。
4、增加了切换游戏势力功能,可以无需开档游玩任意势力。
5、增加了东州兵和骆驼兵模的防跳补丁。


3.8版更新内容:
1、增加了解锁朝廷、在野、乱军势力的功能。
2、增加了解除诸葛亮外出云游状态的脚本补丁,玩家可以使用任何势力收买诸葛亮,并且激活三顾茅庐技能。
3、增加了全势力官爵升级的脚本补丁。
4、增加了部分武将特殊技能全势力激活的脚本补丁。
5、增加了创建新兵种功能。


3.7版更新内容:
1、修正了一些细节上的bug。
2、增加了雾隐版吕玲绮卫队女兵模型的导入。
3、增加了武将模型作为骑兵模型的导入,并且可自由移除已导入的模型。
4、步兵的士兵模型可以选择将军模型,骑兵的士兵模型可以选择已导入的将军模型和雾影女兵模型。

3.6版更新内容:
1、修正了导入弩兵陷阵营战斗跳出的bug。
2、修正了收买武将修改器发生异常的bug。
3、修正了更换异族武将势力游戏跳出的bug。
4、修改了一些2种势力都有的武将相关的bug。
5、新建武将后清空武将编辑页面状态。
6、编辑武将能力页面新增了编辑武将卫队经验选项。
7、基本修改页面增加了所有兵种生产回合、费用下调的修改。
8、基本修改页面增加了所有城市建筑生产回合、费用下调的修改。

3.5版更新内容:
1、修正了兵种修改页面中兵种名称显示错乱的bug,现在很容易分清雇佣兵和城市招募了。
2、增加了乱军刷新概率的修改。
3、增加导入弩骑高顺卫队和弩兵陷阵营的功能。

3.4版更新内容:
1、修正了编辑大众卫队武将名字时会改变大众卫队名称的bug。
2、增加了编辑势力地图颜色的功能。
3、增加了降低敌方所有武将忠诚度的功能。
4、解决游戏中部分武将心灰意冷状态下无法被收买的bug。

3.3版更新内容:
1、修正了一些bug,比如编辑武将坐标会发生变化的bug。
2、创建新武将页面的爵位增加了自定义功能。
3、难度修改页面中,增加了禁用电脑驿站加成,禁止电脑生产刺客,以及能够编辑电脑太守加成几率的新功能。

3.2版更新内容:
1、增加了1.9a中特有的马匹,1.9a特有的马匹一般冲量都比较大,老版马匹冲量较小。
2、修正了武将地图坐标自动计算错误的bug。我把RGB颜色值写错了,另外遗漏了森林颜色。
3、修正了编辑新武将坐标和兵模无效生效的bug。
4、增加了孙刘曹中特殊武将不能被交换到其他势力的注释。

3.1版更新内容:
1、修正了未对掌旗官旗帜,城市旗帜,援军,盟军旗帜做修改的bug。
2、修正了编辑武将人物列传未繁体化,换行的bug。
3、增加了修改势力后代武将姓氏的功能。

3.0.1版更新内容:
1、更新了压缩武将图片的算法,让武将图片显示的更加清晰。
2、修正了解压缩更新包,仅支持GBK编码的bug。

3.0版更新内容:
1、新增编辑势力功能。
2、修正编辑武将坐标可能导致游戏异常的bug。
3、修正禁止电脑驿站暴兵的bug(此处bug是由于1.9和1.7的驿站代码不一样导致的)。
4、增加了添加势力兵种对兵营的要求,提高游戏的可玩性。
5、增加了对城墙生命值的修改。
6、增加了武将身份设置功能,方便玩家自定义君主或者继承人。
7、增加了修改器的自动更新功能,3.0及其后续版本若发现修改器有新版本,将会自动更新到最新版本。

注意事项:
因为3.0版改动较大,若使用编辑势力功能,请首先还原游戏数据到初始状态,重新做一次原始备份档,否则编辑势力后,无法还原到游戏原始状态。


说明:
1、修改器安装文件夹路径最好不要包含中文或者空格(有个别用户因为中文和空格路径问题无法启动修改器,调试器会提示:
Error occurred during initialization of VM
java.lang.UnsatisfiedLinkError: no zip in java.library.path


2、正常情况下,请使用“三国全面战争1.9a修改器”运行修改器。

3、非正常情况下,比如修改器无法启动成功,或者一直处于系统初始化状态,或者弹出错误窗口(不包括修改失败),请使用“三国全面战争1.9a修改器(调试器)”运行修改器。调试器启动时会显示一个黑色的dos窗口,如运行时发生错误,错误信息会打印到黑色dos窗口,请截图或者复制错误信息,发送到:http://www.1mod.org/thread-37918-1-1.html,我看见后会及时修正错误。

4、游戏原始备份档下载地址:http://feeling.sourceforge.net/patch/1.9a/default.zip
如果你需要恢复原始数据,而又未作原始档备份,可以从此地址下载。
恢复原始数据的2种方法:
1、解压缩后,复制data目录下的数据文件到游戏中,替换游戏数据文件。
2、复制压缩文件到游戏目录下的/patch/bak目录下,替换掉你自己的原始备份档,然后通过修改器恢复原始备份档。

5、关于杀毒软件误报问题:
(1)如果是国产杀毒软件报病毒,请你不要抱怨修改器,去抱怨那些垃圾杀毒软件吧,技术不过关,老是误报。
(2)如果是国外知名杀毒软件报病毒,比如卡巴斯基,麦咖啡,微软MSE等等,你可以截图发给我。
(3)推荐使用微软MSE,口碑不错,完全免费。


6、修改需理智,请谨慎修改,方能提高游戏的可玩性。










下载地址:http://feeling.sourceforge.net/patch/1.9a/patch_1.9a_3.9.zip

修改器简易教程:http://tieba.baidu.com/p/1418490788


小技巧:
1.如果希望新建武将成为势力君主,可以在创建后和武将势力君主交换。
2.能够更换所属势力的武将不能有后代,也不能是势力君主或者继承人,如果希望更换这些武将的势力,可以先和势力内的没有后代的垃圾武将交换一下,让他们成为自由身,就可以更换武将所属势力了。
posted @ 2012-01-25 22:57 三人行,必有我师焉 阅读(118922) | 评论 (85)编辑 收藏

关于PaletteData的生成:
case Gdip.PixelFormat16bppARGB1555:                                        
case Gdip.PixelFormat16bppRGB555: 
    paletteData = new PaletteData(0x7C00, 0x3E0, 0x1F); 
break;
case Gdip.PixelFormat16bppRGB565: 
    paletteData = new PaletteData(0xF800, 0x7E0, 0x1F); 
break;
case Gdip.PixelFormat24bppRGB: 
    paletteData = new PaletteData(0xFF, 0xFF00, 0xFF0000); 
break;
case Gdip.PixelFormat32bppRGB:
case Gdip.PixelFormat32bppARGB: 
    paletteData = new PaletteData(0xFF00, 0xFF0000, 0xFF000000); 
break;

32位ImageData中的data是以RGBA的顺序存储的。data[0]:red,data[1]:green,data[2]:blue,data[3]:alpha

从byte[]中读取RGB pixel:
public static int getPixelFromRGBA( int depth, byte[] data )
{
        switch ( depth )
        {
            case 32 :
                return ( ( data[0] & 0xFF ) << 24 )
                        + ( ( data[1] & 0xFF ) << 16 )
                        + ( ( data[2] & 0xFF ) << 8 )
                        + ( data[3] & 0xFF );
            case 24 :
                return ( ( data[0] & 0xFF ) << 16 )
                        + ( ( data[1] & 0xFF ) << 8 )
                        + ( data[2] & 0xFF );
            case 16 :
                return ( ( data[1] & 0xFF ) << 8 ) + ( data[0] & 0xFF );
            case 8 :
                return data[0] & 0xFF;
        }
        SWT.error( SWT.ERROR_UNSUPPORTED_DEPTH );
        return 0;
}

从pixel中取出RGB值:
RGB rgb = imagedata.palette.getRGB( pixel );

生成一个空的32位图片:
ImageData dest = new ImageData( width,
                height,
                32,
                new PaletteData( 0xFF00, 0xFF0000, 0xFF000000 ) );

24位透明图片转成32位透明图片:
    public static ImageData convertToRGBA( ImageData src )
    {
        ImageData dest = new ImageData( src.width,
                src.height,
                32,
                new PaletteData( 0xFF00, 0xFF0000, 0xFF000000 ) );

        for ( int x = 0; x < src.width; x++ )
        {
            for ( int y = 0; y < src.height; y++ )
            {
                int pixel = src.getPixel( x, y );
                RGB rgb = src.palette.getRGB( pixel );

                byte[] rgba = new byte[4];

                rgba[0] = (byte) rgb.red;
                rgba[1] = (byte) rgb.green;
                rgba[2] = (byte) rgb.blue;

                if ( pixel == src.transparentPixel )
                {
                    rgba[3] = (byte) ( 0 );
                }
                else
                {
                    rgba[3] = (byte) ( 255 );
                }
                dest.setPixel( x, y, getPixelFromRGBA( 32, rgba ) );
            }
        }
        return dest;
    }

posted @ 2012-01-15 13:49 三人行,必有我师焉 阅读(3552) | 评论 (1)编辑 收藏

SourceForge最近实在太慢了,忍无可忍,每天浪费我大量时间……终于让我不得不投向GitHub的怀抱……
posted @ 2012-01-14 16:33 三人行,必有我师焉 阅读(2266) | 评论 (0)编辑 收藏

JDK1.6的File.createTempFile方法有bug,在我的机器上第一次调用该方法需要耗时5秒时间,换了好几个1.6的版本均有该问题。JDK1.4,1.5则无此问题。

不一定所有的机器都有此问题,不过这儿肯定是有问题的,起码在我的机器上有问题。
posted @ 2012-01-13 15:51 三人行,必有我师焉 阅读(1625) | 评论 (2)编辑 收藏

Java应用定制工厂(以下简称为JCB,Java Customization Builder)是一个针对Java轻量级桌面应用进行精简优化的小工具,使用它可以精简你的jar包,并自动生成一个精简的JRE,也可以使用它生成一个Exe启动引导程序,并且能够对你的Java应用自动做Pack200和Unpack200处理。使用本工具定制的Java桌面应用通常不会超过5M(包含JRE),SWT客户端程序相对于Swing客户端程序更小,一般不会超过3M。

JCB1.0.3主要功能是可以支持
添加Exe的版本信息,并且能够更细节的定义Exe的启动行为,比如是否显示一个splash窗口,是否优先使用精简版的jre,以及增加了对unpack操作的优化,让应用程序第一次运行启动的更快。同时也增加了对系统权限的支持,不再要求需要管理员权限才能运行软件,Guest用户一样的使用。

下一个版本考虑增加一个Au3的编辑器,具有语法高亮和智能辅助功能,可以让高级用户定制自己的Exe行为。

软件主页:http://jcb.sourceforge.net
JCB1.0.3下载地址:http://sourceforge.net/projects/jcb/files/JCB_1.0.3.zip/download


posted @ 2011-12-26 22:13 三人行,必有我师焉 阅读(1599) | 评论 (3)编辑 收藏

在一个线程中启动了一个线程钩子,然后死活拿不到主线程中的消息,全局钩子就没问题。折腾了一下午才发现这个线程钩子是在子线程中启动的,GetCurrentThreadId 方法拿到的不是主线程的threadId,自然也就获取不到主线程的系统消息。Google了好多文章,都没有提到这一点,真TMD郁闷,完全浪费我时间,还以为是我的类库写错了,闹了半天是调用的参数传的不对。
posted @ 2011-12-24 18:58 三人行,必有我师焉 阅读(1778) | 评论 (1)编辑 收藏

同样的API, IShellFolder遍历控制面板里面节点的children,所有的.net代码都能获取,所有的非.net代码都无法获取???

本来还以为是代码的问题,debug一天死活没找到问题,最后在codeproject上找了十几个例子,发现都一样的结果,why? 

难道仅仅对.net的才开放遍历的权限?
posted @ 2011-12-14 12:35 三人行,必有我师焉 阅读(1727) | 评论 (1)编辑 收藏

1、安装VS2008
2、安装 Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1
3、修改SDK的SetEnv.cmd的第146行 
Set "VCTools=%VCRoot%\VC" 为 Set "VCTools=%VCRoot%\VC\Bin"
4、修改build.bat的环境配置
call "C:\Program Files\Microsoft SDKs\Windows\v6.1\Bin\SetEnv" /xp /x86 /Release (不是%PROGRAMFILES%目录,编译字幕为绿色,PROGRAMFILES%目录编译出来的dll有400多k。)
call "%PROGRAMFILES%\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"
posted @ 2011-12-13 13:16 三人行,必有我师焉 阅读(370) | 评论 (0)编辑 收藏

     摘要: Java应用定制工厂(以下简称为JCB,Java Customization Builder)是一个针对Java轻量级桌面应用进行精简优化的小工具,使用它可以精简你的jar包,并自动生成一个精简的JRE,也可以使用它生成一个Exe启动引导程序,并且能够对你的Java应用自动做Pack200和Unpack200处理。使用本工具定制的Java桌面应用通常不会超过10M(包含JRE),SWT客户端程序相对于Swing客户端程序更小,一般不会超过5M。  阅读全文
posted @ 2011-12-12 16:27 三人行,必有我师焉 阅读(6218) | 评论 (12)编辑 收藏

GreenJVMMake 是一个精简JRE的小工具,详细信息请参见项目:

http://code.google.com/p/greenvm/

javaonepackage 是一个集成了GreenJVMMake的IDE,可以方便的生成自定义的JRE,详细信息请参见项目:

http://code.google.com/p/javaonepackage/

比较遗憾的是javaonepackage的作者遗失了代码,导致项目无法进一步的更新,影响了使用。

用以上工具生成的精简JRE通常不超过2M,可是用exe4j将Java应用转换成exe的时候,却会抛出Couldn't load main class,原因是因为exe4j使用自己的jar来加载用户的Jar包,这其中用到了一些反射机制,精简JRE里面当然是不含有多余的class文件的,所以加载必然失败。我们需要做的就是把这些相关的class添加到精简JRE中,这些class包含在 java\lang, java\util, sun\reflect 这三个包中。将这些class添加到精简jre中,大小也仅仅增加数百K而已,在我们可以的接受范围之内。

PS:由于GreenJVMMake的机制是检查运行时加载的class文件,所以它很可能遗失Jar运行所需要的exception class,这会导致运行时JVM的崩溃,用户需手动加载所有可能会产生的exception class,包含runtime的和非runtime的。
posted @ 2010-09-02 10:53 三人行,必有我师焉 阅读(4011) | 评论 (1)编辑 收藏

现在网络上越来越流行.net和java写的客户端的小应用程序,而且后缀是exe。本文讨论的是如何从exe4j封装的exe文件中将自己想要的jar抽取出来。

exe4j一直是一种比较通用的java exe封装工具,但是其并没有将jar转换为本地文件,而是将jar文件通过特殊处理后,封装成的一个exe文件。因此只要我们了解了exe4j的原理,就可以将jar文件从exe文件中提取出来,并通过反编译工具来查看程序代码。

1. 分析Exe4J,得知其在添加文件到.exe时,使用0x88将文件内容Xor,所以第一步,我们需要将原始的数据提取出来:
import java.io.*;
import java.util.*;

public class gen {
    
public static void main(String args[]) throws IOException {
        FileInputStream fin 
= new FileInputStream(args[0]); // 可以将整个exe文件解码
        FileOutputStream fout = new FileOutputStream(args[1]);
        BufferedInputStream bin 
= new BufferedInputStream(fin);
        BufferedOutputStream bout 
= new BufferedOutputStream(fout);
        
int in = 0;
        
do {
            in 
= bin.read();
            
if (in == -1)
                
break;
            in 
^= 0x88;
            bout.write(in);
        } 
while (true);
        bin.close();
        fin.close();
        bout.close();
        fout.close();
    }
}

2.分析提取出来的数据文件,使用WinHex查看其16进制代码。由于Jar文件的开头总是PK开头,并且总包含有manifest.mf文件,并且结尾总是有3个00,同时结尾段有整个Jar包文件的索引,我们可以根据这一特性来分析我们需要的片段。

1、搜索Jar的manifest,然后往前找,找到的第一个PK段,即为一个Jar的开头。
2、查看片段里Jar里的每个class信息,直到最后的文件索引片段。
3、一个Jar的结束片段位于索引片段之后,仍然包含着PK段,并且最后包含着3个00,且这3个00距离PK大概20个字节左右

根据以上3条准则,足以提取整个Jar数据段,然后导入新文件中,并且以zip字段命名,尝试用ZIP解压缩软件打开,看看是否抽取正确。

需要注意的是WinHex非注册版,只能保存280K大小的文件,更大的Jar文件,需要注册版的WinHex才行。
posted @ 2010-08-22 01:03 三人行,必有我师焉 阅读(6769) | 评论 (4)编辑 收藏

1、调用截图工具:

http://iecapt.sourceforge.net/

2、使用Java类库,通过Render生成Java2D图像:

http://www.ldotc.com/Web+Page+Thumbnails+in+Java.html

方法1的优点在于,图像不会失真,速度快,但是缺点是依赖于服务器端的UI,无UI的话,则无法使用。
方法2的优点是不依赖于服务器端的UI,但是图像容易失真,且速度相对较慢……
posted @ 2010-08-17 12:50 三人行,必有我师焉 阅读(1439) | 评论 (0)编辑 收藏

     摘要: TGA or TARGA format is a format for describing bitmap images, it is capable of representing bitmaps ranging from black and white, indexed colour, and RGB colour, the format also supports various compr...  阅读全文
posted @ 2010-03-29 13:02 三人行,必有我师焉 阅读(1989) | 评论 (0)编辑 收藏

Eclipse 是一个集成开发环境,同时又是一个产品平台。这样有时候我们就会碰到一个问题,在开发Debug项目的时候没有问题,打包发布的时候就有问题了,这可如何是好?由于Debug时候和发布后plugin的ClassLoader机制不尽相同,出现这个问题的几率还有会有的。

Java 在这一点上为我们提供了方法,那就是远程Debug。远程Debug一般用于Web开发,或者客户端无法负载大规模的应用时才会运用到,所以Desktop developer 很少会涉及到这个概念。不过Eclipse 的产品平台却让我再次体会到了Java的强大。

关于Eclipse Remote Debug的文章,大家可以从此处学习:

http://www.ibm.com/developerworks/cn/opensource/os-eclipse-javadebug/index.html

Remote Debug 需要2方面的设置,一个是产品平台JVM启动参数的设置,需要让平台以远程Debug的模式启动。然后是在开发端监听产品平台的运行状态。当产品平台运行到断点代码时,Eclipse开发端就会进入Debug界面,像普通debug一样正常debug了。

posted @ 2009-09-10 16:59 三人行,必有我师焉 阅读(2403) | 评论 (1)编辑 收藏

网上关于Eclipse Fragment的资料比较少,引用Eclipse Wiki的一段话:

An Eclipse Fragment is a way of putting your own classes into the "class loader" of another package (basically, it's as though your class was actually in the other package). If you combine this ability of a Fragment with the notion of a plugin's classpath ordering, then you can force your class to load before a like-named class in the original package.

简而言之,Fragment可以利用Eclipse平台的ClassLoader机制替换原有Plugin的某些文件,以便实现自己的功能。如果仅仅是因为Plugin扩展,而需要替换自己项目的某些Plugin,可以参考文章:

http://wiki.eclipse.org/Steps_to_use_Fragments_to_patch_a_plug-in  (来之不易,感兴趣的可以收藏下)

读完这篇文章,你大概就应该能够了解到Fragment的实现分为两部分:Host 和 Patch。这两部分对应的plugin的manifest.mf文件都需要做特殊处理,对于Patch的build方式也要特殊处理,那就是build出来的plugin jar里放置的不是松散的class文件,而是一个特殊的jar文件,这个 jar 文件定义在Host plugin的manifest.mf 的classpath里面。

通常情况下,按照这篇文章的做法是没有问题的。但是在开发阶段,有一种情况可能无法实现class的替换。

我公司的项目是使用perforce进行项目版本控制的,但是perforce比较傻,有些重要的功能没有实现,而又很关键,因此我想自己针对perforce的eclipse plugin做一个fragment,添加自己想要的功能。但是这个plugin没有source code,而我自己的hack也是反编译class文件进行的。所以在我的work space里,并没有Host plugin的 project。结果按照这篇文章的做法,始终不能在workbench debug的状态下,正确load我hack过的class。因为在代码模式下面是可以正确load的,而现在没有代码,在数次检验无果之后,只能针对ClassLoader去思考了。Host文件里要求一个jar文件,我就用PDE 将 fragment export出来,把Fragment plugin jar 包里包含的那个 jar 文件解压出来,放到 patch project 里,然后重新Debug, OK,这次果然没问题了。一个小小的Class Loader问题,真的能够要人命呀……这应当算是Eclipse的一个bug吧,除非是对一个Plugin进行hack,否则一般也不会碰到这种情况。普通的项目开发,肯定会包含Host Plugin的Project。

我的经历权且当做饭后谈资,不足为虑。我这儿想说的是Fragment机制非常好用,也易于Plugin扩展。比如你的项目分为Open Source 和 Commercial 2种的话, Commercial 部分也可以通过Fragment来实现,不一定需要走Extension Point路线。由于网上相关文章不多,还需要自己多多研究,了解其机制。

posted @ 2009-09-10 16:42 三人行,必有我师焉 阅读(1731) | 评论 (0)编辑 收藏

自定义控件通常是从一个Composite或者Canvas继承而来,但是缺省状态下,这两个控件都无法通过键盘的Tab键得到焦点。通过对SWT的debug,我发现要实现该事件,必须满足一个必要条件:为自定义控件安装一个KeyEvent的监听器

实际上我个人认为这出自于SWT的实现者自己的考虑,可能认为如果没有KeyListener,也就意味着没有Key的操作,那么也无需通知Travserse事件,Travserse事件的前提就是Key操作。但是在某些特定情况下我们不需要KeyListener,也可以模拟出KeyEvent的效果,比如通过TraverseListener的keyTraversed方法。总而言之,想通过键盘为某一个自定义控件获取焦点,就老老实实的给控件加一个KeyListener吧。

此处附加一些和Traverse相关的小知识:

1、如何通过键盘的Tab键跳出 Multiple Style 的Text?
        请使用 Ctrl+Tab 组合键。

2、如何通过键盘操作让上一个控件获取焦点?
        请使用 Shift+Tab 或者 Ctrl+Shift+Tab 组合键。

3、Button 可以通过回车键和空格键激活。所以如果有些地方无法使用回车键(比如按钮在Dialog中),可以考虑使用空格键。

4、一些特殊的KeyCode:
        回车键:SWT.KEYPAD_CR
        小键盘的回车键:SWT.TRAVERSE_RETURN
        方向键:SWT.ARROW_UP,SWT.ARROW_DOWN,SWT.ARROW_LEFT,SWT.ARROW_RIGHT
posted @ 2009-04-07 11:22 三人行,必有我师焉 阅读(2233) | 评论 (2)编辑 收藏

项目地址:http://findbugs.sourceforge.net/

用于分析项目代码,自动发现项目潜在Bug,万中无一的好东西,绝对值得下载一试,有兴趣的自己研究。
TeamLeader和PM 强烈推荐,用于Code Review。
posted @ 2009-02-16 00:02 三人行,必有我师焉 阅读(2869) | 评论 (5)编辑 收藏

做不下去了,Vista快把我逼疯了,啥都不支持,随意取消n多API,开发人员能做的事情太有限了,Vista你到底想干什么?

看一看MSDN,全是抱怨Vista的。管理员给出的答案就是按照兼容XP的方式运行,这算哪门子解决方案呀。

Have you tried running your application elevated? (Right Click->Run As Administrator) If this doesn't work try applying an XP SP2 Compatibility shim from the Compatibility Tab in the properties dialog. (Right Click->Properties | Compatibility).

Let me know if this works.

Thanks!


怒呀!!!!!!!

从Win3.1 到 Vista,没有哪个版本比Vista更失败的,无论是从用户角度,还是从开发角度。微软是不是想倒闭不做了?
posted @ 2008-12-05 18:16 三人行,必有我师焉 阅读(2295) | 评论 (5)编辑 收藏

沉寂了一段时间,现在继续SWT Win32 Extension的开发,说实话,最近的进展挺失败的。Black Glossy效果我发现在某些机器上的显示效果非常的卡,我自己的机器却没有任何问题。另外就是我最近在XP下实现了Window Mixer API,用来管理系统声音。Win98,2000,XP都没有问题,结果Vista让我郁闷了,Vista居然取消了Mixer,我哭呀。以下是微软员工给出的答案:

That's because the mixer APIs are virtualized on Windows Vista - you don't get to see the real audio hardware by default, only a virtualized version.  We did this because the vast majority of applications that used the mixer APIs were using them to control their own volume, which is quite rude (it says "I own the box, no other sounds on the system matter").

 You have two choices.  The first is to run your application in XP compatibility mode, in which case you'll be able to access the real audio hardware (please note: you'll see exactly what the hardware provides, which may lead to surprising results).

 The other choice is to use the new Vista audio engine APIs.  either the IAudioEndpointVolume API which allows you access to the master volume for each of the audio endpoints on the machine.  If you really need to access the actual audio controls the IDeviceTopology interface will allow you direct access to the various controls on the audio hardware.

没脾气了,只能专门为Vista实现一套简单的API了。
posted @ 2008-10-23 18:25 三人行,必有我师焉 阅读(2379) | 评论 (5)编辑 收藏

最近做了一个可视化编辑器相关的项目,采用了GMF。现在项目即将进入尾声,以后可能不再接触这个东西,so在还没有忘掉之前,将经验记录下来以供大家参考。 当然做这个项目之前,我对GMF,EMF一无所知,只是对GEF有所了解,所以可能会有些囫囵吞枣的感觉,但是相信我的理解还是会对各位有所帮助。

GMF其实是一个整合了GEF,EMF的自动化生成代码的项目。使用GMF,可以快速的生成一个包含可视化编辑器的项目,这一点网上有文章介绍:15分钟学会GMF。15分钟是夸张了一点,不过15天完成一个可视化项目,对一个熟练的GMF程序员来说,却绝对不是什么难事。但是既然使用了GMF框架,你就不得不面对以下几个问题:

一、既然是框架,自然要遵守框架的规则,GMF是模式驱动设计的,也就是说必须建好模型,才能进行下一步的开发工作。但是国内很多项目,需求总是不断更新,这种情况下,不要轻易使用GMF。
二、使用GMF框架,自然不具备GEF的灵活性,很多地方都被限制住了,不适合做灵活性非常大的图形设计。
三、不得不忍受GMF里大量的bug。

我比较过GMF1.0,2.0,2.1三个版本,其中1.0完全不能容忍,2.0比较傻,2.1还过得去,所以GMF还是值得大家期待的,毕竟是越做越好。


一个比较简单的GMF流程编辑器

个人感觉GMF非常适合做流程编辑器,主要是图形要求简单,并且适合GMF自动布局,兼之对模型要求不高。

在学习GMF之前,有必要研究一下GEF和EMF,其中GEF是必须要有所了解的,而对EMF要求不算太高,能建一个ecore模型,了解emf的commandstack就够了(其实我本人不太喜欢EMF,我更喜欢用自己的模型框架)。

GMF的学习周期大概2周左右,上手到熟练大概需要1个月的时间(我自己的学习周期),当然这期间会碰到各种各样的技术问题,针对不同的case,碰到的问题也会不一样,而我这个系列的文章,主要就是把我所遇到的问题陈列出来,并提供一个解决之道。

附GMF相关资料:
八进制:GMF常见问题
GMF Newsgroup Q and A
posted @ 2008-09-04 14:52 三人行,必有我师焉 阅读(2536) | 评论 (4)编辑 收藏

在Birt Designer中,Binding 是无处不在的,如何正确的是用Birt的Binding呢?首先我们需要知道Birt的Binding Type。

Birt的Binding type随着Birt的版本的升级而越来越丰富,早期的Birt版本只有2种类型:为自己创建一个Data Column Binding,和使用Container的Data Binding,随着CrossTab的出现,进而出现了Cube,ReportItem Reference Binding的概念。

普通的Binding,一般是通过属性编辑器的BindingPage来创建,可以set一个Data set,然后自动创建一个Binding列表。Crosstab和Chart两种类型的Report Item 可以不依赖于Data set,而采用Cube来作为Bingding源。而ReportItem Reference 的概念更是简便了Binding的生成,我们可以让一个ReportItem 直接引用另外一个ReportItem的Binding,而非仅仅是Container的Binding,当然既然是引用,那么你是无法编辑这些Binding的,而且被引用的ReportItem必须包含一个名字,有些ReportItem比如Table本身是可以不设名字的,但在这个地方你就要加上了。

再来说说Binding的设置,我们可以通过Binding Dialog和Binding Page来设置,这两种设置是不同的,如果在Binding Dialog上添加一个Binding,那么这个Binding是添加到这个Report Item的BindingHolder身上,如果在 Binding Page上设置,则Bindnig会添加到自身,让自己成为BindingHolder。
posted @ 2008-07-04 11:47 三人行,必有我师焉 阅读(2603) | 评论 (1)编辑 收藏

通过打开Birt透视图,然后Reset至缺省的Layout,我们能够看到基本的一些View和一个主要的报表可视化编辑器。

先来说一下View,Birt主要的View包含7块:

左上角包含3个视图,Palette,Data Explorer和Resource Explorer,Palette里放置了报表常用的可视化组件,直接将这些组件拖到报表设计器的时候,这些组件并没有作特殊的初始化处理,而从Data Explorer里向设计器拖入一个Dataset会自动生成一个Table,拖入一个Cube会生成一个Crosstab,拖入一个Dataset Column会生成一个DataItem。Data Explorer主要是用来管理和显示数据源。Resource Explorer 老版本里是Library Explorer,新版本则变更为了Resource Explorer,用来特别显示Library,CSS文件,其他文件则不进行特殊处理。

左下角有2个视图,包含Navigator视图和Online视图,Navigator视图是用来建立Birt项目用的,如果是Birt Rcp版本,我们则看不到这个视图,这是IDE版本专有的一个视图,在Rcp版本里没有Project这个概念,直接以文件的形式进行管理。Outline是Birt里较为重要的视图,所有的报表部件都会在这个View里显示并会随着报表的变化实时刷新。

右下角包含了Property Editor 和 Problems两个视图, Property Editor 用来编辑每个可视化报表元素的属性,一般比较常用的属性都会在前几个Tab页里,但是有些属性前几个Tab页里都没有,这时候需要选择Advanced这个Tab页,它里面包含了这个元素所有可用的属性,如果连这儿也没有,那么说明该元素不存在你想要的属性。Problems视图则用来显示报表收集到的一些问题,如果报表校验的时候发生错误,会在此处显示出来。

以上的那些视图属于缺省视图,但还有几个视图也比较有用,一个是Error log视图,一个是Example视图。Error log视图主要是开发用的,当你使用Birt进行二次开发的时候,难免会碰到一些bug,当你感觉有问题的时候,不妨打开error log视图,只要Birt捕捉到了异常,一般都会显示在这个视图里。而Example视图里提供了各种各样的视图,可以Open 和 Save, Open的时候会自动帮你在workspace里建立一个项目,以便你浏览该项目文件。Save则是把这个Example保存到本地某个目录。

说完视图,再来看看Birt可视化的报表设计器,这个设计器包含了五个部分:Layout,Master Page,Script,XML Source,Preview。

Layout为设计器的主要部分,只要通过可视化的拖拽,一个报表就会被自动生成出来,当然要想灵活运用报表设计器,就必须对各个组件的属性了如指掌,Birt提供了丰富灵活的属性供用户选择。MasterPage主要用来设置页眉页脚,以及打印显示之类的功能。Script页面,当你在Layout页面里选中一个元素之后,切换到Script页面,就可以对这个元素进行脚本编码,主要用来监听各种事件,使用Javascript,在采用Web显示里,这些脚本会生效。XML Source则是将这张报表背后的XML source显示出来,用户如果觉得自己对Birt很熟,可以直接在这儿手工修改代码。Preview则是预览Birt报表,Birt会启动Tomcat显示Web运行效果。

基本上Birt还是比较强大的,基本的功能一应俱全。可能你会发现缺少一些更高级花哨的功能,比如flash之类的,其实怎么说呢,不是Birt没有,而是开源版的没有,这个功能在Birt商业版里,可以在 http://www.actuatechina.com/download.php 下载专业版试用。如同IBM,这是这类公司特有的策略,免费上面做收费。喜欢的话,可以试一下专业版,呵呵,可惜网上找不到破解版。

posted @ 2008-06-20 11:48 三人行,必有我师焉 阅读(3817) | 评论 (1)编辑 收藏

Eclipse3.4马上就要Release了,相信Eclipse的fans都已经开始翘首以待,望穿秋水了。不过现在的RC版本用得很不爽呀,性能非常差,Eclipse的惯例就是最后一个月的工作基本上就是对性能做优化,以达到最佳使用效果。

随着Plugin的增多,Eclipse采用了特殊的策略,增加了一个子目录dropins,用来放用户新增加的plugin,而原有的plugins目录,则基本用于系统基本功能,2者的区别就是,前者可以任意添加删除,后者则基本上是一个ReadOnly的状态,添加了就不能再作修改了,Eclipse会将每一个添加的plugin记录下来,以后启动就不再检查这些plugin了。

不过对于我来说,我一直都习惯于使用plugins目录,下了一个插件直接解压,就直接覆盖安装到plugins目录了,而且有一些plugin不支持dropins目录,必须在plugins目录下才能正常工作。不过一旦插件安装失败,想再reset就比较麻烦了。Eclipse不会自动恢复到初始安装状态,经过测试,找到了一个解决方案,用原始的eclipse的文件替代2个目录:configuration和p2目录。plugins文件位置记录在configuration\org.eclipse.equinox.simpleconfigurator\bundles.info里,p2目录里则记录了更多的初始化信息。要想Reset Eclipse3.4,这两个目录必须被恢复到初始化状态,然后就可以正常使用了。

posted @ 2008-06-17 22:30 三人行,必有我师焉 阅读(7807) | 评论 (5)编辑 收藏

最近的项目需要使用报表,因为是RCP应用,所以选择了Birt,用了一下,感觉还可以,就是网上资料少了点,不过以前也研究过一些Eclipse相关技术,这些都不重要了,找了SDK版本Debug,啥研究不出来?

BIRT是一个Eclipse-based开放源代码报表系统。它主要是用在基于Java与J2EE的Web应用程序上。BIRT主要由两部分组成:一个是基于Eclipse的报表设计和一个可以加到你应用服务的运行期组件。BIRT同时也提供一个图形报表制作引擎。

官方主页:http://www.eclipse.org/birt
官方BBS支持:http://www.actuatechina.com/forum2.html

基本上来说Birt功能还是很强大的,支持时下比较流行的WebService,Ajax技术,既可用于Web,也可以用于桌面,更新也算稳定,基本上遵循Eclipse的开发步骤,一个一个大版本,同时支持脚本调用,debug开发等等。唯一不足的就是中国的国情支持得还不够完善,毕竟中国比较特殊,我以前给公司做党务报表,要按照纸质报表画,一分一毫都不能变差,那个变态呀,在电脑上画报表还是拿尺子量。

刚刚开始用,慢慢研究,看了下Birt自带的Example,的确是很强大,做得也很漂亮,自己试着创建一个报表也很简单,希望能够比较快的上手吧。

在网上找了一些资源:
http://blogger.org.cn/blog/more.asp?name=sixsun&id=13933 BIRT 中文指南
http://www.springside.org.cn/docs/reference/Birt.htm BIRT报表
http://www-128.ibm.com/developerworks/cn/opensource/os-ecl-birt/ Birt的IBM DW的中文教程
http://download.eclipse.org/birt/downloads/demos/FirstReport/MyFirstReport.html Birt Flash Demo.
posted @ 2008-06-12 12:02 三人行,必有我师焉 阅读(6191) | 评论 (8)编辑 收藏

  最近很多人问我SWT Extension 项目上的那个不需要的JREExample是如何做出来的。我以前也会执着于这个问题,毕竟如果不依赖于JRE的话,就不需要为用户准备一个容量极大的安装包,但是这种做法看似有利,实则有利有弊。

就我所知,目前把Java程序编译成本机可执行程序的方法有两种,一种是GCJ,免费的,一种是Excelsior JET,商业的。我已经很久没有碰过GCJ了,因为当初用起来实在是非常麻烦,现在的版本如何,我不太清楚。我自己使用的是Excelsior JET,版本为3.7。有一点要注意的的是,Excelsior JET的后续版本好像已经不支持这个功能了,3.7是我所知的最后一个版本,能支持当前所有的Win32平台和早期的Linux(当前比较流行的Ubuntu不支持,因为内核版本过高,不过企业版Redhat没有问题)。不过我是很久以前从0day当下来的,由于0day仓储只保留一年,故现在已经找不到了,我自己的机器上也没有安装包了(有一次大意之下,把整个Download目录全给删掉了,事后悔之晚矣)。

Excelsior JET无非就是用自己的Runtime来代替JRE,只是比JRE更加灵活,根据Java程序具体的依赖来生成对应的Runtime。其实这个Runtime也挺大的,通常10M左右,不过比起JRE,那要小很多了。SWT Extension上的那个例子只有6M,是因为我用ASPack把所有的DLL文件全部压缩过了,体积小了一半。

就我的感觉,Excelsior JETGCJ更加灵活,也更好用,毕竟是商业版的东西,它的网站上曾经有例子将Eclipse 3.0编译成本机程序,不过我当初照着例子试了一遍,没有成功。Excelsior JET的编译过程极为耗时,我上大学的时候,当时机器只有128M内存,编译了一天JRE也没有完成,后来找同学借了根256的内存,这才得以完成。

JAVA代码编译成本机程序的弊端也是有的,那意味着你将无法在线升级,GCJ也许可以,但是Excelsior JET是绝对不行的,这是因为它最后一步要对所有DLL进行链接,如果更换了DLL文件,它会检测出来并报错。

各位看官如果哪位有兴趣,可以自行在网上查找Excelsior JET3.7或其他版本。由于安装包我自己也没有,故无法提供下载,见谅。

posted @ 2008-05-18 15:38 三人行,必有我师焉 阅读(6786) | 评论 (7)编辑 收藏

在SWT Extension中,引入了Function这个类。基本上所有的Win32 JNI库都有这个类,用来直接操纵Win32 的部分API。有了这个Class,我们不用编写JNI,就可以实现某些简单的,甚至是较复杂的Win32 API。这里我们就以EnumWindows这个API举例,看看怎么Java来执行这个Win32 API。
    private static final String FUNTION_ENUMWINDOWS = "EnumWindows";
    
private static final String USER32_LIB = "user32";
    
private static List windowsList = new ArrayList();
    
    
public static int[] enumWindows()
    {
        windowsList.clear();
        Callback callback 
= new Callback(Windows.class"enumWindowsProc"2);
        
int address = callback.getAddress();
        
if (address != 0)
        {
            
try
            {
                Function function 
= new Function(USER32_LIB, FUNTION_ENUMWINDOWS);
                function.invoke_I(address, 
0);
                function.close();
            } 
catch (Exception e)
            {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
            callback.dispose();
        }
        
int[] handles = new int[windowsList.size()];
        
for (int i = 0; i < windowsList.size(); i++)
            handles[i] 
= ((LONG) windowsList.get(i)).value;
        
return handles;
    }

    
private static int enumWindowsProc(int hwnd, int lParam)
    {
        windowsList.add(
new LONG(hwnd));
        
return 1;
    }
EnumWindows是用来遍历Windows窗口的API,它需要传入一个返回boolean值的callback的地址作为参数。实际上在C里面,一个boolean值无非就是是否非0,如果为0,则为false,不为0,则为true。我们只需要new 一个function实例,传入这个API所在的Lib和API名字,然后执行invoke方法就OK了,在Function里面,可以最多执行含有4个简单类型参数的API。

让我们再来看看FindWindowEx这个API,它需要传入2个int变量和2个字符串指针,根据SWT的设计,我们是可以将Java的字符串转换为指针的,因此通过Function我们也可以实现这个API:
    private static final String FUNTION_FINDWINDOWEX = Extension.IsUnicode ? "FindWindowExW"
            : 
"FindWindowExA";
    
private static final String USER32_LIB = "user32";
    
    
public static int findWindowEx(int parent, int hwndChildAfter, String className,
            String windowName)
    {
        
int result = 0;
        
int lpClassName = 0;
        
int lpWindowName = 0;
        
int hHeap = Extension.GetProcessHeap();

        
if (className != null)
        {
            TCHAR buffer 
= new TCHAR(0, className, true);
            
int byteCount = buffer.length() * TCHAR.sizeof;
            lpClassName 
= Extension.HeapAlloc(hHeap, Extension.HEAP_ZERO_MEMORY, byteCount);
            Extension.MoveMemory(lpClassName, buffer, byteCount);
        }
        
if (windowName != null)
        {
            TCHAR buffer 
= new TCHAR(0, windowName, true);
            
int byteCount = buffer.length() * TCHAR.sizeof;
            lpWindowName 
= Extension.HeapAlloc(hHeap, Extension.HEAP_ZERO_MEMORY, byteCount);
            Extension.MoveMemory(lpWindowName, buffer, byteCount);
        }

        
try
        {
            Function function 
= new Function(USER32_LIB, FUNTION_FINDWINDOWEX);
            result 
= function.invoke_I(parent, hwndChildAfter, lpClassName, lpWindowName);
            function.close();
        } 
catch (Exception e)
        {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        
if (lpClassName != 0) Extension.HeapFree(hHeap, 0, lpClassName);
        
if (lpWindowName != 0) Extension.HeapFree(hHeap, 0, lpWindowName);
        
return result;
    }
其实像这种简单参数类型的API,Win32 里还有很多,我们完全不必为其专门编写JNI,只需使用熟悉的Java即可。虽然不是调用全部的API,但大部分常用的API都是没有问题的,关键是如何灵活运用。现在的大型商业RCP应用中,其实多多少少都参和了JNI,用于提升对用户的友好性和软件的执行性能,毕竟Java天生就是客户端开发的矮子。对于JNI,我们既不能一味排斥,也不能滥用,要把握一个平衡点,使之成为Java客户端开发的利器。
posted @ 2008-05-11 19:31 三人行,必有我师焉 阅读(5823) | 评论 (7)编辑 收藏

网上有一篇关于JNI中文问题的文章,写得很详细,http://www.vckbase.com/document/viewdoc/?id=1611

我在这里主要是想说说我碰到的一些问题,并且希望能从各位老大身上获得答案。

因为一直从事Java编程,基本上没有涉及过C++的开发,最近因为开源项目SWT Extension,不得已需要用JNI来实现一些系统Native功能。但是总是需要一些Java字符串对应C++的字符串的问题。一边情况下我都是使用SWT的TCHAR来解决问题,少部分情况需要传递Java String到JNI。然而少部分的这些Case总是在某些问题下出现乱码或者异常。我一直使用的是网上比较流行的中文编码解决方案:
char* jstringToNative( JNIEnv  *env, jstring jstr )
{
  
int length = env->GetStringLength(jstr );
  
const jchar* jcstr = env->GetStringChars(jstr, 0 );
  
char* rtn = (char*)malloc( length*2+1 );
  
int size = 0;
  size 
= WideCharToMultiByte( CP_ACP, 0, (LPCWSTR)jcstr, length, rtn,(length*2+1), NULL, NULL );
  
if( size <= 0 )return NULL;
  env
->ReleaseStringChars(jstr, jcstr );
  rtn[size] 
= 0;
  
return rtn;
}

jstring nativeTojstring( JNIEnv
* env, char* str )
{
  jstring rtn 
= 0;
  
int slen = strlen(str);
  unsigned 
short * buffer = 0;
  
if( slen == 0 )
    rtn 
= env->NewStringUTF( str ); 
  
else
  {
    
int length = MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, NULL, 0 );
    buffer 
= (unsigned short *)malloc( length*2 + 1 );
    
if( MultiByteToWideChar( CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length ) >0 )
      rtn 
= env->NewString(  (jchar*)buffer, length );
  }
  
if( buffer )
  free( buffer );
  
return rtn;
}

一般情况下,这两个函数能够很好的工作。但是在读写注册表时,如果一个key的名字或者value的名字中包含了中文,jstringToNative的解决方案是不正确的,我在网上查了一下其它的关于Java访问注册表的开源项目,发现虽然它们都对字符串进行了处理,但依然存在着中文问题。我进行了数次尝试,但都没有成功。最后到了已经绝望的时候,用开头我提到的那篇文章中里说的最不可能用到的方法将问题成功地解决了:
char* jstringToNative( JNIEnv  *env, jstring jstr )
{
  
const char* pstr = env->GetStringUTFChars(jstr, false);
  
int nLen = MultiByteToWideChar( CP_UTF8, 0, pstr, -1, NULL, NULL );
  LPWSTR lpwsz 
= new WCHAR[nLen];    
  MultiByteToWideChar( CP_UTF8, 
0, pstr, -1, lpwsz, nLen );
  
int nLen1 = WideCharToMultiByte( CP_ACP, 0, lpwsz, nLen, NULL, NULL, NULL, NULL );    
  LPSTR lpsz 
= new CHAR[nLen1];
  
int size = 0;
  size 
= WideCharToMultiByte( CP_ACP, 0, lpwsz, nLen, lpsz, nLen1, NULL, NULL );
  
if( size <= 0 ){
      delete [] lpwsz;
      
return NULL;
  }
  env
->ReleaseStringUTFChars(jstr, pstr );
  delete [] lpwsz;
  
return lpsz;
}
问题虽然解决了,但是我却不求甚解,为什么直接通过env拿到unicode字串,然后转成多字节串不行,但是通过env拿到utf-8字串,然后转成unicode字串,再将这个unicode字串转成多字节串就能工作?

如果大家有兴趣的话,不妨试试,用JNI调用RegOpenKeyEx这个API,就能验证我说的这个Case。哪位老大对JNI比较在行的话,可以在评论中告诉我,不甚感激。
posted @ 2008-05-04 13:17 三人行,必有我师焉 阅读(4022) | 评论 (2)编辑 收藏

Glossy 效果,顾名思义,就是玻璃质的光泽透明的效果,在Windows平台下的应用越来越广泛,从Media Player10开始,它的button已经运用上了此效果。现在但凡是微软新发布的软件,十有八九都带有glossy特效。Glossy 效果使用了Gdi Plus的API,因此在Win98和Win2000下,必须安装gdiplus.dll才能使用,此动态链接库不大,只有700多K,但是想要在自己的应用程序中画出绚丽多彩的效果,那是少不了这个小东西的。关于Glossy效果的描述,可以参见CodeProject上的一片文章WindowsVistaRenderer,http://www.codeproject.com/KB/vista/WindowsVistaRenderer.aspx

Glossy特效有一个重要的组成部分,就是椭圆的光晕,此效果在Linux下可能并没有实现(并未下定论,我还没有深入研究过Linux下的图形库), 所以SWT的Gdip并没有将其公开出来,而是放入custom的API里,也就是说,你可以自行调用此效果的API,但是SWT并不负责为你提供封装, 因此你并不能使用GC来实现该特效,这对我们的界面开发极为不利,自己调用Gdip的API,繁琐不说,还很容易导致JVM退出。

为了能够方便的使用GC来画出此特效,我们不得不采用一些非常规的手段:反射。利用反射我们可以拿到GC的一些内部数据,在这个地方,我们只需要拿到GCData就可以,它包含了画图所需要具备的元素。Glossy效果需要使用PathGradientBrush,我们把这个刷子赋给GCData,就可以使用GC来画出glossy特效了。
 1 public class GCExtension {
 2 
 3     private GC gc;
 4 
 5     private GCData data;
 6 
 7     public GCExtension(GC gc) {
 8         this.gc = gc;
 9         data = getGCData(gc);
10     }
11 
12     public void fillGradientPath(Path path, float[] centerPoint,
13             Color centerColor, int centerColorAlpha, Color[] surroundColors,
14             int[] surroundColorAlphas) {
15         if (gc == null || gc.handle == 0)
16             SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
17         if (path == null || centerPoint == null || centerColor == null
18                 || surroundColorAlphas == null)
19             SWT.error(SWT.ERROR_NULL_ARGUMENT);
20         if (path.handle == 0 || centerPoint.length < 2
21                 || centerColor.handle == 0
22                 || surroundColors.length != surroundColorAlphas.length)
23             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
24         for (int i = 0; i < surroundColors.length; i++) {
25             if (surroundColors[i] == null || surroundColors[i].handle == 0)
26                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
27         }
28 
29         int brush = Gdip.PathGradientBrush_new(path.handle);
30         if (brush == 0)
31             SWT.error(SWT.ERROR_NO_HANDLES);
32         PointF point = new PointF();
33         point.X = centerPoint[0];
34         point.Y = centerPoint[1];
35         Gdip.PathGradientBrush_SetCenterPoint(brush, point);
36 
37         int colorRef = centerColor.handle;
38         int rgb = ((colorRef >> 16& 0xFF| (colorRef & 0xFF00)
39                 | ((colorRef & 0xFF<< 16);
40         int color = Gdip.Color_new(centerColorAlpha << 24 | rgb);
41         if (color == 0)
42             SWT.error(SWT.ERROR_NO_HANDLES);
43         Gdip.PathGradientBrush_SetCenterColor(brush, color);
44         Gdip.Color_delete(color);
45 
46         int[] colors = new int[surroundColors.length];
47         for (int i = 0; i < surroundColors.length; i++) {
48             colorRef = surroundColors[i].handle;
49             rgb = ((colorRef >> 16& 0xFF| (colorRef & 0xFF00)
50                     | ((colorRef & 0xFF<< 16);
51             colors[i] = Gdip.Color_new(surroundColorAlphas[i] << 24 | rgb);
52             if (colors[i] == 0)
53                 SWT.error(SWT.ERROR_NO_HANDLES);
54         }
55         Gdip.PathGradientBrush_SetSurroundColors(brush, colors,
56                 new int[] { colors.length });
57         for (int i = 0; i < surroundColors.length; i++) {
58             Gdip.Color_delete(colors[i]);
59         }
60         data.gdipBrush = brush;
61         boolean advanced = gc.getAdvanced();
62         if (!advanced)
63             gc.setAdvanced(true);
64         int mode = Extension.GetPolyFillMode(gc.handle) == Extension.WINDING ? Gdip.FillModeWinding
65                 : Gdip.FillModeAlternate;
66         Gdip.GraphicsPath_SetFillMode(path.handle, mode);
67         Gdip.Graphics_FillPath(data.gdipGraphics, data.gdipBrush, path.handle);
68         if (!advanced)
69             gc.setAdvanced(false);
70         if (data.gdipBrush != 0) {
71             Gdip.PathGradientBrush_delete(data.gdipBrush);
72             data.gdipBrush = 0;
73         }
74     }
75 
76     private GCData getGCData(GC gc) {
77         GCData data = null;
78         try {
79             Object obj = null;
80             Field field = gc.getClass().getDeclaredField("data");
81             if (field != null) {
82                 field.setAccessible(true);
83                 obj = field.get(gc);
84             }
85             if (obj != null && obj instanceof GCData)
86                 data = (GCData) obj;
87         } catch (Exception e) {
88 
89         }
90         return data;
91     }
92 }

特效截图:

posted @ 2008-04-30 15:54 三人行,必有我师焉 阅读(3540) | 评论 (3)编辑 收藏

SWT Win32 Extension写到现在的状况,在win32 natvie上面已经没有太多花样了,常用的一些功能我都已经做得差不多了,现在主要是做一些自定义的控件,就目前的进度,还只是完成了Shell,Menu,ToolBar 3个部分,还有很多内容可以慢慢完善。不过自从发布了自定义的菜单以后,SWT Win32 Extension的用户群大增,也对我提出了更高的要求。不过现在的主要任务是实现功能,因此代码的质量上肯定是差了点。现有的接口都是我自己通过Example的需求来加的,以后等功能做的完善上,再将现有的架构进行较大的重构,我想应该是一个不错的步骤。毕竟就我一个人做这个东西,还要兼职写Example,Document,测试,网站维护,虽然每天都在加班加点,但还是感觉时间不够用。每天都有用户发邮件来催进度,所以维护这个项目现在真的是让我废寝忘食了,但总体来说还是物有所值,毕竟辛辛苦苦的努力,还是有所回报的。开源嘛,本来就是一种奉献精神,回馈社会,让所有人都来分享自己的成果。

这些天一直忙着写新的Feature,今天写的差不多了,于是回过头来整理Example。本来上个版本我就想接管Eclipse Native的菜单,不过没能如愿,因为Eclipse的菜单都是LazyLoad的。今天又尝试了一下,终于成功的实现了这个功能。做完了才知道其实很简单,所遇到的重重障碍只不过是因为自己的框架老是蹦出新的bug。唉,自己测试自己开发的东西总是有盲点存在,实在是无能为力呀。

从本质上来说,我自定义的菜单和标准菜单控件的代码及事件上的实现基本一致,所以接管Eclipse原生的菜单并不是一件很难的事情,当自定义的菜单接收到一个事件的时候,只需将这个事件转发给Eclipse的原生菜单就好了,一切就是这么简单。重点就是Notify SWT.Selected 和 SWT.Show 事件,前者用来触发Action的行为,后者用来触发Eclipse原生菜单的LazyLoad。

截图如下:
posted @ 2008-04-20 20:37 三人行,必有我师焉 阅读(3371) | 评论 (4)编辑 收藏


The idea is from DotnetMagic, I copied its style, but different implement methods. DotnetMagic uses shell self message circulation, but I use swt event listener. I want to implement the custom style menu for all platforms initially, but it's impossible. It has to use a lot of advanced OS funcitions. If I use pure swt public functions, I can't implement  some features. For example, if I click the menu, the window shell will be deactivated, I click window shell's titlebar, swt event manager doesn't send messages to me. I must use WND Message hook to deal these messages. So I have to add the feature into SWT Win32 Extension, but not a independent project.

Some beautiful snapshots:


Office 2003 Style Menu


VS 2005 Style Menu


Project Download: http://www.swtui.cn/downloads/org.eclipse.swt.win32.extension.zip
JNLP Online Demo: http://www.swtui.cn/jws/example.jnlp

If you don't have installed Java Runtime, you can access it via http://www.swtui.cn/downloads/org.eclipse.swt.win32.extension.example.independence_native.zip

Eclipse Plugin Update Site URL: http://www.swtui.cn/update

 

posted @ 2008-04-02 11:25 三人行,必有我师焉 阅读(3129) | 评论 (5)编辑 收藏

Add Flash Control to SWT Win32 Extension, now you can check out the latest code from CVS and run it.

Project Main Page: http://www.swtui.cn

cvs -d:pserver:anonymous@feeling.cvs.sourceforge.net:/cvsroot/feeling login



Flash Control Listener   


Flash Control Hook Interceptor

 

posted @ 2008-03-14 22:00 三人行,必有我师焉 阅读(2650) | 评论 (0)编辑 收藏

在SWT Win32 Extension 中新添加了窗口系统菜单管理功能,现在可以自定义窗口系统菜单了。
posted @ 2008-03-10 20:38 三人行,必有我师焉 阅读(2262) | 评论 (0)编辑 收藏

窗口是一个应用程序的门面,有一个好看的窗口,用户的评价自然也会大大提高,经过几天的努力,终于在SWT Win32 Extension中加入了自定义的Window Theme,当然目前只完成了一些基本的实现,高级实现依然有待于研究。


Windows XP Theme


Custom Theme

  目前已完成了任意SWT Shell wrapper,系统菜单管理,不规则边框的切边等功能,尚未完成的功能还有很多,比如窗口菜单。未来准备加入用户自定义Theme的接口,只要符合规范都可以进行自动切换。由于功能仍然还有很多地方尚未完成,暂时还没有提供打包下载,有兴趣的话,可以从项目CVS上Checkout出来看看效果。

cvs -d:pserver:anonymous@feeling.cvs.sourceforge.net:/cvsroot/feeling login
posted @ 2008-01-15 14:25 三人行,必有我师焉 阅读(2805) | 评论 (0)编辑 收藏

做Java UI的人应该都很熟悉Look and Feel,Swing拥有的这个功能的确是相对于SWT的一个巨大优势。不过SWT的GC也可以画出自己的UI,前提是这个控件是Custom的,而不是系统级的。Eclipse Presentation 就是对eclipse本身UI提供的一个扩展。本来我还对这个扩展很友好的,不过现在觉得真是一锅粥里掉了个老鼠屎,完全变了味道。

由于闲来无视,把一个1年半前一个德国人写的Eclipse VS L&F Plugin down了下来,无奈bug太多,用户体验也和我个人感觉不一致,索性就把代码check out 出来,作了一些本地修改。不过让我吐血的地方就是,如果我从eclipse default L&F 切换到这个L&F,就问题一堆,从其它的L&F切换就没有问题。Debug了半天,毫无进展,只是发现很多地方都是Null Exception。于是把Eclipse 2.2 L&F的代码翻了出来,经过仔细对照,还是没有找到解决的方案,我就感觉怪怪的,也不知道哪儿出了问题,觉得大概是自己没有把Presentation的代码吃透的原因。于是又回头开始一行行的从有问题的代码处开始Debug,看看我的代码和2.2L&F的代码在运行时到底有什么区别,终于功夫不负有心人,我发现这个代码片断:
    /**
     * Sets the minimized state for this stack. The part may call this method to
     * minimize or restore itself. The minimized state only affects the view
     * when unzoomed.
     *
     * This implementation is specific to the 3.3 presentation's
     * min/max story; otherwise it just forwards the call.
     
*/

    
public void setMinimized(boolean minimized) {
        
// 'Smart' minimize; move the stack to the trim

        Perspective persp = getPage().getActivePerspective();
        
if (Perspective.useNewMinMax(persp)) 
{
原来3.3的L&F做了专门处理,而我的代码和3.3是一致的,和 2.2L&F是不一致的,不过我找了半天,也没有在2.2L&F的代码里找出异样之处,晕的不行。唯一的线索就是
boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX);

可恶的是,IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX 这个静态常量是无法在项目里找到eclipse自身的引用。2.2L&F plugin里也没有,该死的eclipse肯定是写死在代码里了。于是翻出editplus,对eclipse ui workbench的代码多文件搜索了一把,然后开始吐血,它居然把所有的L&F的配置都写在UI Workbench的Preference Page里,这还算什么插件,根本就没有把扩展的接口给出来,还是很重要的一个配置。其实2.2L&F以前只是它的一个内部package,后来重构成一个plugin,3.0L&F到现在依然还是一个package。不过我想既然2.2L&F做成插件了,怎么也改和系统独立开来呀,现在这样就成了一个半吊子的plugin.

Eclipse终于被我bs了一把^_^,林子大了,什么鸟都有。


改过之后L&F自己觉得好用了不少

 

posted @ 2007-12-25 19:19 三人行,必有我师焉 阅读(3549) | 评论 (5)编辑 收藏

新增了4个Low Level Hook:Mouse_LL,Keyboard_LL, JournalRecord,JournalPlayback。由于是Low Level Hook,因此Hook是不依赖于dll,所以可以直接将HookProc在Java程序中编写。这四个钩子都是全局的,而且都很有用处。Mouse_LL,Keyboard_LL,自不必说,用来监听鼠标和键盘的。Journal的2个Hook则是所谓的日志钩子,WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实时事件的回放。

基本上现在用SWT-Extension来实现这四个Hook还是很容易的,做了一下简单的数据封装和事件封装,而且加入了结合体对象和JNI的handle互相转换的函数。用来做C++的一些事情还是比较得心应手的。最新版本的Example提供了一个简单的系统Record/Playback例子,可以记录和回放系统事件。

 

最新的build已经发布到 http://feeling.sourceforge.net 上了,有兴趣的可以下载看看,并且可以作为Eclipse插件使用,支持 eclipse 3.2 以上版本。

PS: Journal的2个Hook Vista已经停止支持了,所以这个例子在Vista 下是看不到的:-(
posted @ 2007-12-12 18:18 三人行,必有我师焉 阅读(1512) | 评论 (2)编辑 收藏

SWT-Extension这个项目做了很久,但一直都没有realease,只是个人做着玩玩,很重要的一个原因是对Windows System Hook的机制没有能够很好地实现出来。Hook本身不算是很难的技术,在C++,C#里都能够很容易的实现,为什么运用Java就那么困难呢?

首先当然主要还是我个人对C++并不在行了,其次就是Java和C++交互的问题了。要想通过C++把数据传给Java,就要通过JNI标准的接口来实现,也就是要通过 JNIENV 来实现,但是HookProc 这个CallBack 是给系统进程调用的,不是给你Java调用的,你说系统进程调用了 HookProc 之后,没法把这个事件传递给Java,那么还有一个方法,用Java不间断的轮循Hook里的数据,这倒是能实现,网上也有一个老外的例子,但不好的地方就是当我系统没工作的时候,你Java还在那儿轮循我干嘛?这不是浪费资源吗?所以呢,这解铃还需系铃人,系统的事件还得让系统来通知你才好。在Java里,有一个IO阻塞,比如当调用System.in.read()的时候,系统就是等待你的输入,如果你不输入,系统就一直等着不工作。还有线程,有wait方法,非要等着其他的线程通过notify把你唤醒你才能工作。在C++里也有这么一套机制:CreateEvent 和WaitForSingleObject,也就是说我先创建一个事件,然后将这个事件置于未激活状态,让它一直等待,将线程阻塞住,当HookProc被系统进程调用的时候,就将这个事件激活,通知Java程序你可以开始干活了,干完活以后再次被阻塞,直到这个Hook被uninstall掉。当然这其中还有一些 C++ 代码的细节性问题,比如怎么让不同的进程共享同一个事件,这里要说明的就是不同的进程可以共享同一个事件,但是不能共享同一个事件句柄,同一个事件,在不同的进程里有不同的句柄,句柄是不能跨进程的。 

我个人认为Hook应当是SWT-Extension里一个很重要的组成部分,SWT本身只能实现线程钩子,对于系统级的钩子无能为力。因为系统机钩子需要实现数据共享操作,还需要DLL入口句柄,这些我已都在SWT-Extension中实现了。唯一让我遗憾的是没有办法实现日志钩子,这是一个很有用的钩子,可以用来记入当前用户行为的操作,然后重新演示,我想如果做自动化测试这个会很有用,或者给游戏软件练功什么的,呵呵。之所以不能实现是因为这个钩子很特别,它要的不是DLL的句柄,而是应用程序的句柄,这没有办法从Java程序里获得,我试过javaw.exe,但是也不行,会导致系统假死。以后有时间在研究吧。

做完Hook,终于可以松一口气,发一个小小的realease了,剩下的就是document工作要做了,一个枯燥无味的工作,就当练习一下英语好了。

这里发一个Hook的截图:




没有了标题栏的Eclipse
最新的Build和代码也可以在 http://feeling.sf.net 上下载了。
posted @ 2007-12-07 15:08 三人行,必有我师焉 阅读(2444) | 评论 (17)编辑 收藏

GitHub |  开源中国社区 |  maven仓库 |  文件格式转换