Propojení Pythonu s nativními knihovnami s využitím balíčku cffi

30. 5. 2023
Doba čtení: 21 minut

Sdílet

 Autor: Python
Python je dnes pravděpodobně nejrozšířenějším programovacím jazykem, k čemuž přispěl i fakt, že pro něj existuje obrovské množství balíčků. Mnoho z nich je ve skutečnosti jen lepidlo mezi Pythonem a nativními knihovnami.

Obsah

1. Propojení Pythonu s nativními knihovnami s využitím balíčku cffi

2. Instalace knihovny cffi

3. Funkce psaná v C, která sečte své dva celočíselné parametry

4. Překlad funkce a uložení výsledku překladu do dynamicky linkované knihovny

5. Načtení dynamicky linkované knihovny v Pythonním skriptu a zavolání nativní funkce přes ctypes

6. Srážka staticky typovaného světa se světem typovaným dynamicky

7. Načtení dynamicky linkované knihovny a zavolání funkce z této knihovny s využitím cffi

8. Kontrola datových typů předávaných hodnot knihovnou cffi

9. Funkce naprogramovaná v céčku, která vytiskne předaný řetězec

10. Céčkovské řetězce jsou odlišné od Pythonovských řetězců!

11. Předání řetězců do céčkovské funkce realizované přes cffi

12. Programovací jazyk Python a céčkovské ukazatele

13. Funkce psaná v C, která prohodí hodnoty parametrů předaných referencí

14. Inicializace ukazatelů na straně Pythonu, předání ukazatelů do volané céčkové funkce

15. Kontrola, zda se s ukazatelem nezachází jako s polem

16. Práce s poli, konverze mezi polem a datovými typy Pythonu

17. Funkce psaná v C, která vyplní pole zvolenou hodnotou

18. Volání funkce pro výplň pole z Pythonu, zobrazení výsledného pole

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Propojení Pythonu s nativními knihovnami s využitím balíčku cffi

Programovací jazyk Python je dnes pravděpodobně nejrozšířenějším [1][2] programovacím jazykem, k čemuž přispěl i fakt, že pro něj existuje obrovské množství balíčků řešících problémy z různých oborů (od obecného zpracování dat přes tvorbu webů a grafického uživatelského rozhraní až po ML a AI). A mnoho těchto balíčků není ve skutečnosti nic jiného, než „pouhé“ sofistikované lepidlo mezi Pythonem a nativními (dynamicky linkovanými) knihovnami. Jak je však kooperace mezi Pythonem na straně jedné a nativním kódem na straně druhé realizována po technologické stránce?

Programový kód napsaný typicky v C, C++ (popř. v Rustu, Go či Fortranu atd.) je nejprve přeložen do nativní dynamické knihovny (tedy konkrétně do souboru s koncovkou „.so“ na Linuxu a „.dll“ ve Windows). Aplikace psaná v Pythonu tuto dynamickou knihovnu načte a přes balíček ctypes umožní volání funkcí naprogramovaných v C/C++ atd. Zpočátku se může zdát, že se jedná o bezproblémové řešení, ovšem na cestě k výslednému produktu je nutné zdolat poměrně mnoho překážek. Některé jsou relativně snadné (například ctypes lze nahradit za cffi, pokud to vývojáři více vyhovuje – což si ukážeme dnes i příště), další již komplikovanější. Tyto problémy spočívají v tom, že se střetávají dva rozdílné typové systémy. Navíc obě technologie předpokládají, že jedna z komunikujících stran je psaná v céčku – a to znamená, že se mezi dva programovací jazyky s automatickou správou paměti vložilo rozhraní předpokládající manuální správu paměti se všemi z toho plynoucími důsledky (to je případ komunikace mezi Pythonem a Go).

