Python – Acessando bibliotecas do Linux com ctypes

A linguagem Python possui muitas bibliotecas, e facilmente conseguimos instalar qualquer uma da internet ou do próprio www.pypi.python.org, que possuirão ‘N’ recursos para protocolos, comunicações, banco de dados, web, mineração de dados, matemática, Gui e entre muitas outras funções.

Vamos conhecer agora um recurso  sensacional para usar no Python, de importar bibliotecas compartilhadas do sistema operacional, no Linux por exemplo as famosas lib*.so e poder usar as funções definidas nas mesmas.

Acessando o link do PyPi sobre ctypes https://docs.python.org/2/library/ctypes.html podemos ver que é bem direto sobre o que você pode fazer e o que ele faz.

ctypes is a foreign function library for Python. It provides C compatible data types, and allows calling functions in DLLs or shared libraries. It can be used to wrap these libraries in pure Python.

Uma breve descrição de ctypes, onde surgiu partir da versão 2.5, e com ele conseguimos utilizar uma função em uma biblioteca do sistema, podemos dizer ser um fork da libffi, lib esta que permite chamadas das funções C possam ser feitas em tempo de execução, e isso conseguimos apenas com o Python.

 

Básico do ctypes

Muito bacana isso, vamos fazer um teste com uma biblioteca que tenho certeza que se você possui em seu computador, a libc.so, vamos criar o script ex1_ctypes.py:

#! /usr/bin/python

import ctypes

# Modo 1 - Funcao printf()
# Apenas com o nome da lib
#_libc = ctypes.CDLL("libc.so.6")
# Com o caminho absoluto
_libc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6")
_libc.printf("Usando printf()\n")

# Modo 2 - Funcao printf()
_load_libc = ctypes.cdll.LoadLibrary("libc.so.6")
_load_libc.printf("Usando printf()\n")

# Modo 3 - Funcao time()
print ctypes.cdll.LoadLibrary("libc.so.6").time(None)

# Funcao get_nprocs()
print "Cores: %d" % _libc.get_nprocs()

Executando:

$ python ex1_ctypes.py 
Usando printf()
Usando printf()
1441760077
Cores: 4

Você viu como foi simples em poucas linhas usamos o printf() e o time() da libc, o que fizemos foi importar o modulo ctypes, e carregar nossa libc, usando apenas o nome libc.so.6, ou caminho absoluto da biblioteca e usando CDLL ou LoadLibrary, além da grande sacada de carregar e na mesma linha utilizar a função como na linha 17.

 

Pesquisando por funções

E como saber o nome correto das funções, muitas vezes você não esta familiarizado com a estrutura de libs do Linux, mas você pode usar o find_library do ctypes, então vamos ao script ex2_ctypes.py:

# /usr/bin/python

import ctypes
from ctypes.util import find_library as findlib

# Tradicional
print ctypes.util.find_library("c")

# Um atalho :)
print findlib("m")
print findlib("udev")
print findlib("lzma")
print findlib("crypt")
print findlib("abc")
print findlib("crypto")

Executando:

$ python ex2_ctypes.py 
libc.so.6
libm.so.6
libudev.so.1
liblzma.so.5
libcrypt.so.1
None
libcrypto.so.1.0.0

Que maravilha, não? E ficou claro que caso encontrar a biblioteca retornara com o nome da mesma, caso contrario retorna com None, conforme tentei pesquisar pela ‘abc‘ na linha14.

 

Criando nossa própria biblioteca

Vamos criar uma biblioteca em C básica com apenas duas funções, onde uma irá retornar o nome da função e a próxima um numero randômico, nada que não poderíamos fazer em Python facilmente, mas irei utilizar como exemplo.

Código da nossa biblioteca, mylib.c:

#include <stdio.h>
#include <time.h>

char *str_name = "Eu sou global :(\0";

void itself(void);
int randomize(int);


void itself()
{
  printf("Arquivo %s\n",__FILE__);
  printf("Funcao  %s\n",__func__);
  printf("Blog Cleiton Bueno\n");
}


int randomize(int N)
{
  srand(time(NULL));
  int result = rand() % N;
  return result;
}

Construindo nossa biblioteca compartilhada:

$ gcc -Wl,-soname,mylib -o mylib.so -shared -fPIC mylib.c

Criando nosso Python para usar as funções itself() e randomize(int), no ex3_ctypes.py:

# /usr/bin/python


import ctypes


mylib = ctypes.CDLL('./mylib.so')


print mylib.itself()
print
print 'Random: %d' % mylib.randomize(ctypes.c_int(50))

Executando nossa aplicação:

$ python ex3_ctypes.py 
Arquivo mylib.c
Funcao  itself
Blog Cleiton Bueno
19

Random: 28

Esta imaginando o poder que isso pode lhe oferecer?

 

Acessando variáveis de bibliotecas

Eu não comentei nada sobre a variável global str_name no exemplo anterior, porque iria abordar nesta etapa.

E como você esta pensando, sim, podemos acessar estas variáveis, vamos ao ex4_ctypes:

#! /usr/bin/python

import ctypes


mylib = ctypes.CDLL("./mylib.so")

# Devo dizer que da minha lib "mylib" irei importar a variavel "str_name"
# que eh um ponteiro de caracteres "c_char_p"
my_str = ctypes.c_char_p.in_dll(mylib, 'str_name')

print my_str.value

Executando nosso script:

