Archive for January, 2018

Python 调用 C++ DLL

January 28th, 2018

update 2018-2-4

早些时候用Pyqt写了一个app,最近需要加入一个License的认证模块,然而这个认证模块是拿C++写的,并只给出了dll、lib和header。。。网上查找了一些资料,一种方法是采用ctypes 去加载dll;另外一种就是编写一个PyObject 然后编译成pyd模块供python调用。

这里我采用第一种方式,他们提供给我的模块是_stdll的方式,因为然后我又把这个dll包了一层,以__cdecl方式导出。

#ifdef LICENSEDLL_EXPORTS
#define LICENSEDLL_API extern "C" __declspec(dllexport)
#else
#define LICENSEDLL_API __declspec(dllimport)
#endif

LICENSEDLL_API bool  verify(char *module, char *errorMsg);
.....

然后我将这个工程编译生成LicenseDLL.dll。下面就是python方面的任务了,我引入ctypes,然后利用CDLL加载LicenseDLL.dll,利用dll.verify.argtypes,我声明verify函数具有两个参数,其中pinfo为100个字节的数组,并将其传入verify函数。该函数返回后,写入ErrorInfo,使用QString(str(ErrorInfo.raw))将其转换成QString对象,完成对于dll的调用。

def CheckLicenseInfo():
    from ctypes import *
    dll = CDLL(app.g_pwd + os.sep + 'LicenseDLL.dll')
    dll.verify.argtypes = [c_char_p,c_char_p]
    pinfo = c_char * 100
    ErrorInfo = pinfo()

    if not dll.verify('DESIGNAPP', ErrorInfo):
        QMessageBox.critical(None, 'Error', QString(str(ErrorInfo.raw)))
        exit()

但是这种方式有个最大的问题:由于直接读dll,可能会存在dll依赖链的问题,比如我操作的时候就遇到Windows Error 126和127的问题,126应该就是dll依赖缺失,需要使用depandency的问题,127有可能是dll本身的问题,没有把里面的函数符号导出。127比较复杂,有时候遇到挺懵逼的….. 

编写python扩展也是一种可行的方式,可以编译成pyd的形式,在python下直接import。因为python要调用C++的模块,那么必然涉及到python对象向C++传值和传地址的问题,也涉及到C++返回值向python传值的问题,直接上源码来一步一步说明吧。

编写代码首先要引入Python.h Python.lib 默认用安装包安装的是没有python_d.lib的,当然也要引入需要wapper的头文件,我根据官方提供的spamError例子代码修改为我的代码,总的框架代码有PyMODINIT_FUNC initXXXXX(void),static PyMethodDef LicenseMethods[]={…},还有常规的warpper代码static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args)。PyMODINIT_FUNC initXXXXX(void)为固定格式,把XXXXX换成我们编写的模块即可。

#include "Python.h"
#include "LicenseProve.h"

static PyObject *LicenseError;

static PyObject *isHavingLicense(PyObject *self, PyObject *args)
{
    const char *name;
	char *err;
    if (!PyArg_ParseTuple(args, "s|s", &name,&err))
        return NULL;

    std::string modelName(name);
    std::string errorMsg(err);

    bool ret = Allone::License::CLicenseProve::getInstance()->isHavingLicense(modelName,errorMsg);

	//if(ret == false)
	//	return Py_False;
    //return Py_True;
    return Py_BuildValue("(iss)",(int)ret,modelName.c_str(),errorMsg.c_str());
}

static PyObject *getLicenseType(PyObject *self, PyObject *args)
{
    size_t ret = Allone::License::CLicenseProve::getInstance()->getLicenseType();
    return PyInt_FromSize_t(ret);
}

static PyObject *getLicenseInfo(PyObject *self, PyObject *args){...}
static PyObject *getRegisterInfo(PyObject *self, PyObject *args){...}
static PyObject *getAppendInfo(PyObject *self, PyObject *args){...}