Dnes se zaměříme na balíček cffi, který v Pythonu umožňuje volat nativní funkce a předávat jim „nativní“ datové typy a datové struktury. cffi není, na rozdíl od ctypes, součástí standardní knihovny Pythonu, ovšem v několika ohledech se jedná o lepší řešení, které dobře „škáluje“ společně s rostoucím množstvím nativního kódu, který je nutné z Pythonu volat.

Poznámka: zatímco cffi je určeno pro Python, existuje podobný balíček určený pro Common LISP. Ten se pro změnu jmenuje CFFI.

2. Instalace knihovny cffi

Samotná instalace knihovny cffi je velmi snadná, protože poslední stabilní verze této knihovny je dostupná na PyPI a tato knihovna má jen jedinou závislosti – knihovnu pycparser. Instalaci pro aktuálně přihlášeného uživatele provedeme příkazem:

$ pip3 install --user cffi
 
Collecting cffi
  Using cached cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (442 kB)
Collecting pycparser
  Using cached pycparser-2.21-py2.py3-none-any.whl (118 kB)
Installing collected packages: pycparser, cffi
Successfully installed cffi-1.15.1 pycparser-2.21

Poněkud naivní kontrola instalace může vypadat následovně:

$ python3
 
Python 3.8.10 (default, Mar 13 2023, 10:26:41)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cffi
>>> help(cffi]

3. Funkce psaná v C, která sečte své dva celočíselné parametry

V úvodních kapitolách praktické části dnešního článku si ukážeme, jak lze z Pythonu zavolat velmi jednoduchou funkci, která je naprogramována v jazyku C. Tato funkce akceptuje dva parametry typu int, sečte je a následně vrátí výsledek součtu. Deklarace takové funkce je skutečně triviální:

extern int add(int x, int y)
{
    return x+y;
}

4. Překlad funkce a uložení výsledku překladu do dynamicky linkované knihovny

Soubor adder.c, v němž je výše uvedená funkce uložena, nyní přeložíme do objektového souboru, který bude pojmenován adder.o. Povšimněte si použití volby PIC, kterou se zapíná takzvaný Position Independent Code (tedy instrukcí nepoužívajících absolutní skoky) a která se používá při vytváření sdílených knihoven na některých architekturách (na x86_64 však v našem jednoduchém příkladu dostaneme stejný výsledek i bez použití této volby):

$ gcc -Wall -ansi -c -fPIC adder.c -o adder.o

Následně z tohoto objektového souboru vytvoříme sdílenou knihovnu (shared library) pojmenovanou libadder.so (přípona .so značí „shared object“):

$ gcc -shared -Wl,-soname,libadder.so -o libadder.so adder.o

Přesvědčíme se, že soubor libadder.so skutečně vznikl:

$ file libadder.so 
 
libadder.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=ca87f6b0705f4ddd589de8cc00bd31cdb8c6b6d6, not stripped

Popř. se můžeme podívat i na symboly, které jsou v tomto souboru definovány. Použijeme k tomu nástroj nm:

$ nm libadder.so 
 
00000000000010f9 T add
0000000000004020 b completed.8061
                 w __cxa_finalize
0000000000001040 t deregister_tm_clones
00000000000010b0 t __do_global_dtors_aux
0000000000003e78 d __do_global_dtors_aux_fini_array_entry
0000000000004018 d __dso_handle
0000000000003e80 d _DYNAMIC
0000000000001114 t _fini
00000000000010f0 t frame_dummy
0000000000003e70 d __frame_dummy_init_array_entry
00000000000020a0 r __FRAME_END__
0000000000004000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000002000 r __GNU_EH_FRAME_HDR
0000000000001000 t _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000001070 t register_tm_clones
0000000000004020 d __TMC_END__

Můžeme se dokonce podívat, jakým způsobem se funkce add přeložila do strojového kódu. K tomuto účelu použijeme nástroj objdump a pro získání disassemblovaného textu zvolené funkce použijeme trik s awk, který z výpisu „vykousne“ pouze požadovanou funkci:

