存档

‘工作相关’ 分类的存档

对linux和windows的动态库总结

2009年9月26日

windows下:

根据调用方式的不同,对动态库的调用可分为静态调用方式和动态调用方式。

(1)静态调用,也称为隐式调用,由编译系统完成对DLL的加载和应用程序结束时DLL卸载的编码(Windows系统负责对DLL调用次数的计数),调用方式简单,能够满足通常的要求。通常采用的调用方式是把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,想使用DLL中的函数时,只须在源文件中声明一下。 LIB文件包含了每一个DLL导出函数的符号名和可选择的标识号以及DLL文件名,不含有实际的代码(这里的lib文件和静态库是不一样的)。Lib文件包含的信息进入到生成的应用程序中,被调用的DLL文件会在应用程序加载时同时加载在到内存中。

(2)动态调用,即显式调用方式,是由编程者用API函数加载和卸载DLL来达到调用DLL的目的(比如LoadLibrary、GetProcAddress、FreeLibrary),比较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。

静态调用举例:

首先编制一个DLL,简单由2个文件组成:ShowInfo.h ShowInfo.c 代码如下:

ShowInfo.h:

  1. #ifdef SHOWINFO_EXPORTS
  2. #define SHOWINFO_API __declspec(dllexport)
  3. #else
  4. #define SHOWINFO_API __declspec(dllimport)
  5. #endif
  6. SHOWINFO_API void fnShowInfo(void);

ShowInfo.c:

  1. #include "ShowInfo.h"
  2. #include <stdio.h>
  3.  
  4. SHOWINFO_API void fnShowInfo(void)
  5. {
  6. printf("Crab\n");
  7. }

编译时,务必打开SHOWINFO_EXPORTS宏开关,这样DLL里面的函数申明就会带有__declspec(dllexport)关键字了(否则生成不了lib文件),构建完后,就生成了一个lib文件,一个dll文件

使用这个DLL的时候,需要做如下事情,第一:导入ShowInfo.h,但不要打开SHOWINFO_EXPORTS宏开关,这样函数声明就变成了__declspec(dllimport);第二:编译时导入刚才生成的lib包;第三:将dll放在exe的所在目录,或者VC的工程目录。

上面这个制作DLL和使用DLL的方式,其实就是JNI的方式,可以拿javah生成的对比下。

还有一种方式是用(.DEF)模块定义文件,个人暂不倾向这个,呵呵。

linux下:

linux环境要干净透明些,无需在函数名上挂狗牌。如果要制作动态库,写好C文件后,直接用 gcc ShowInfo.c -fPIC -shared -o libShowInfo.so 就搞定了。也就是加个-fPIC -shared 参数,指定GCC不要链接成可执行文件,而是动态库。这里要注意几点:一、libShowInfo.so的命名方式,一定要 lib*.so这样的命名;二、-shared是指编译成动态库,-fPIC学问稍微大些,如果不指定它,那么其他程序调用这个so的时候,它会为每个程序维护一个副本,其实,对于小型的dll,完全没有必要指定-fPIC,效率会非常高的,但是对于大型的dll,那么还是指定下吧。使用就更简单了,gcc Main.c -L. -lShowInfo -o Main,其中-L指明让GCC去哪里找动态库,-l则是指定动态库的名字(lib*.so中间的*就是名字),顺提一句,如要要使用数学类的方法,得加上 -lm,是指用一个数学动态库。

上面讲的,其实就是类似于windows的静态调用,在linux下,同样也是可以用动态方式加载和使用dll了,这里就不讲了。另外,在Linux里面,可以采用ldd命令来检查程序所依赖的共享库。

C/C++ , , , ,

彻底理解JAVA的编码转换问题

2009年9月16日

最近在写一些JNI接口的时候,碰到了一系列的编码转换问题,进行了一番总结,总算是彻底搞懂了。

在JAVA中处理字符串编码,我们可以理解为JVM里面的形态和外面的形态2种。比如:

首先,我们来探讨下编译期的编码问题:

  1. public class Main 
  2. {
  3.     String testStr = "你好";
  4. }

