新手学python(2):C语言调用完成数据库操作

继续介绍本人的python学习过程。本节介绍如何利用python调用c代码。内容还是基于音乐信息提取的过程,架构如图一。Python调用c实现的功能是利用python访问c语言完成mysql数据库操作。

新手学python(2):C语言调用完成数据库操作

在利用python调用c语言之前,我们需要首先完成c语言功能代码,然后再考虑语言的转换问题,所以我们先介绍c语言实现的数据库访问代码。数据库操作主要包括DDL和DML,DDL在创建数据库和表时完成,c语言完成的是DML。在具体的实现中,c语言主要完成了:连接数据库,insert和select三个操作。可以认为音乐信息数据库是一个read-only数据库,只允许添加和检索,不允许删除(删除可以通过直接操作数据库完成)。

1. 数据库设计

       关于音乐信息的数据库只有一个表格:all_music,创建表的SQL语句如下:

create table all_music(
    id integer auto_increment not null primary key,
    original_name varchar(100),
    name varchar(500) not null,
    artist varchar(500),
    genre varchar(30),
    album varchar(500),
    release_date date,
    directory varchar(300),
    size integer
);

由于音乐没有很好的主键,所以我们采用surrogate key,并设定其为自动增长的字段。其他信息主要包括音乐名,歌手,专辑,类型和发行时间等。

2. C语言操作数据库

定义完数据库表格,我们就可以实现访问该表格的c代码,相关代码如下:

#include <mysql.h>

typedef struct
{
    char original_name[100];
    char name[500];
    char artist[500];
    char genre[30];
    char album[500];
    char date[20];
    char directory[300];
    int size;
}music;

typedef struct
{
    char id[20];
    char original_name[100];
    char directory[300];
}row_result;

char* column_name[8]={"original_name","name","artist","genre","album","release_date","directory","size"};

MYSQL * connect_to(char* host,char* database,char* user,char* password)
{
    MYSQL* con_ptr;
    con_ptr=mysql_init(NULL);

    if((con_ptr=mysql_real_connect(con_ptr,host,user,password,database,3306,NULL,0))==NULL)
    {
        fprintf(stderr,"connect failed:%s\n",mysql_error(con_ptr));
        exit(-1);
    }

    return con_ptr;
}

void close_connect(MYSQL * con_ptr)
{
    mysql_close(con_ptr);
}

void insert(MYSQL * con_ptr,char* table,music* m)
{
    int res;

    char sql[2000];
    sprintf(sql,"insert into %s(%s,%s,%s,%s,%s,%s,%s,%s) values (‘%s‘,‘%s‘,‘%s‘,‘%s‘,‘%s‘,‘%s‘,‘%s‘,‘%d‘)",table,column_name[0],column_name[1],column_name[2],column_name[3],column_name[4],column_name[5],column_name[6],column_name[7],m->original_name,m->name,m->artist,m->genre,m->album,m->date,m->directory,m->size);

    res=mysql_query((MYSQL*)con_ptr,sql);

    if(res)
    {
        fprintf(stderr,"insert failed:%s",mysql_error(con_ptr));
        return;
    }
}

MYSQL_RES * select_music(MYSQL * con_ptr,char* table)
{
    int res;

    char sql[100];
    sprintf(sql,"select id,original_name,directory from %s ",table);

    res=mysql_query((MYSQL*)con_ptr,sql);

    if(res)
    {
        fprintf(stderr,"select failed:%s",mysql_error(con_ptr));
        return NULL;
    }
    else
    {
        MYSQL_RES* result=mysql_store_result((MYSQL*)con_ptr);

        if(result) return result;
        else return NULL;
    }
}

row_result * fetch_row(MYSQL_RES * result)
{
    MYSQL_ROW mysql_row;

    if(result==NULL)
        return NULL;

    mysql_row=mysql_fetch_row((MYSQL_RES*)result);

    if(mysql_row)
    {
        row_result* row=(row_result*)malloc(sizeof(row_result));
        strcpy(row->id,mysql_row[0]);
        strcpy(row->original_name,mysql_row[1]);
        strcpy(row->directory,mysql_row[2]);

        return row;
    }
    else
    {
        fprintf(stderr,"no rows to return!\n");
        return NULL;
    }
}