$ objdump -d -M intel libadder.so | awk -F"\n" -v RS="\n\n" '$1 ~ /add/'
 
00000000000010f9 :
    10f9:       f3 0f 1e fa             endbr64
    10fd:       55                      push   rbp
    10fe:       48 89 e5                mov    rbp,rsp
    1101:       89 7d fc                mov    DWORD PTR [rbp-0x4],edi
    1104:       89 75 f8                mov    DWORD PTR [rbp-0x8],esi
    1107:       8b 55 fc                mov    edx,DWORD PTR [rbp-0x4]
    110a:       8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
    110d:       01 d0                   add    eax,edx
    110f:       5d                      pop    rbp
    1110:       c3                      ret

5. Načtení dynamicky linkované knihovny v Pythonním skriptu a zavolání nativní funkce přes ctypes

Nejdříve si ukážeme, jak je možné dynamicky linkovanou knihovnu načíst do Pythonu přes standardní knihovnu ctypes. Po načtení (které ovšem může vyvolat výjimku) je možné pracovat s výsledkem jako s objektem, který mj. obsahuje i metodu nazvanou add, kterou lze zavolat jako běžnou Pythonovskou funkci:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1,2))
Poznámka: aby bylo možné sdílenou knihovnu nalézt, je nutné nastavit proměnnou prostředí LD_LIBRARY_PATH (jinak by se knihovna hledala v /usr/lib, popř. /usr/lib64 atd. ale nikoli v aktuálním adresáři.). V našem konkrétním případě to znamená, že se interpret Pythonu zavolá následujícím způsobem:
$ export LD_LIBRARY_PATH=.
$ python3 call_via_ctypes.py

Nebo takto:

$ LD_LIBRARY_PATH=. python3 call_from_python.py

Výsledkem by v každém případě měla být hodnota „3“ vypsaná na terminál.

6. Srážka staticky typovaného světa se světem typovaným dynamicky

Výše uvedená céčkovská funkce akceptuje pouze parametry typu int, což je z pohledu céčka celočíselná hodnota se znaménkem s „vhodnou“ šířkou odvozenou mj. i od použité architektury. Z pohledu Pythonu se ovšem chápání funkcí liší, protože funkcím je možné v runtime předat parametry libovolného typu. Například se můžeme pokusit o předání řetězce namísto druhého celočíselného parametru a sledovat, jak se bude program chovat:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1, "foo"))

Program po spuštění kupodivu nezhavaruje (ctypes se snaží o provedení konverze), i když vypíše nesmyslné výsledky:

$ python3 call_via_ctypes2.py 
 
-1157149183

Naproti tomu snaha o předání celého čísla obsahujícího příliš velkou hodnotu, která se nevejde do nativního typu int, již vede k pádu aplikace:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1, 2**100))

Pokus o spuštění:

$ python3 call_via_ctypes3.py 
 
Traceback (most recent call last):
  File "call_via_ctypes3.py", line 9, in
    print(adder.add(1, 2**100))
ctypes.ArgumentError: argument 2: <class 'OverflowError'>: int too long to convert

7. Načtení dynamicky linkované knihovny a zavolání funkce z této knihovny s využitím cffi

Nyní se skript načítající dynamicky linkovanou knihovnu a volající v ní uloženou nativní funkci pokusme přepsat tak, aby využíval balíček cffi a nikoli ctypes. Je to vlastně velmi jednoduché a přímočaré, ovšem s jednou výjimkou – balíčku cffi musíme specifikovat i hlavičku volané funkce (popř. definici použitých datových typů), a to v původní céčkovské syntaxi. Díky tomu dodáme balíčku cffi i ty informace, které nelze odvodit z obsahu dynamicky linkované knihovny:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    int add(int x, int y);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1,2))

Při spuštění je opět nutné zajistit, aby načítaná dynamicky linkovaná knihovna byla uložena v adresáři, na který ukazuje proměnná prostředí LD_LIBRARY_PATH.

