一直以来,觉得java的classloader很不错的,做产品的话,可以将基本的做下来后,将扩展通过classloader的方式来做,将更新的补 丁使用classloader来做,在使用java的网络游戏中,可以将扩展通过classloader的机制,实现动态的更新,省的每次更新要重新下载 客户端,在手机上可是一个很不错的点子。
erlang也支持代码的热部署,java也可以动态更新,那么,C++呢,思来考去,没有想到类似非常完美的方案,只能知道一个基本成型的内容。
现在,不管是windows还是linux,都支持dll,我们可以动态的加载dll,是否可以像java那样,动态的调用函数呢?答案是可以的。需要借 助部分汇编来实现,要知道,我们可能只是知道一个函数的名字,函数参数的类型,参数的个数等等,都是不知道的。因为我更想做一个普遍使用的,而不是要告诉 你:“把某某的头文件给我”。
首先看外表的内容,java的ClassLoader的模拟接口:
ClassLoader.h
01 | #ifndef _CLASSLOADER_H_ |
02 | #define _CLASSLOADER_H_ |
03 | #include "define.h" |
04 | #include "String.h" |
05 | class ClassLoader |
06 | { |
07 | public : |
08 | ClassLoader(); |
09 | ClassLoader(ClassLoader* parent); |
10 | virtual ~ClassLoader(); |
11 | void load( const String& name); |
12 | void * getFun( const String& name); |
13 | private : |
14 | ClassLoader* parent; |
15 | void * dl_handle; |
16 | }; |
17 | #endif /* _CLASSLOADER_H_ */ |
接下来是ClassLoader.cpp
01 | #include "java/lang/ClassLoader.h" |
02 | #include < dlfcn.h > |
03 | #include "System.h" |
04 | ClassLoader::ClassLoader() |
05 | { |
06 | dl_handle = 0; |
07 | } |
08 |
09 | ClassLoader::~ClassLoader() |
10 | { |
11 | if (dl_handle) { |
12 | dlclose(dl_handle); |
13 | } |
14 | dl_handle = 0; |
15 | } |
16 |
17 | ClassLoader::ClassLoader(ClassLoader* parent) |
18 | { |
19 | dl_handle = 0; |
20 | this ->parent = parent; |
21 | } |
22 | void ClassLoader::load( const String& name) |
23 | { |
24 | System::out:: printf ( "ClassLoader::load %s/n" , ( char *)name); |
25 | dl_handle = dlopen(( char *)name, RTLD_LAZY); |
26 | if (!dl_handle) { |
27 | System::out:: printf ( "!!! %s/n" , dlerror()); |
28 | } |
29 | } |
30 |
31 | void * ClassLoader::getFun( const String& name) |
32 | { |
33 | System::out:: printf ( "ClassLoader::getFun %s/n" , name.c_str()); |
34 | void * fun = dlsym(dl_handle, name.c_str()); |
35 | char * error = dlerror(); |
36 | if (error != 0) { |
37 | System::out:: printf ( "error %s/n" , error); |
38 | return 0; |
39 | } else |
40 | return fun; |
41 | } |
java的Class的封装:
Class.h
01 | #ifndef _CLASS_H_ |
02 | #define _CLASS_H_ |
03 | #include "define.h" |
04 | #include "String.h" |
05 | class Object; |
06 | class Method; |
07 | class ClassLoader; |
08 | class Class |
09 | { |
10 | public : |
11 | Class(); |
12 | Class( const Class& aClass); |
13 | virtual ~Class(); |
14 | Object* newInstance(); |
15 | static Class forName( const String& name, boolean initialize, / |
16 | ClassLoader* loader); |
17 | Method* getMethod( const String& name, void * parm); |
18 | ClassLoader* loader; |
19 | String name; |
20 | boolean initialize; |
21 | }; |
22 | #endif /* _CLASS_H_ */ |
java的Class的封装实现:
Class.cpp
01 | #include "java/lang/Class.h" |
02 | #include "java/lang/Object.h" |
03 | #include "java/lang/reflect/Method.h" |
04 | #include "java/lang/ClassLoader.h" |
05 | #include "System.h" |
06 | #include < dlfcn.h > |
07 | Class::Class() |
08 | { |
09 | loader = 0; |
10 | initialize = false ; |
11 | } |
12 |
13 | Class::Class( const Class& aClass) |
14 | { |
15 | this ->name = aClass.name; |
16 | this ->loader = aClass.loader; |
17 | this ->initialize = aClass.initialize; |
18 | } |
19 | Class::~Class() |
20 | { |
21 |
22 | } |
23 |
24 | Class Class::forName( const String& name, boolean initialize, / |
25 | ClassLoader* loader) |
26 | { |
27 | Class ret; |
28 | ret.name = name; |
29 | ret.initialize = initialize; |
30 | ret.loader = loader; |
31 | if (ret.loader != 0) { |
32 | ret.loader->load(name); |
33 | } |
34 | return ret; |
35 | } |
36 |
37 | Object* Class::newInstance() |
38 | { |
39 | System::out::println( this ->name); |
40 | Object* ret = new Object(); |
41 | ret->c.name = this ->name; |
42 | ret->c.initialize = this ->initialize; |
43 | ret->c.loader = this ->loader; |
44 | return ret; |
45 | } |
46 |
47 | Method* Class::getMethod( const String& name, void * parm) |
48 | { |
49 | System::out::println( "Class::getMethod" ); |
50 | System::out::println( this ->name); |
51 | System::out::println(name); |
52 | return new Method( this ->loader->getFun(name)); |
53 | } |
Java的函数Method 的封装:
Method.h
01 | #ifndef _METHOD_H_ |
02 | #define _METHOD_H_ |
03 |
04 | #include "String.h" |
05 | class Object; |
06 | class Method |
07 | { |
08 | public : |
09 | Method(); |
10 | virtual ~Method(); |
11 | Method( void * fun); |
12 | void invoke(Object* obj, ... ); |
13 | void * fun; |
14 | }; |
15 |
16 | #endif /* _METHOD_H_ */ |
上面的内容,没什么好说的,linux下编译,理论上,windows也是可以的,只是调用的函数不一样而已。核心的实现在Method的实现中,毕竟,我们做了这么多,就是为了调用函数。这是实现:
Method.cpp
01 | #include "java/lang/reflect/Method.h" |
02 | #include "java/lang/Object.h" |
03 | #include "stdarg.h" |
04 | Method::Method() |
05 | { |
06 | this ->fun = 0; |
07 | } |
08 |
09 | Method::~Method() |
10 | { |
11 |
12 | } |
13 |
14 | void Method::invoke(Object* obj, ...) |
15 | { |
16 | if ( this ->fun == 0) |
17 | return ; |
18 | typedef void (*func)( void * ...); |
19 | func fun; |
20 | va_list ap; |
21 | int inc = 0; |
22 | va_start (ap, obj); |
23 | void ** arg = 0; |
24 | while ( true ) { |
25 | void * s = va_arg (ap, void *); |
26 | if (s == 0) |
27 | break ; |
28 | inc+=4; |
29 | } |
30 | int argc = inc/4; |
31 | arg = new void *[argc]; |
32 | va_start (ap, obj); |
33 | while ( true ) { |
34 | void * s = va_arg (ap, void *); |
35 | if (s == 0) |
36 | break ; |
37 | argc--; |
38 | arg[argc] = s; |
39 | } |
40 | va_start (ap, obj); |
41 | while ( true ) { |
42 | void * s = va_arg (ap, void *); |
43 | if (s == 0) { |
44 | asm( "call %0/n" : : "m" ( this ->fun)); |
45 | asm( "add %0, %%esp/n" : : "m" (inc)); |
46 | break ; |
47 | } else { |
48 | asm( "push %0/n" : : "m" (arg[argc])); |
49 | } |
50 | argc++; |
51 | } |
52 | delete [] arg; |
53 | va_end (ap); |
54 | } |
55 |
56 | Method::Method( void * fun) |
57 | { |
58 | this ->fun = fun; |
59 | } |
核心的内容总是占用了很大的代码实现,理论上来说,使用汇编会更少一些,大部分的代码都是在对参数进行处理,由于C/C++没有对不定参数参数长度的处理(我没有找到,如果有谁记得找到通知我一声)。这里使用了判断参数是否为0来决定参数是否结束。
首先第一步统计参数的个数,每个参数限定为指针。
第二步复制参数,按照倒着的顺序复制参数,这样使得接下来的调用函数的参数顺序正确。
第三步就是汇编的调用:
va_start(ap, obj); |
将参数按照模拟数序push到堆栈中,然后,再模拟函数调用,最后平衡堆栈。
下面是一个使用的例子:
ClassLoader* loader = new ClassLoader(); |
注意:
上述的方法其实还是有一定的限制的,要求参数必须是指针或者整型,对于传递引用可能会引起问题,另外一个就是我因为使用的0来判断结束,所以如果参数传递了0就可能会出问题,更好地办法是传递一个表示参数个数的值。