zoukankan      html  css  js  c++  java
  • linux下go的动态链接库的使用

    转自:http://blog.csdn.net/xtxy/article/details/21328143

    在使用lua进行服务器端游戏逻辑开发时,发现了LUA的各种不方便的地方,不能编译检查,不能断点调试,笔误的函数和变量不提示出错等等,所以有了全部使用go来做服务器端开发的想法。

    如果不需要热更新,那使用go开发服务器逻辑是很轻松的,而游戏服务器特别是页游,一般都需要支持热更新,所以我决定使用go的动态链接库方式来实现,也就是底层框架是go,上层逻辑是go的动态链接库。go原生不支持动态链接库,在查阅了很多文章之后,决定使用gccgo来实现。

    经过了大约一周的时间,终于把框架搭建起来了,期间遇到了一些比较坑的问题,记录在此,以便以后不会再犯,也可以帮助其他有同样需求的同学快速搭建这样的框架。

    这个例子需要了解go目录构建和环境变量的知识,如果不了解,可以先看看网上的文章,很简单的。

    整个框架搭建好了之后的方式是GOCCGO,就是GO -> C -- dll --> C -> GO。底层和最上层都是GO,中间使用了C来提供动态链接库的方式。

    1 首先来看底层特殊的地方,就是GO->C的部分。

    go文件如下:

    // script.go
    
    package script
    
    // 1
    //extern initDll
    func c_initDll(string, string)
    
    //extern runDll
    func c_runDll(string, map[string]interface{}) string
    
    var dataMap map[string]interface{} // 2
    
    func Init(fileName string, funcName string) {
        dataMap = make(map[string]interface{})
    
        return c_initDll(fileName, funcName)
    }
    
    func Run(buf []byte) string {
        str := string(buf)
    
        retStr := c_runDll(str, dataMap)
    
        return retStr
    }

    其中1处是gccgo的特殊写法,//extern funcName就是说有一个c的函数,在go中使用c_funcName来调用它。(是不是能使用其他名字我没有试过)

    2处是声明了一个map结构,这个后面再说,此时先不管。

    文件中的Init函数在程序启动时初始化时调用,用来加载dll;Run函数就是调用dll来处理每次的游戏逻辑了。

    对应的c文件如下:

    // script.c
    
    #include <dlfcn.h>
    
    struct __go_string {    // 1
        const unsigned char *__data;
        int __length;
    };
    
    struct __go_string (*dllEntry)(struct __go_string, int); // 2
    
    void loadDll(struct __go_string fileName, struct __go_string funcName)
    {
        void *dll = dlopen(fileName.__data, RTLD_LAZY); // 3
        if(!dll)
        {
            // error
        }
        
        dlerror();
        
        *(void **)(&dllEntry) = dlsym(dll, funcName.__data); // 4
        if(NULL != dlerror())
        {
            // error
        }
    }
    
    struct __go_string runDll(struct __go_string inData, void *dataMap)
    {
        return dllEntry(inData, dataMap); // 5
    }

    其中1处是go中string类型在c中的表示,string在c中是一个结构体。

    2处是动态链接库的主函数,动态链接库导出这个函数来对游戏逻辑进行处理。

    3处和4处分别是加载动态链接库和获取函数地址的代码。

    5处就是调用动态链接库的主函数的地方,例子中传递参数为string和一个指针(其实就是Map的地址),返回值也是一个string。

    编译他们的方法如下:

    gccgo -o testc.o -c test.c

    gccgo -o testgo.o -c test.go

    ar cr libtest.a testgo.o testc.o

    最后编译出来的libtest.a,需要放到pkg目录下面相应的地方。也就是你使用go install命令时它将生成出来的库放到哪里,你就讲libtest.a拷贝到哪里。

    GO->C的部分就已经完成了。

    2 动态链接库的接口, C->GO的部分

    c文件如下:

    // dll.c
    
    struct __go_string {
        const unsigned char *__data;
        int __length;
    };
    
    extern struct __go_string go_entry(struct __go_string, int, void *) 
    __asm__ ("GameLogic_ctrl.Entry"); // 1
    
    // void __attach(void) __attribute__ ((constructor)); // 2
    
    struct __go_string centry(struct __go_string input, void *dataMap)
    {
        return go_entry(input, dataMap);
    }

    1处是C调用GO函数的声明,Entry是函数名称,GamLogic_ctrl是包名称,如果这个地方出错的话,你可以用nm命令查看go生成的lib文件,将包名称修改正确。

    2是动态链接库的初始化函数声明,如果需要,就可以添加一个,调用go函数,做一些初始化操作。

    对应的go文件如下:

    // dllMain.go
    
    package ctrl
    
    func Entry(inData string, index int, dataMap map[string]interface{}) string {
        return ""
    }

    和普通的go文件写法是一样的。

    编译他们的方法如下:

    go install -compiler=gccgo -gccgoflags='-fPIC' // 将所有的go文件编译出来

    gccgo -o libcdll.a -c dll.c -fPIC // 将dll入口的c文件编译出来

    然后将libcdll.a拷贝到其他go生成的库的目录下,执行:

    gccgo -shared -o dllMain.so *.a // 将单个库文件编译成最终的动态链接库

    整个框架动态链接库部分就完成了。

    下面我说一下使用这个框架需要注意的地方:

    1. 编译动态链接库时除了c部分外,其他不要直接使用gccgo命令,因为一些外部库,比如访问mongodb的mgo库,你是无法使用gccgo编译出来的(或者说很困难),最简单的方法就是用go install -compiler=gccgo的方式;

    2. 动态链接库不会执行初始化部分,也就是说在package里面的init函数并不会被调用;

    3. 和上面一条比较像,import的外部库(包括系统库),比如fmt并不会被import进来。解决方法是在基础框架部分(也就是可执行文件),将动态链接库需要用到的库全部import进来;

    4. 动态链接库里面申请的内存在调用完成后会被释放。比如在动态链接库主函数文件(上面最后一个包含Entry函数的文件)里面加一个包里面的全局变量:var tmpMap = make(map[string]interface{}),如果在Entry函数里面访问tmpMap,你会发现tmpMap指向的map空间已经被释放了,访问它会报段错误。解决方法是在基础框架里声明一个map结构,然后传递给动态链接库使用,这个map数据就不会被释放。也就是在我列举的第一个文件(script.go)的注释2处,声明了一个dataMap,就是起这个作用。

    5. 在gccgo的说明文档里,slice是可以在c和go之间作为函数参数传递的,但是我试过了之后,发现有问题。首先是go的slice到c里面之后,并不是一个struct,而是一个struct *,就是说是一个指针,而且我将指针指向的内存打印出来,和文档上说的完全对不上,找不到字符数组,找不到len的位置,cap的位置似乎在这个指针向上偏移4个字节……我被搞蒙了,就没有研究了,用string来代替slice进行传输。

    由于是自己摸索出来的东西,所以可能有一些地方不正确,希望大家指正,我随时修改。

  • 相关阅读:
    反射(五)之动态代理的作用
    反射(四)之反射在开发中的适用场景及利弊
    反射(三)之通过反射获取构造方法, 成员变量, 成员方法
    反射(二)之反射机制
    反射(一)之初探反射
    java多线程(五)之总结(转)
    java多线程(四)之同步机制
    java多线程(三)之售票案例
    java多线程(二)之实现Runnable接口
    java多线程(一)之继承Thread类
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/4548336.html
Copyright © 2011-2022 走看看