8. Kontrola datových typů předávaných hodnot knihovnou cffi

Víme již, že knihovna ctypes nekontroluje, že například do funkce akceptující celé číslo předáváme řetězec atd. Jak ale bude kontrola datových typů předávaných hodnot vypadat, pokud použijeme knihovnu cffi?

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    int add(int x, int y);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1,"foo"))

Nyní bude chyba detekována a volání se nezdaří (což je jen dobře):

Traceback (most recent call last):
  File "call_via_cffi2.py", line 14, in <module>
    print(adder.add(1,"foo"))
TypeError: an integer is required

Podobně dojde k chybě ve chvíli, kdy se pokusíme předat příliš velkou celočíselnou hodnotu:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    int add(int x, int y);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
adder = load_library("libadder.so")
print(adder.add(1,2**100))

Detekce chyby v runtime:

Traceback (most recent call last):
  File "call_via_cffi3.py", line 14, in <module>
    print(adder.add(1,2**100))
OverflowError: int too big to convert
Poznámka: z prvního uvedeného příkladu je patrné, že cffi dokáže provádět kontroly předávaných datových typů lépe, než knihovna ctypes (ostatně má mnohem více informací).

9. Funkce naprogramovaná v céčku, která vytiskne předaný řetězec

V další části dnešního článku si opět ukážeme způsob volání velmi jednoduché céčkovské funkce. Tentokrát bude funkce akceptovat řetězec (což je ovšem v pojetí céčka ukazatel na první znak řetězce), který následně vytiskne:

#include <stdio.h>
 
extern void greet(char *x) {
    printf("Hello %s!\n", x);
}

Z dalšího textu bude patrné, že nyní již nebude vše tak jednoduché, jako doposud – a to kvůli zcela odlišnému chápání řetězců v céčku a v Pythonu.

10. Céčkovské řetězce jsou odlišné od Pythonovských řetězců!

Způsob předávání dalších číselných typů (kromě komplexních čísel) je stejně jednoduchý (nebo problematický – záleží na úhlu pohledu), jako práce s parametry a návratovými hodnotami typu int, resp. long. Pojďme si však ukázat, jak lze zajistit komunikaci mezi funkcí napsanou v C a skriptem v Pythonu za situace, kdy je nutné céčkovské funkci předat řetězec. Z pohledu céčka mají řetězce zcela jiné vlastnosti, než v Pythonu:

  1. Jedná se o sekvenci bajtů ukončených hodnotou 0
  2. Z pohledu programátora se jedná o ukazatel na první znak řetězce
  3. Tato sekvence bajtů je měnitelná
  4. O alokaci a dealokaci se musí postarat programátor

Začneme funkcí popsanou v předchozí kapitole.

Nejprve nám již známým způsobem načteme a inicializujeme dynamicky linkovanou knihovnu, získáme referenci na céčkovskou funkci a zavoláme tuto funkci s předáním řetězce:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet("world")

Výsledkem bude:

Hello w!

Tento první pokus nebude v Pythonu 3 úspěšný, neboť se provádí převod na wchar_t*, tedy předává se ukazatel na řetězec zkonvertovaný na pole „širokých“ znaků. Hned první široký znak, tedy „W“, bude ve druhém bajtu obsahovat nulu, která (z pohledu céčka) řetězec ukončí, takže se vypíše právě ono dvojité wé:

Zkusme tedy převod řetězce na typ bytes, který je taktéž knihovnou ctypes podporován. Určíme, že se má řetězec přetransformovat do kódování UTF-8, kde již bude nulový bajt skutečně umístěn pouze na konci řetězce:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet(b"world")

Výsledek:

Hello world!

Alternativní způsob, který korektně převede Pythonovský řetězec do podoby kompatibilní s většinou céčkovských funkcí:

import ctypes
 
 
def load_library(library_name):
    return ctypes.CDLL(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet("world".encode("utf-8"))

11. Předání řetězců do céčkovské funkce realizované přes cffi

Nyní se pokusme stejnou chybu, tedy přímé předání Pythonovského řetězce do céčkovské funkce, která akceptuje char *, udělat i při použití knihovny cffi:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void greet(char *x);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet("world")

Knihovna cffi je v tomto ohledu mnohem přísnější než ctypes a chybu odhalí (což je jen dobře, protože tato chyba by jinak mohla zůstat nepovšimnuta):

Traceback (most recent call last):
  File "call_via_cffi1.py", line 14, in
    greeter.greet("world")
TypeError: initializer for ctype 'char *' must be a bytes or list or tuple, not str

Oba způsoby opravy jsou knihovnou cffi akceptovány:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void greet(char *x);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet(b"world")

Výsledek:

Hello world!

I tento způsob je akceptován:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void greet(char *x);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
greeter = load_library("libgreeter.so")
greeter.greet("world".encode("utf-8"))

Výsledek:

Hello world!

12. Programovací jazyk Python a céčkovské ukazatele

Mnoho funkcí naprogramovaných v céčku a uložených v přeložené formě do dynamicky linkovaných knihoven očekává, že některé předávané parametry budou ukazateli, popř. takové funkce vrací ukazatele na hodnotu určitého typu. To představuje určitý problém, protože v Pythonu se koncept ukazatelů nepoužívá (a reference jsou v Pythonu něco zcela odlišného). Proto je nutné, aby byla zajištěna explicitní „výroba“ ukazatelů přímo v Pythonu, což budou, jak uvidíme dále, objekty, které nám na straně Pythonu umožní přístup k uložené hodnotě a na straně céčka se bude jednat o skutečné řetězce.

13. Funkce psaná v C, která prohodí hodnoty parametrů předaných referencí

Použití ukazatelů si prozatím ukážeme na další velmi jednoduché funkci (v další části článku budou uvedeny složitější příklady). Jedná se o funkci, které se předají odkazy na dvě celočíselné hodnoty. Funkce tyto hodnoty prohodí a díky tomu, že parametry nejsou předávány hodnotou, ale již zmíněným odkazem, bude tato modifikace viditelná i mimo funkci swap:

extern void swap(int *x, int *y)
{
    *x = *x ^ *y;
    *y = *x ^ *y;
    *x = *x ^ *y;
}

14. Inicializace ukazatelů na straně Pythonu, předání ukazatelů do volané céčkové funkce

Funkci swap musíme při jejím volání předat dvojici ukazatelů. Tyto ukazatele na straně Pythonu „vyrobíme“ konstruktorem ffi.new, kterému v řetězci předáme jak očekávaný datový typ z pohledu céčka, tak i (nepovinnou) výchozí hodnotu. Pro přístup k takto reprezentovaným hodnotám se používá operátor indexování, přičemž při použití ukazatelů je jediným platným indexem nula:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void swap(int *x, int *y);
""")
 
    def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
swapper = load_library("libswapper.so")
 
x = ffi.new("int *", 10)
y = ffi.new("int *", 20)
 
swapper.swap(x, y)
print(x[0])
print(y[0])

Výsledek:

20
10

15. Kontrola, zda se s ukazatelem nezachází jako s polem

Mohlo by se zdát, že se objekty získané přes ffi.new(„int *“, 10) chovají jako pole, když se k jejím hodnotám přistupuje přes indexovací operátor. Ve skutečnosti je však jediným platným indexem nula, což je v době běhu programu kontrolováno:

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void swap(int *x, int *y);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
swapper = load_library("libswapper.so")
 
x = ffi.new("int *", 10)
y = ffi.new("int *", 20)
 
swapper.swap(x, y)
print(x[1])
print(y[1])

V tomto příkladu jsme se pokusili o přístup k prvkům s indexem 1, což však není povoleno:

Traceback (most recent call last):
  File "call_via_cffi2.py", line 19, in <module>
    print(x[1])
IndexError: cdata 'int *' can only be indexed by 0

16. Práce s poli, konverze mezi polem a datovými typy Pythonu

Od ukazatelů (k nimž se ovšem ještě vrátíme příště) se dostáváme k problematice použití céčkových polí. Připomeňme si, že pole jsou společně s řetězci (což jsou vlastně taktéž pole) v céčku jedinými datovými typy, které se předávají odkazem a nikoli hodnotou (pole lze předat hodnotou v případě, že je zabaleno do struktury). Navíc se pole pro naprostou většinu operací (s výjimkou operátorů & a sizeof) chovají stejně, jako by se jednalo o ukazatel na typ, jenž odpovídá typu prvků pole:

int a[10];

V naprosté většině výrazů se s tímto polem pracuje, jakoby se jednalo o proměnnou typu int *. Podobně platí pro referencování prvků polí, že lze identifikátorem pole pracovat tak, jakoby se jednalo o ukazatel:

E1[E2] == (*((E1)+(E2)))

Interně jsou prvky pole uloženy za sebou bez použití výplně – a tudíž se jedná o zcela odlišnou formu reprezentace, než je tomu u Pythonních seznamů a n-tic.

17. Funkce psaná v C, která vyplní pole zvolenou hodnotou

Poslední céčkovskou funkcí, kterou dnes použijeme, je funkce určená pro vyplnění prvků pole zadanou hodnotou, které se předávají tři parametry:

  1. Ukazatel na první prvek pole
  2. Velikost pole, resp. počet prvků, které se mají změnit
  3. Nová hodnota prvků, která se má nastavit

Deklarace této funkce vypadá následovně:

extern void fill(int *array, int size, int value)
{
    int i;
    for (i=0; i<size; i++) {
        array[i] = value;
    }
}
Poznámka: samozřejmě by bylo možné použít k tomu určenou funkci ze standardní knihovny.

18. Volání funkce pro výplň pole z Pythonu, zobrazení výsledného pole

Výše uvedenou céčkovskou funkci nyní zavoláme s předáním pole, které je vytvořeno konstruktorem:

array = ffi.new("int[10]")

Nejprve naplníme prvních pět prvků pole hodnotou 42 a následně druhých pět prvků hodnotou –1:

filler.fill(array, len(array)//2, 42)
 
filler.fill(array+5, len(array)//2, -1)

Celý skript bude vypadat následovně:

bitcoin_skoleni

from cffi import FFI
 
ffi = FFI()
 
ffi.cdef("""
    void fill(int *x, int, int);
""")
 
def load_library(library_name):
    return ffi.dlopen(library_name)
 
 
filler = load_library("libfiller.so")
 
array = ffi.new("int[10]")
print(list(array))
 
filler.fill(array, len(array)//2, 42)
print(list(array))
 
filler.fill(array+5, len(array)//2, -1)
print(list(array))

Vypsané hodnoty:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[42, 42, 42, 42, 42, 0, 0, 0, 0, 0]
[42, 42, 42, 42, 42, -1, -1, -1, -1, -1]
Poznámka: komplikovanější příklady, v nichž se budou používat složitější datové struktury, si ukážeme příště.

19. Repositář s demonstračními příklady

Všechny Pythonovské skripty, které jsme si v dnešním článku ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu knihovnu cffi:

# Příklad Stručný popis Adresa
1 adder/adder.c funkce psaná v C, která sečte své dva celočíselné parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/adder.c
2 adder/call_via_cffi1.py zavolání céčkovské funkce přes cffi s korektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi1.py
3 adder/call_via_cffi2.py zavolání céčkovské funkce přes cffi s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi2.py
4 adder/call_via_cffi3.py zavolání céčkovské funkce přes cffi s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi3.py
5 adder/call_via_cffi.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_cffi.sh
6 adder/call_via_ctypes1.py zavolání céčkovské funkce přes ctypes s korektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes1.py
7 adder/call_via_ctypes2.py zavolání céčkovské funkce přes ctypes s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes2.py
8 adder/call_via_ctypes3.py zavolání céčkovské funkce přes ctypes s nekorektními parametry https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes3.py
9 adder/call_via_ctypes.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ca­ll_via_ctypes.sh
10 adder/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/ma­ke_library.sh
11 adder/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/adder/clean.sh
       
12 greeter/greeter.c funkce psaná v C, která na standardní výstup vytiskne řetězec https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/greeter.c
13 greeter/call_via_cffi1.py zavolání céčkovské funkce přes cffi s nekorektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi1.py
14 greeter/call_via_cffi2.py zavolání céčkovské funkce přes cffi s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi2.py
15 greeter/call_via_cffi3.py zavolání céčkovské funkce přes cffi s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi3.py
16 greeter/call_via_cffi.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_cffi.sh
17 greeter/call_via_ctypes1.py zavolání céčkovské funkce přes ctypes s nekorektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes1.py
18 greeter/call_via_ctypes2.py zavolání céčkovské funkce přes ctypes s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes2.py
19 greeter/call_via_ctypes3.py zavolání céčkovské funkce přes ctypes s korektním parametrem https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes3.py
20 greeter/call_via_ctypes.sh nastavení cest a spuštění všech tří předchozích Pythonovských skriptů https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/call_via_ctypes.sh
21 greeter/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/gre­eter/make_library.sh
22 greeter/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter/clean.sh
       
23 swapper/swapper.c céčkovská funkce prohazující obsah svých dvou parametrů předávaných referencí https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/swapper.c
24 swapper/call_via_cffi1.py zavolání céčkovské knihovny z jazyka Python (korektní varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi1.py
25 swapper/call_via_cffi2.py zavolání céčkovské knihovny z jazyka Python (nekorektní varianta) https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi2.py
26 swapper/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/call_via_cffi.sh
27 swapper/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swap­per/make_library.sh
28 swapper/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/swapper/clean.sh
       
29 filler/filler.c céčkovská funkce pro vyplnění části pole zadanou hodnotou https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/filler.c
30 filler/call_via_cffi.py zavolání céčkovské knihovny z jazyka Python https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ca­ll_via_cffi.py
31 filler/call_via_cffi.sh nastavení cest a spuštění Pythonovského skriptu https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ca­ll_via_cffi.sh
32 filler/make_library.sh skript pro překlad céčkovské funkce a vytvoření dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/ma­ke_library.sh
32 filler/clean.sh skript pro smazání objektového souboru i dynamicky linkované knihovny https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/filler/clean.sh

20. Odkazy na Internetu

  1. TIOBE Index for May 2023
    https://www.tiobe.com/tiobe-index/
  2. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  3. CFFI documentation
    https://cffi.readthedocs.i­o/en/latest/
  4. cffi 1.15.1 na PyPi
    https://pypi.org/project/cffi/
  5. Python Bindings: Calling C or C++ From Python
    https://realpython.com/python-bindings-overview/
  6. Interfacing with C/C++ Libraries
    https://docs.python-guide.org/scenarios/clibs/
  7. Cython, pybind11, cffi – which tool should you choose?
    http://blog.behnel.de/posts/cython-pybind11-cffi-which-tool-to-choose.html
  8. Python FFI with ctypes and cffi
    https://eli.thegreenplace­.net/2013/03/09/python-ffi-with-ctypes-and-cffi
  9. Propojení Go s Pythonem s využitím cgo a ctypes
    https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes/
  10. Propojení Go s Pythonem s využitím cgo a ctypes (2. část)
    https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes-2-cast/
  11. Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven/
  12. Programovací jazyk Rust: použití FFI při předávání struktur
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pri-predavani-struktur/
  13. Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven (2. část)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven-2-cast/
  14. Dynamic-link library
    https://en.wikipedia.org/wiki/Dynamic-link_library

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.