$ python ex4_ctypes.py 
Eu sou global :(

 

 

Tipos de dados

No exemplo anterior eu deveria fornecer um numero qualquer para minha função randomize(int), e de onde tirei aquele ctype.c_int(), depois usei um tal de ctypes.c_char_p para importar um ponteiro de caracteres, pois é, esta função é uma das responsáveis por tornar compatível os tipos de dados entre C <-> Python, uma relação completa entre os tipos segue abaixo.

ctypes type C type Python type
c_bool _Bool bool (1)
c_char char 1-character string
c_wchar wchar_t 1-character unicode string
c_byte char int/long
c_ubyte unsigned char int/long
c_short short int/long
c_ushort unsigned short int/long
c_int int int/long
c_uint unsigned int int/long
c_long long int/long
c_ulong unsigned long int/long
c_longlong __int64 or long long int/long
c_ulonglong unsigned __int64 or unsigned long long int/long
c_float float float
c_double double float
c_longdouble long double float
c_char_p char * (NUL terminated) string or None
c_wchar_p wchar_t *(NUL terminated) unicode or None
c_void_p void * int/long or None

 

Extras

São muitas as possibilidades e recursos, principalmente com a parte de ponteiros e que merecem e devem ter uma grande atenção.

Algumas dicas extra que posso dar é como exemplo a função create_string_buffer(), caso precisar criar um buffer mutável, onde nos parenteses você fornece o tamanho do buffer, e ele irá retornar um objeto que é um array de c_char, outro recurso é o ctypes.Structure, se você pensou em alguma semelhante com struct em C, pensou certo, onde você pode usar? Vamos ver.

Criando uma nova biblioteca, mystruct.c:

#include <stdio.h>
#include <string.h>

struct c2py
{
  char indice;
  char buff[50];
  int key;
};

void printStruct(struct c2py *);

void printStruct(struct c2py *my_struct)
{
  my_struct->indice = 'A';
  strncpy(my_struct->buff,"Cara, apareci no Python :)", 26);
  my_struct->key = 2015;
}

Compilando a biblioteca:

$ gcc -Wl,-soname,mystruct -o mystruct.so -shared -fPIC mystruct.c

Código Python para usar nossa struct, ex5_ctypes.py:

#! /usr/bin/python

import ctypes

# Class com a estrutura do struct criado na lib em C
class skeleton_mystruct(ctypes.Structure):
  _fields_ = [
    ("indice", ctypes.c_char),
    ("buff", ctypes.c_char * 50),
    ("key", ctypes.c_int)
  ]


# Carregando a nossa biblioteca
lib_mystruct = ctypes.CDLL('./mystruct.so')

# Criando um objeto da nossa estrutura
s1_dev = skeleton_mystruct()

# Acessando nossa funcao printStruct passando a nossa estrutura
lib_mystruct.printStruct(ctypes.byref(s1_dev))

# Print da estrutura indexada
print "Indice : %c " % s1_dev.indice
print "Buff   : %s " % s1_dev.buff
print "Key    : %d " % s1_dev.key


# Explorando nosso objeto s1_dev
print
print type(s1_dev)
print "\nObjeto s1_dev"
print dir(s1_dev)

print "\nObjeto s1_dev.buff"
print dir(s1_dev.buff)

Executando nossa aplicação python:

$ python ex5_ctypes.py
Indice : A
Buff   : Cara, apareci no Python :)
Key    : 2015

< class '__main__.skeleton_mystruct' >

Objeto s1_dev
['__class__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_b_base_', '_b_needsfree_', '_fields_', '_objects', 'buff', 'indice', 'key']

Objeto s1_dev.buff
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Acho que ficou claro o que foi usado no Python para enquadrar com o que fizemos em C certo? Nas referências coloquei um link que aborda só a parte de Structure, vale a pena conferir. Mas e o create_string_buffer? Vamos adaptar um script python pra o buff deste exemplo, no ex6_ctypes.py:

#! /usr/bin/python

import ctypes

# Class com a estrutura do struct criado na lib em C
class skeleton_mystruct(ctypes.Structure):
  _fields_ = [
    ("indice", ctypes.c_char),
    ("buff", ctypes.c_char * 50),
    ("key", ctypes.c_int)
  ]


# Carregando a nossa biblioteca
lib_mystruct = ctypes.CDLL('./mystruct.so')

# Criando um objeto da nossa estrutura
s1_dev = skeleton_mystruct()

# Acessando nossa funcao printStruct passando a nossa estrutura
lib_mystruct.printStruct(ctypes.byref(s1_dev))


my_buff = ctypes.create_string_buffer(len(s1_dev.buff))
my_buff = s1_dev.buff

print "Buffer  : %s " % my_buff


# Test:
# Lembra que falei de que o create_string_buffer retorna
# um array de c_char, vamos verificar
print "\ncreate_string_buffer(10) == 10*c_char
print type(ctypes.create_string_buffer(10)) == 10*ctypes.c_char

Executando a aplicação:

$ python ex6_ctypes.py
Buffer  : Cara, apareci o Python :)

create_string_buffer(10) == 10*c_char
True

Vimos um uso pratico do create_string_buffer e confirmamos realmente o que é seu tipo.

Demos uma boa pincelada nos principais recursos do ctypes, mas ele vai muito além.

Gostou de conhecer o ctypes? Incrível não? Caso pretenda usar recomendo fortemente acessar python ctypes, porque existem muitas opções para ser utilizadas e combinadas.

Espero que tenham gostado, até a próxima!

 

Referencias

http://www.pypi.python.org

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

https://docs.python.org/2/library/ctypes.html#structured-data-types

Share Button

CC BY-NC-SA 4.0 Python – Acessando bibliotecas do Linux com ctypes by Cleiton Bueno is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.