void free_row(row_result * row)
{
    free(row);
}

void free_result(MYSQL_RES * result)
{
    mysql_free_result(result);
}

上述代码首先定义了一个music结构体,对应要访问数据库表的一行,每一个元素的大小也和数据库表的定义一致。之后定义了一个表示返回结果的结构体row_result。此外,为了方便操作数据库表格,还定义了一个column_name数组表示数据库表的每一列。

       后面开始具体的数据库操作。connect_to函数建立与数据库的连接(特别注意不要将自定义的函数名字与库函数重名,否则会带来非常难找的bug!),并返回数据库连接指针。close_connect断开数据库连接。

Insert函数将一个music结构体对应的行插入数据库表中,代码的关键是构造一个没有错误的sql语句,构造sql语句时容易存在的问题是sql中如果存在“’”就会导致实际插入时的格式错误。这是因为当我们在指定某列的值时,需要采用类似‘%s‘这样的格式,如果要插入的数值也包括“’”就会导致错误的匹配。解决方案就是利用转义字符,python的mysql库为我们提供了一个可用的函数,在介绍python调用时会再次介绍。C语言貌似没有很好的函数解决该问题。

select_music函数会检索表中所有的行,我们利用mysql_store_result一次获得所有的行;另外一个可以利用的函数是mysql_use_result,这个函数会一次返回一行结果。两种函数的对比显而易见,但是在测试mysql_use_result时,它总会在返回部分结果后终止,不太可靠,因而采用了mysql_store_result函数。mysql_store_result返回的并不是可以直接访问的行数据,而是所有行的一个结果集,我们还需要利用fetch_row遍历结果集,获得每一行的真正数据。free_row和free_result分别释放每一行的空间和整个结果集。

最后,将上述代码打包成动态链接库,以供python调用。编译代码为:
gcc -I/usr/include/mysql  -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fno-strict-aliasing -fwrapv db_operation.c -rdynamic -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto -fPIC  -shared -o libdb_operation.so

3. Python调用C代码

3.1 数据类型的对应

在调用c代码时,我们需要创建music数据结构来存放插入的音乐信息,也需要row_result结构体来保存select返回的结果。如果利用python调用上面的c代码,我们不可避免地要创建上面两个结构体。为了将python中的数据结构映射到c中的数据结构,python提供了一个叫ctypes的包,用以实现数据类型的转换。ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。针对上面定义的music结构体,python中对应的数据类型如下:

#!/usr/bin/python2.6

from ctypes import *

class Music(Structure):
    _fields_=[
            (‘original_name‘,c_char*100),
            (‘name‘,c_char*500),
            (‘artist‘,c_char*500),
            (‘genre‘,c_char*30),
            (‘album‘,c_char*500),
            (‘release_date‘,c_char*20),
            (‘directory‘,c_char*300),
            (‘size‘,c_int)
            ]

    def setAttr(self,original_name,name,artist,genre,album,release_date,directory,size):
        self.original_name=original_name;
        self.name=name;
        self.artist=artist;
        self.genre=genre;
        self.album=album;
        self.release_date=release_date;
        self.directory=directory;
        self.size=size;

我们需要定义一个表示结构体的类Music,设置其_fields_属性,每一个属性的设置都包括属性名和属性类型。由于该类是在c中使用,所以数据类型都被转换为c语言可识别的类型,如c_char和c_int等。ctypes的类型对应如下:

新手学python(2):C语言调用完成数据库操作

和Music类似,表示返回结果的结构体在python中也有对应的类:

#!/usr/bin/python2.6

from ctypes import *

class Row(Structure):
    _fields_=[
            (‘id‘,c_char*20),
            (‘original_name‘,c_char*100),
            (‘directory‘,c_char*300)
            ]

3.2 函数调用

完成数据结构的对应之后,下面就可以实现具体的python函数:

#!/usr/bin/python2.6

import sys
import ctypes
import music as m
import row
import MySQLdb as mysql

