使用C++对java的classloader进行模拟

一直以来,觉得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就可能会出问题,更好地办法是传递一个表示参数个数的值。

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据