假设该java文件是GBK编码的,我们用如下命令:
javac -encoding GBK Main.java
假设该java文件是UTF-8编码的,我们用如下命令:
javac -encoding UTF-8 Main.java

虽然2个Main.java文件编码不一样,但是编译好的class 2进制文件,是一模一样的。那么是不是编译成UNICODE编码了呢?我看了下2进制文件里面的“你好”并非UNICODE编码,估计是处于安全考虑,进行了一些转换。不过,有一点我们可以总结了:N份源文件有着不同的编码格式,它们编译出来的class文件,都是统一的。至于中文被编译成什么编码格式了,我们不得而知,但可以猜测是UNICODE的变型。

上面的str变量就是字符串的内在形态,”你好”就是外在形态。str对象的任何操作都是基于内在形态的,我们把外形形态的字符串放入String类操作的时候,就要负责编码的转换。你可以看出上面这一行代码的编码转换吗?其实是有的:这里的”你好”是GBK编码的,因为它是Main.java里面的文字,而Main.java是GBK的;但是str的内部编码可不是GBK哦,所以这里JVM已经进行了转码。

上面是将中文写在代码里面,如果从文件中读入中文呢,我们是将再来看个例子:

  1. BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
  2. System.out.println(reader.readLine());

上面这个例子是从文件test.txt中读入一行打印,会不会出现乱码呢?我们来看一些情况:

注:下面4个试验,是用Eclipse来完成的
1.test.txt是UTF-8的,代码文件是GBK的(乱码)
2.test.txt是GBK的,代码文件是UTF-8的(乱码)
3.test.txt是UTF-8的,代码文件是UTF-8的(正常)
4.test.txt是GBK的,代码文件是GBK的(正常)

先说,为何乱码,道理非常简单,因为没有人告诉FileReader以什么编码去读test.txt。那么FileReader到底是用什么编码在读test.txt呢?
在Eclipse中,Eclipse会根据源码的编码格式来读,所以只要源码即:Main.java和test.txt编码一致即可。
但是在,命令行中,是根据操作系统的默认编码来读的,感谢索尔的提醒。

那么,我们为了不乱码,就需要告诉JAVA,外面文件的编码格式是什么,让JVM用正确的解码方法去解码字符串。于是,代码改为:

  1. BufferedReader reader = new BufferedReader(new InputStreamReader(
  2.                 new FileInputStream("test.txt"), "UTF-8"));
  3. System.out.println(reader.readLine());

这里的UTF-8就是test.txt的编码格式。程序运行正常。
上面2个例子,一个是通过代码里面写中文,一个是通过读取文件获得中文,最终这些中文在JVM的运行时,是以什么形式储存在内存中的呢?答案是:UNICODE,2个字节。

现在我们考虑一个复杂的场景:我们这个系统是一个中间站,需要把A模块的GBK字符串送到B模块处理,B模块是用UTF-8处理这些字符串的,当B模块处理完后,我们接收这些字符串,然后转发给A模块。这里就有着几个转码的步骤:

1.先从A模块取下字符串,我们告诉String,getBytesFromA()返回的是GBK编码的字节序,执行完后,所有得到的字节被解析为GBK编码,再转码成UNICODE编码,存储在strFromA对象中
String strFromA = new String(getBytesFromA(), “GBK”);
2.将strFromA从UNICODE转码成 UTF-8 ,然后传入sendBytesToB
sendBytesToB(strFromA .getBytes(”UTF-8″));
3.B模块执行完后,我们用getBytesFromB()从B模块取回字符串,告诉String用UTF-8来读取这些字节,同样所有得到的字节被解析为UTF-8编码,再转码成UNICODE编码,存储在strFromB对象中
String strFromB = new String(getBytesFromB(), “UTF-8″);
4.将strFromB从UNICODE转码成 GBK ,然后传入sendBytesToA
sendBytesToA(strFromB .getBytes(”GBK”));

