Python调用动态链接库DLL学习笔记

Python调用动态链接库DLL

动态链接库(DLL,全称Dynamic Link Library)进程之间共享使用的函数库。动态链接库提供了一种方法,是进程可以调用不属于其自身可执行代码的函数。函数的可执行代码位于一个DLL中,当调用DLL中的方法时,进程由原本自身的代码执行到DLL中。DLL有助于程序模块化,减少重复开发。

概述:

接触dll之前觉得它很神秘,就像是一个黑盒子,静静地躺在安装的程序的某一个文件夹内,既不能直接运行,也不能接收消息,但离了他们程序往往又无法运行。动态链接库是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?

语法:

__declspec(dllexport):
将一个函数声名为导出函数,就是说这个函数要被其他程序调用,即作为DLL的一个对外函数接口。
通常它和extern "C" 合用,extern "C"告诉编译器该部分可以在C/C++中使用,形式如下:
extern "C"
{

__declspec(dllexport) RETURN_TYPE FUNCTION()

}

__declspec(dllimport) :

当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。但如果你显示地导入函数,编译器会产生质量更好的代码。由于编译器确切地知道了一个函数是否在一个DLL中,它就可以产生更好的代码,不再需要间接的调用转接。

Win32的PE格式(Portable Executable Format)把所有导入地址放在一个导入地址表中。

__declspec(dllimport) void func1(void);

void main(void)
{
func1();
}