static PyMethodDef LicenseMethods[] = {
    {"isHavingLicense",  isHavingLicense, METH_VARARGS,"Having License in this Computer?"},
	{"getLicenseType", getLicenseType, METH_NOARGS,"Get License type"},
	{"getLicenseInfo", getLicenseInfo, METH_NOARGS,"Get License Info"},
	{"getRegisterInfo", getRegisterInfo, METH_NOARGS,"Get License Register Info"},
	{"getAppendInfo", getAppendInfo, METH_NOARGS,"Get License Append Info"},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC initLicense(void)
{
    PyObject *m;

    m = Py_InitModule("License", LicenseMethods);
    if (m == NULL)
        return;

    LicenseError = PyErr_NewException("License.error", NULL, NULL);
    Py_INCREF(LicenseError);
    PyModule_AddObject(m, "error", LicenseError);
}

在LicenseMethods需要加入我们编写的函数,双引号里面的是python调用的函数名,第二个参数是c++函数里面的函数名,第三个是声明传入参数类型,METH_VARARGS为可变参数,METH_NOARGS是没有参数。这个实质上就是一个参数表,连接python和c++端的实现。c++端的实现都写成static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args),我们需要的参数都是通过args以Tuple传入,调用PyArg_ParseTuple(args, “s|s”, &name,&err),可以拿到传入的值,”s|s”表示元祖里每个参数的类型,常用类型可以在官方wiki中找到,s就是代表string,i就是代表integer等等。后面就是根据指定的类型,传入到name和error中,C++中可以使用参数传值,但是python的参数传入的都是值,因此我将修改的的值都放置到一个tuple中返回给python,也就是使用Py_BuildValue返回,参数和PyArg_ParseTuple类似,第一个是每个参数的类型,后面是参数值。如果返回值简单,我们也可以直接返回Py_False等。

在PyMODINIT_FUNC initLicense(void)中,大致也分为Py_InitModule初始化,PyErr_NewException注册,在异常处理这一块,我没有细看,但是这个应该是处理各种C++发生的异常,由于python不用管理内存,但是在C++中我们就必须处理,因此Py_INCREF用来管理引用计数,最后还需要将error和注册的python模块连接起来。

在python中,只需要使用import License,直接引入即可,看下面代码:

def CheckLicenseInfo2():
    error = ""
    # print(License.isHavingLicense('DESIGNAPP',error))
    ret = License.isHavingLicense('DESIGNAPP',error)
    err = ret[2]
    if not ret[0]:
        QMessageBox.critical(None, 'Error', QString(str(err)))
        exit()

    print(License.getLicenseType())
    print(License.getLicenseInfo())
    print(License.getRegisterInfo())
    print(License.getAppendInfo())

由于isHavingLicense返回的是tuple,因此我们只要指明索引,就可以找到需要的内容。更多的参数类型可以查看C:\Python27\include,下面都是各种的传入传出函数和宏,满足我们的需要。

这种pyd的方式实质上也是dll,但是比直接调用ctype读取dll有更好的兼容性,在实际生产中推荐使用pyd调用C++函数函数。

 

参考:

http://www.cnblogs.com/night-ride-depart/p/4907613.html

https://docs.python.org/2/library/ctypes.html

http://wolfprojects.altervista.org/dllforpyinc.php

http://icejoywoo.github.io/2015/10/30/intro-of-extending-cpython.html

https://docs.python.org/2/c-api/arg.html

https://docs.python.org/2/extending/extending.html?highlight=meth_varargs

http://blog.csdn.net/mkc1989/article/details/38943927

golang ide环境配置

January 20th, 2018

最近golang和区块链火起来了,觉得自己有必要也跟一次风了。

golang的背景啊,优缺点啊就不讲了,网上大把资料,只要有C/C++的基础,这个golang程序猿就可以快速胜任。我就说一下配置过程和调试过程中的坑。golang的安装非常简单,只需要将其解压到制定目录,设置好GOROOT和GOPATH即可,GOROOT是golang的可执行文件,而GOPATH从我理解就是Go Project的所在目录,我们平时go get所下载的工程都会放到$GOPATH/src中。