总结如下:
1.JAVA在运行时,所有的编码都为UNICODE,存在char类型中,2个字节。如果遇到3个字节的UNICODE,用2个char存
2.回想,一个程序在运行期,内存里面的字符串从哪里来?2个途径,1.写在源码里面;2.运行期从文件或者网络等读取。对于1,我们需要在编译期告诉javac encoding是什么;对于2,我们需要告诉String或者FileReader等,读进来的字符串是何编码。一旦进入内存,将全部转为UNICODE
3.在运行期的字符串,可以通过getBytes方法来转化任意编码,如果不指定,那么就是用默认的编码(Eclipse的默认编码是源文件的编码,命令行的默认编码是操作系统的编码)。比如,一个字符串要保存到文件中,如果要存成UTF-8,最好在witer中告诉JAVA,你要保存为UTF-8。
4.最后总结,大型项目,切记编码问题,检视所有的外部来源是否都被指定编码格式了

java基础

CUnit的精简测试框架

2009年7月24日

CUnit使用起来需要去写一些和CUnit相关的代码才能跑起来,没有JUnit4.0那种POJO的方式来的这么清爽,于是我定义了一些宏,将和业务无关的CUnit代码屏蔽起来,使用的时候,针对每个.C文件写一个测试suit文件,让它们一起包含Test.h即可。

200907241927