流程:
  • 加载DLL

    根据调用的函数是符合什么调用约定

    stdcall调用约定:两种加载方式

    Objdll = ctypes.windll.LoadLibrary("dllpath")
    Objdll = ctypes.WinDLL("dllpath") 
    

    cdecl调用约定:也有两种加载方式

    Objdll = ctypes.cdll.LoadLibrary("dllpath")
    Objdll = ctypes.CDLL("dllpath")
    

    windll和cdll分别是WinDLL类和CDll类的对象

  • 调用DLL中的函数

    在前面加载dll的时候会返回一个DLL对象(假设名字叫Objdll),利用该对象就可以调用dll中的方法。
    e.g. 如果dll中有个方法名字叫Add(注意如果经过stdcall声明的方法,如果不是用def文件声明的导出函数或者extern “C” 声明的话,编译器会对函数名进行修改,这个要注意。)
    调用:nRet = Objdll.Add(12, 15) 即完成一次调用。

    看起来调用似乎很简单,这是因为Add这个函数太简单了,现在假设函数需要你传入一个int类型的指针(int*),可以通过库中的byref关键字来实现,假设现在调用的函数的第三个参数是个int类型的指针。

    intPara = c_int(9)
    dll.sub(23, 102, byref(intPara))
    print intPara.value
    

    如果是要传入一个char缓冲区指针,和缓冲区长度,方法至少有四种:

    # 方法1
    szPara = create_string_buffer('/0'*100)
    dll.PrintInfo(byref(szPara), 100);
    print szPara.value
     
    # 方法2
    sBuf = 'aaaaaaaaaabbbbbbbbbbbbbb'
    pStr = c_char_p( )
    pStr.value = sBuf
    #pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value
    dll.PrintInfo(pStr, len(pStr.value))
    print pStr.value
     
    # 方法3
    strMa = "/0"*20
    FunPrint  = dll.PrintInfo
    FunPrint.argtypes = [c_char_p, c_int]
    #FunPrint.restypes = c_void_p
    nRst = FunPrint(strMa, len(strMa))
    print strMa,len(strMa)
     
    # 方法4
    pStr2 = c_char_p("/0")
    print pStr2.value
    #pVoid = ctypes.cast( pStr, ctypes.c_void_p ).value
    dll.PrintInfo(pStr2, len(pStr.value))
    print pStr2.value
    
  • C基本类型和ctypes中实现的类型映射表

    ctypes数据类型 C数据类型
    c_char char
    c_short short
    c_int int
    c_long long
    c_ulong unsign long
    c_float float
    c_double double
    c_void_p void
    对应的指针类型是在后面加上"_p",如int*是c_int_p等等。
    在python中要实现c语言中的结构,需要用到类。

  • DLL中的函数返回一个指针

    虽然这不是一个常用的方法,不过这种情况的处理方法也很简单,其实返回的都是地址,把他们转换相应的python类型,再通过value属性访问。

    pchar = dll.getbuffer()
    szbuffer = c_char_p(pchar)
    print szbuffer.value
    
  • 处理C中的结构体类型

    这是最麻烦也是最复杂的一种调用,在python里面申明一个类似c的结构体,要用到类,并且这个类必须继承自Structure。
    先看一个简单的例子:
    C里面dll的定义如下:

    typedef struct _SimpleStruct
    {
        int    nNo;
        float  fVirus;
        char   szBuffer[512];
    } SimpleStruct, *PSimpleStruct;
    typedef const SimpleStruct*  PCSimpleStruct;
     
    extern "C"int  __declspec(dllexport) PrintStruct(PSimpleStruct simp);
    int PrintStruct(PSimpleStruct simp)
    {
        printf ("nMaxNum=%f, szContent=%s", simp->fVirus, simp->szBuffer);
    return simp->nNo;
    }
    

    python中的定义:

    from ctypes import *
    class SimpStruct(Structure):
        _fields_ = [ ("nNo", c_int),
                  ("fVirus", c_float),
                  ("szBuffer", c_char * 512)]
     
    dll = CDLL("AddDll.dll")
    simple = SimpStruct();
    simple.nNo = 16
    simple.fVirus = 3.1415926
    simple.szBuffer = "magicTong/0"
    print dll.PrintStruct(byref(simple))
    

    上面例子结构体很简单,但是如果结构体里面有指针,甚至是指向结构体的指针,处理起来会复杂很多,不过Python里面也有相应的处理方法,下面这个例子来自网上
    C代码如下:

    typedef struct 
    
    {
    
    char words[10];
    
    }keywords;
    
     
    
    typedef struct 
    
    {
    
    keywords *kws;
    
    unsigned int len;
    
    }outStruct;
    
    extern "C" int __declspec(dllexport) test(outStruct *o);
    
    int test(outStruct *o)
    
    {
    
    unsigned int i = 4;
    
    o->kws = (keywords *)malloc(sizeof(unsigned char) * 10 * i);
    
    strcpy(o->kws[0].words, "The First Data");
    
    strcpy(o->kws[1].words, "The Second Data");
    
     
    
    o->len = i;
    
    return 1;
    
    }
    

    Python代码如下:

    class keywords(Structure):
     
            _fields_ = [('words', c_char *10),]
     
     
     
    class outStruct(Structure):
     
            _fields_ = [('kws', POINTER(keywords)),
     
                        ('len', c_int),]
     
    o = outStruct()
     
    dll.test(byref(o))
     
     
     
    print o.kws[0].words;
     
    print o.kws[1].words;
     
    print o.len
    
  • 两个例子

    • 这是一个GUID生成器,其实很多第三方的python库已经有封装好的库可以调用,不过这得装了那个库才行,如果想直接调用一些API,对于python来说,也要借助一个第三方库才行,这个例子比较简单,就是用C++调用win32 API来产生GUID,然后python通过调用C++写的dll来获得这个GUID。
      c++代码如下:
    extern "C"__declspec(dllexport) char* newGUID(); 
     
    char* newGUID() 
     
    {
     
         static char buf[64] = {0};
     
         statc GUID guid;
     
         if (S_OK == ::CoCreateGuid(&guid)) 
     
         {
     
           _snprintf(buf, sizeof(buf),
     
    "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", 
     
    guid.Data1,
     
    guid.Data2,
     
    guid.Data3,
     
    guid.Data4[0], guid.Data4[1],
     
    guid.Data4[2], guid.Data4[3],
     
    guid.Data4[4], guid.Data4[5],
     
    guid.Data4[6], guid.Data4[7]
     
    ); 
     
           ::MessageBox(NULL, buf, "GUID", MB_OK); 
     
          }
     
         return (char*)buf;
     
    }
    
    

    python代码如下:

    def CreateGUID():
     
        """
        创建一个全局唯一标识符
        类似:E06093E2-699A-4BF2-A325-4F1EADB50E18
        NewVersion
        """
     
        try:
     
            # dll path
     
            strDllPath = sys.path[0] + str(os.sep) + "createguid.dll"
     
            dll = CDLL(strDllPath)
     
            b = dll.newGUID()
     
            a = c_char_p(b)
     
        except Exception, error:
     
            print error
     
            return ""
     
        return a.value
    
    • 这个例子是调用kernel32.dll中的createprocessA函数来启动一个记事本进程

      #  -*- coding:utf-8 -*- 
       
      from ctypes import * 
       
       
       
      # 定义_PROCESS_INFORMATION结构体
       
      class _PROCESS_INFORMATION(Structure):
       
          _fields_ = [('hProcess', c_void_p),
       
                      ('hThread', c_void_p),
       
                      ('dwProcessId', c_ulong),
       
                      ('dwThreadId', c_ulong)]
       
       
       
      # 定义_STARTUPINFO结构体
       
      class _STARTUPINFO(Structure):
       
          _fields_ = [('cb',c_ulong),
       
                      ('lpReserved', c_char_p),
       
                      ('lpDesktop', c_char_p),
       
                      ('lpTitle', c_char_p),
       
                      ('dwX', c_ulong),
       
                      ('dwY', c_ulong),
       
                      ('dwXSize', c_ulong),
       
                      ('dwYSize', c_ulong),
       
                      ('dwXCountChars', c_ulong),
       
                      ('dwYCountChars', c_ulong),
       
                      ('dwFillAttribute', c_ulong),
       
                      ('dwFlags', c_ulong),
       
                      ('wShowWindow', c_ushort),
       
                      ('cbReserved2', c_ushort),
       
                      ('lpReserved2', c_char_p),
       
                      ('hStdInput', c_ulong),
       
                      ('hStdOutput', c_ulong),
       
                      ('hStdError', c_ulong)]
       
       
       
      NORMAL_PRIORITY_CLASS = 0x00000020 #定义NORMAL_PRIORITY_CLASS
       
      kernel32 = windll.LoadLibrary("kernel32.dll")  #加载kernel32.dll
       
      CreateProcess = kernel32.CreateProcessA   #获得CreateProcess函数地址
       
      ReadProcessMemory = kernel32.ReadProcessMemory #获得ReadProcessMemory函数地址
       
      WriteProcessMemory = kernel32.WriteProcessMemory #获得WriteProcessMemory函数地址
       
      TerminateProcess = kernel32.TerminateProcess
       
       
       
      # 声明结构体
       
      ProcessInfo = _PROCESS_INFORMATION()
       
      StartupInfo = _STARTUPINFO()
       
      fileName = 'c:/windows/notepad.exe'       # 要进行修改的文件
       
      address = 0x0040103c        # 要修改的内存地址
       
      strbuf = c_char_p("_")        # 缓冲区地址
       
      bytesRead = c_ulong(0)       # 读入的字节数
       
      bufferSize =  len(strbuf.value)     # 缓冲区大小
       
       
       
      # 创建进程 
       
      CreateProcess(fileName, 0, 0, 0, 0, NORMAL_PRIORITY_CLASS,0, 0, byref(StartupInfo), byref(ProcessInfo))