由于刚开始接触,我手动设置了go编程环境,也使用go run 运行了hello world,但是一直无法调试go程序,在网上找了半天debugger,发现delver可以用来调试go程序,我习惯使用git+make install安装程序,安装完毕之后,我将其安装到go所在目录,方便直接命令行调用。

spider@ubuntu:/usr/lib/go-1.9.2$ ls
api  AUTHORS  bin  blog  CONTRIBUTING.md  CONTRIBUTORS  doc  favicon.ico  lib  LICENSE  misc  PATENTS  pkg  README.md  robots.txt  src  test  VERSION
spider@ubuntu:/usr/lib/go-1.9.2$ cd bin/
spider@ubuntu:/usr/lib/go-1.9.2/bin$ ls
dlv  go  godoc  gofmt

只要用过gdb的童靴,这个就非常简单了,比如break,continue,print 等,如下所示:

spider@ubuntu:/tmp$ dlv debug test.go
Type 'help' for list of commands.
(dlv) l
> _rt0_amd64_linux() /usr/lib/go-1.9.2/src/runtime/rt0_linux_amd64.s:8 (PC: 0x4571b0)
Warning: debugging optimized function
     3:	// license that can be found in the LICENSE file.
     4:	
     5:	#include "textflag.h"
     6:	
     7:	TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
=>   8:		LEAQ	8(SP), SI // argv
     9:		MOVQ	0(SP), DI // argc
    10:		MOVQ	$main(SB), AX
    11:		JMP	AX
    12:	
    13:	// When building with -buildmode=c-shared, this symbol is called when the shared
(dlv) b main.main
Breakpoint 1 set at 0x4a08d8 for main.main() ./test.go:12
(dlv) c
> main.main() ./test.go:12 (hits goroutine(1):1 total:1) (PC: 0x4a08d8)
Warning: debugging optimized function
     7:	   return n
     8:	  }
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
=>  12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) n > main.main() ./test.go:13 (PC: 0x4a08ef)
Warning: debugging optimized function
     8:	  }
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
=>  13:	    var i int
    14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:14 (PC: 0x4a08f8)
Warning: debugging optimized function
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
=>  14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:15 (PC: 0x4a0913)
Warning: debugging optimized function
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { =>  15:	       fmt.Printf("%d\t", fibonacci(i))
    16:	    }
    17:	}
(dlv) p i
0
(dlv) n
0	> main.main() ./test.go:14 (PC: 0x4a0a02)
Warning: debugging optimized function
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
=>  14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:15 (PC: 0x4a0913)
Warning: debugging optimized function
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { =>  15:	       fmt.Printf("%d\t", fibonacci(i))
    16:	    }
    17:	}
(dlv) p i
1

另外我们也可以使用dlv attach pid的方式进行调试,当然我们必须开启/proc/sys/kernel/yama/ptrace_scope to 0。然而我gdb、dlv再怎么强大,还是没有IDE方便,尤其是断点调试,经过调研VSCode、Gogland、Atom满足我们需求,由于之前我一直是用JetBrain系产品进行开发,毫无疑问,我是用Gogland作为IDE。我着重说一下他的调试,当debug某个程序时候,Gogland会打印信息:

GOROOT=/usr/lib/go-1.9.2 #gosetup
GOPATH=/home/spider/go #gosetup
/usr/lib/go-1.9.2/bin/go build -o /tmp/___go_build_main_go -gcflags "-N -l" -a /home/spider/go/src/awesomeProject/main.go #gosetup
/opt/GoLand/plugins/intellij-go-plugin/lib/dlv/linux/dlv --listen=localhost:41151 --headless=true --api-version=2 --backend=default exec /tmp/___go_build_main_go -- #gosetup

可以看到第一行、第二行是打印GOROOT和GOPATH,第三行是打印进行编译,添加-gcflags “-N -l”,是为了去掉编译优化,方便调试,这行执行完毕会把程序放到/tmp/___go_build_main_go中,第四行就是使用gogland中的dlv插件的debug server,后端就是我们编译的/tmp/___go_build_main_go程序。

 

参考

https://golang.org/doc/editors.html