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