Test.h出了包含一些头文件以外,还有一串宏,定义如下(下面的中文是举例):

  1. #include "include/Basic.h"
  2.  
  3. #define INITIALIZE_CUNIT() \
  4.  if (CUE_SUCCESS != CU_initialize_registry()) \
  5.   return CU_get_error();
  6.  
  7. #define FINISH_CUNIT() \
  8.  CU_basic_set_mode(CU_BRM_VERBOSE); \
  9.  CU_basic_run_tests(); \
  10.  CU_cleanup_registry(); \
  11.  return CU_get_error();
  12.  
  13. #define NewTestSuit(SuitName) \
  14.  CU_pSuite pSuite = NULL; \
  15.  pSuite = CU_add_suite("Suite_Of_"#SuitName, before, after);
  16.  
  17. #define AddTest(FunctionName) CU_add_test(pSuite, #FunctionName, FunctionName);
  18.  
  19. #define GoTests() \
  20.  test模块1(); \
  21.  test模块2(); \
  22.  test模块3();
  23.  
  24. void test模块1(void);
  25. void test模块2(void);
  26. void test模块3(void);

下面是TestMain.c,以后可以不用动。

  1. #include "Test.h"
  2. int main(){
  3.  INITIALIZE_CUNIT();
  4.  GoTests();
  5.  FINISH_CUNIT();
  6. }

下面给出 Test模块1.c 的例子

  1. #include "Test.h"
  2. static int before(void){
  3.  // 该模块的方法运行前的准备工作写在这里
  4.  return 0;
  5. }
  6. static int after(void){
  7.  // 运行完的销毁工作也在这里
  8.  return 0;
  9. }
  10. void test_方法1(void)
  11. {
  12.  CU_ASSERT_EQUAL(1, 1);
  13. }
  14. void test_方法2(void)
  15. {
  16.  CU_ASSERT_EQUAL(1, 1);
  17. }
  18. void test_方法3(void)
  19. {
  20.  CU_ASSERT_EQUAL(1, 1);
  21. }
  22.  
  23. void test模块1()
  24. {
  25.  NewTestSuit(模块1);
  26.  AddTest(test_方法1);
  27.  AddTest(test_方法2);
  28.  AddTest(test_方法3);
  29. }

需要注意的是,和JUnit4.0有点不同:
JUnit4.0运行流程为:before->方法1->after->before->方法2->after->before->方法3->after
CUnit的运行流程为: before->方法1->方法2->方法3->after

C/C++

打印GB2312编码中的所有汉字

2009年6月30日
  1. for(int j=0xB0; j<0xF8; j++)
  2.         {
  3.             for(int i=0xA1;i<0xFF;i++)
  4.             {
  5.                 byte[] bytes = new byte[2];
  6.                 bytes[0] = (byte)j;
  7.                 bytes[1] = (byte)i;
  8.                 System.out.println(new String(bytes));
  9.             }
  10.         }

java基础

TOMCAT源码总结一

2009年6月28日

首先不得不说这个类org.apache.tomcat.util.net.JIoEndpoint,它负责所有的TCP请求连接,实现了一个服务器模式,启用一个后台监听线程,负责接收到来的socket,然后从线程池中取出响应的worker,扔给worker进行处理,自己继续监听。其次worker是一个负责处理socket的一个线程,就是它带着用户的请求开始进入Tomcat世界的,默认的worker总共有200个,即:最多200个线程。当处理完一个请求的时候,这个线程并不会销毁,而是进入wait阻塞,这个线程的对象也不会销毁,是进入了一个栈里面,对应workstack那个数据结构。每当接收线程拿到一个socket的时候,就先从栈里面拿出一个已有的线程对象,然后就利用该对象的assign方法,将这个socket给它,并调用notify重新唤醒这个worker的处理线程。以后我们做小型服务器的时候,可以借鉴它的实现方式。正在研究多线程的朋友,这个类绝对让你可以学的透彻!

相对应的还有一个org.apache.tomcat.util.net.NioEndpoint,这个和前面那个功能差不多,但是用了NIO包里的API,有一个最大的区别就是,接收线程接收一个socket之后,可能会将这个socket先放入缓存池,然后worker从池里面拿socket去处理,比前面那个类看起来功能和性能都会提升很多,不过代码行有2K多,相当复杂,设计不够精巧,有兴趣可以去研究下。

org.apache.tomcat.util.buf.MessageBytes:这是一个接近底层的字符串处理类,为什么说是接近底层,是因为socket接收进来的都是字节类型,而java用的是char或者String,这之间的转换涉及到编码问题和性能问题,所以凡是socket收进来的信息,全部都用这个类表示,只有当要输出字符串的时候,才会将里面的字节进行转换,实现一种延迟加载的懒模式,被Tomcat底层所使用的Request类,就是大量使用了这个类来存放数据。我们来小小窥视一下,Request类:

  1. private MessageBytes methodMB = MessageBytes.newInstance();
  2.     private MessageBytes unparsedURIMB = MessageBytes.newInstance();
  3.     private MessageBytes uriMB = MessageBytes.newInstance();
  4.     private MessageBytes decodedUriMB = MessageBytes.newInstance();
  5.     private MessageBytes queryMB = MessageBytes.newInstance();
  6.     private MessageBytes protoMB = MessageBytes.newInstance();
  7.  
  8.     // remote address/host
  9.     private MessageBytes remoteAddrMB = MessageBytes.newInstance();
  10.     private MessageBytes localNameMB = MessageBytes.newInstance();
  11.     private MessageBytes remoteHostMB = MessageBytes.newInstance();
  12.     private MessageBytes localAddrMB = MessageBytes.newInstance();
  13.     
  14.     private MimeHeaders headers = new MimeHeaders();

或许大家会觉得,构造出这么多的类,性能会高到哪里去,其实不是这样的,不停的构造和销毁对象的确会损耗相当的性能,但是一个对象被构造出来,可以重复利用,那就相当完美了,这个类就是如此的设计,其中有一个回收资源的方法,叫recycle(),这个方法可以清空里面的数组,清空里面的对象,而不会销毁自己本身,因为使用它的对象,只要调用recycle,以后又可以重复使用了。

MessageBytes其实内置了2个重要的类,org.apache.tomcat.util.buf.ByteChunk和org.apache.tomcat.util.buf.CharChunk,这2个类带我们回到了C时代,为什么这么说?因为它简直就是一个字符串处理类,一些眼熟的算法全部映入眼帘,比如字符转匹配算法,indexOf,startsWith,判断字符转是否相等,查找字符,等等,比之JDK提供的性能更好,功能更强大(这句话说过了,呵呵)

还有一个实用的值得学习的数据结构是,org.apache.tomcat.util.buf.Ascii,如果知道表驱动的朋友们,一定对这个类很熟悉了,判断大小写?判断是不是英文单词?判断是不是空白符?判断是不是数字,将字节类型转换为int、long类型,大小写转换,等等。这些都是大学计算机课程的课后练习题。

tomcat研究