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进行传输。

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

  • 相关阅读:
    According to TLD or attribute directive in tag file, attribute end does not accept any expressions
    Several ports (8080, 8009) required by Tomcat v6.0 Server at localhost are already in use.
    sql注入漏洞
    Servlet—简单的管理系统
    ServletContext与网站计数器
    VS2010+ICE3.5运行官方demo报错----std::bad_alloc
    java 使用相对路径读取文件
    shell编程 if 注意事项
    Ubuntu12.04下eclipse提示框黑色背景色的修改方法
    解决Ubuntu环境变量错误导致无法正常登录
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/4548336.html
Copyright © 2011-2022 走看看