class CallDB:

    connect_lib=ctypes.cdll.LoadLibrary(‘./libdb_operation.so‘);
    connect_lib.fetch_row.restype=ctypes.POINTER(row.Row);#capitalized POINTER

    @staticmethod
    def connectTo(host,database,user,password):
        c_host=ctypes.c_char_p(host);
        c_database=ctypes.c_char_p(database);
        c_user=ctypes.c_char_p(user);
        c_password=ctypes.c_char_p(password);
        c_con_ptr =CallDB.connect_lib.connect_to(c_host,c_database,c_user,c_password);
        return c_con_ptr;

    @staticmethod
    def insert(c_con_ptr,table,music):
        c_table=ctypes.c_char_p(table);
        CallDB.connect_lib.insert(c_con_ptr,c_table,ctypes.pointer(music));

    @staticmethod
    def closeConnect(c_con_ptr):
        CallDB.connect_lib.close_connect(c_con_ptr);

    @staticmethod
    def select(c_con_ptr,table):
        c_table=ctypes.c_char_p(table);
        c_result=CallDB.connect_lib.select_music(c_con_ptr,c_table);
        return c_result;

    @staticmethod
    def fetchRow(c_result):
        c_row_result=CallDB.connect_lib.fetch_row(c_result);
        return c_row_result if c_row_result else None;

    @staticmethod
    def freeRow(c_row_result):
        CallDB.connect_lib.free_row(c_row_result);

    @staticmethod
    def freeResult(c_result):
        CallDB.connect_lib.free_result(c_result);

上述代码首先引入几个必要的包,然后定义一个类CallDB。类的开始定义了一个全局变量connect_lib表示加载的动态链接库。C语言实现的函数就是通过该全局变量进行访问。下一行代码稍后再做解释。

       第一个静态函数实现数据库的连接,调用的是c语言的connect_to函数。由于connect_to的参数都是c语言下的数据类型,我们不能直接传递python下的数据类型,需要首先利用ctypes将其转换成c语言可识别的类型。返回值c_con_ptr在c语言是一个MYSQL指针,python不知道其具体类型。由于我们在python中不会访问该指针,所以我们无需指定其具体类型。后面的静态函数通过调用c函数实现了数据库的插入和检索。

       可以看出,利用python实现基本的c调用很简单,但是需要注意两点。第一,非基本数据类型指针参数的传递。在insert函数中,music参数通过ctypes.pointer函数被转化成一个指针类型。虽然我们实现了c语言下music结构体在python下对应的类Music,但是python没有指针的概念,传递的参数必须被手动转换成指针。下面的代码演示了insert函数的具体使用:

music=m.Music();
directory=mysql.escape_string(results[0]);
album=mysql.escape_string(results[1].encode(‘utf-8‘));
release_date=results[2][4:8]+results[2][2:4]+results[2][0:2];
name=mysql.escape_string(results[i][0].encode(‘utf-8‘));
genre=mysql.escape_string(results[i][1]);
artist=mysql.escape_string(results[i][2].encode(‘utf-8‘));
original_name=mysql.escape_string(results[i][3]);
size=int(results[i][4]);
music.setAttr(original_name,name,artist,genre,album,release_date,directory,size);
db.CallDB.insert(MyRequestHandler.c_con_ptr,‘all_music‘,music);

在前面我们提到过SQL语句中转义字符的问题,MySQLdb为我们提供了一个函数escape_string可以解决转义的问题。第二,使用c代码的返回值。函数fetchRow在c语言下的返回值是一个row_result结构体指针。虽然这个指针在python下有对应的类Row,但是这需要我们手工指定,这就是开头代码

connect_lib.fetch_row.restype=ctypes.POINTER(row.Row);

的作用。特别注意,这个地方的POINTER需要大写,小写会报错。在获得返回值之后,访问对应的属性可通过下面的代码完成:

c_row_result=db.CallDB.fetchRow(MyRequestHandler.c_row_result);
if c_row_result:
     print c_row_result.contents.original_name;

如果是基本类型,如int,char则无需指定,可以直接访问返回值;如果返回类型是char*,则我们也需要手工指定返回类型为c_char_p。

       当然,如果单纯从数据库操作来看,完全可以利用MySQLdb包完成同样的功能,在此只是演示python如何调用c代码。

新手学python(2):C语言调用完成数据库操作,布布扣,bubuko.com

新手学python(2):C语言调用完成数据库操作

上一篇:全方位剖析 Linux 操作系统,太全了!!!


下一篇:黄聪:Wordpress程序Mysql查询导致CPU100%,页面错误增量飙高解决方案