Obsah
1. Propojení Pythonu s nativními knihovnami s využitím balíčku ctypes: struktury a ukazatele
2. Nativní funkce, které budou volány z Pythonu
3. Pomocná funkce pro vykreslení pixelu
4. Volání nativních funkcí z Pythonu s předáním všech parametrů
5. Úplný zdrojový kód dnešního prvního demonstračního příkladu: céčková i Pythonní část
6. Předání většího množství parametrů do nativních funkcí volaných z Pythonu
7. Modifikace skriptu psaného v Pythonu, který nativní funkce volá
8. Předání struktury do nativních funkcí
9. Konstrukce struktury na straně skriptu psaného v Pythonu
10. Úplný zdrojový kód dnešního třetího demonstračního příkladu: céčková i Pythonní část
11. Nativní funkce akceptující ukazatel na strukturu
12. Předání ukazatele na strukturu z Pythonu do nativních funkcí
13. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu: céčková i Pythonní část
14. Struktura s hodnotami použitými při výpočtech fraktálů
15. Úplný zdrojový kód dnešního pátého demonstračního příkladu: céčková i Pythonní část
17. Ukázka nekorektní práce s pamětí
18. Zarovnání a výplně ve strukturách
19. Repositář s demonstračními příklady
1. Propojení Pythonu s nativními knihovnami s využitím balíčku ctypes: struktury a ukazatele
Propojení programovacího jazyka Python s jazykem C (popř. s Rustem nebo jazykem Zig) přináší zajímavé možnosti a můžeme tak vyřešit poměrně velké množství praktických problémů. V první řadě tato technologie umožňuje relativně snadné volání funkcí ze systémových knihoven, ale i dalších funkcí dostupných formou dynamicky sdílených knihoven. Díky tomu je možné spojit snadnost a rychlost tvorby aplikací v Pythonu (vysokoúrovňový jazyk s relativně velkou mírou abstrakce) s optimalizovaným nativním kódem. Dobrým příkladem takového propojení je projekt Numpy, v němž se výpočetně náročné části realizují nativními funkcemi, ovšem programátoři tyto funkce volají přímo z Pythonu. A příkladem propojení Pythonu s Rustem může být projekt Polars, se kterým jsme se na stránkách Roota taktéž již setkali v článcích Knihovna Polars: výkonnější alternativa ke knihovně Pandas a Knihovna Polars: výkonnější alternativa ke knihovně Pandas (datové rámce).
V úvodním článku o knihovně ctypes, v němž jsme toto téma probírali, jsme si ukázali, jakým způsobem je možné do nativních (céčkovských) funkcí předávat celočíselné hodnoty, hodnoty typu float/double a taktéž pole (resp. přesněji řečeno ukazatele na první prvky polí). Dnes si tyto informace doplníme o způsob předávání celých datových struktur (struct), a to jak hodnotou, tak i referencí (tedy přes ukazatel). To nám umožní jak snížení počtu předávaných parametrů, tak i zajištění větší čitelnosti výsledných programů: týká se to jak Pythonní části, tak i části naprogramované v jazyku C.
2. Nativní funkce, které budou volány z Pythonu
Připomeňme si ve stručnosti, jak vlastně vypadají nativní funkce, které budeme chtít volat ze skriptu naprogramovaného v Pythonu. Jedná se o funkce, které dokážou vypočítat klasickou Mandelbrotovu množinu i Juliovy množiny (pro zadané parametry). Výsledné obrázky jsou reprezentovány jednorozměrnými poli s prvky typu unsigned char (pixely jsou ukládány po řádcích ve formátu RGB se čtyřmi bajty na pixel) a pixely jsou obarveny na základě barvové palety (256 trojic hodnot, taktéž typu unsigned char). Tím jsme si do značné míry zjednodušili práci, protože do funkcí pro výpočet Mandelbrotovy množiny a Juliových množin předáváme celočíselné hodnoty, hodnoty typu double a taktéž ukazatele na typ unsighed char.
Funkce provádějící výpočet Mandelbrotovy množiny, vypadá následovně (kvůli jednoduchosti se neprovádí žádné optimalizace):
void render_mandelbrot(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < height; y++) { cx = xmin; for (x = 0; x < width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < 150) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / width; } cy += (ymax - ymin) / height; } }
Funkce pro výpočet Juliových množin navíc akceptuje další dva parametry cx a cy, které jsou typu double:
void render_julia(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels, double cx, double cy) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < height; y++) { zx0 = xmin; for (x = 0; x < width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < 150) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / width; } zy0 += (ymax - ymin) / height; } }
3. Pomocná funkce pro vykreslení pixelu
Pro úplnost si doplníme zdrojový kód pomocné funkce, která slouží pro vykreslení jednoho pixelu. Této funkci se předá ukazatel na první bajt pixelu v rastrovém obrázku (přes referenci!), barvová paleta a index do této palety. Funkce navíc změní referenci na aktivní pixel – další volání putpixel tedy vybarví následující pixel v rastrovém obrázku:
void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; }
4. Volání nativních funkcí z Pythonu s předáním všech parametrů
Podívejme se nyní na způsob volání nativních funkcí z Pythonu. Volat přitom budeme obě výše zmíněné funkce pro výpočet a vykreslení fraktálů, které mají hlavičky:
void render_mandelbrot(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels) { void render_julia(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels, double cx, double cy) {
Do těchto funkcí se předává několik typů hodnot:
- Celočíselné hodnoty typu unsigned int
- Hodnoty typu double
- Hodnoty typu const unsigned char * (ve skutečnosti pole 256×3 prvky)
- Hodnoty typu unsigned char * (obrázky šířka×výška×4 bajty)
Předání prvních dvou typů hodnot je triviální – Pythonovské proměnné typu int/long převedeme do nativní podoby pomocí ctypes.c_int() a proměnné typu double pak pomocí ctypes.c_double().
Pro převod barvové palety (256×3 prvky) na nativní pole prvků typu unsigned char si vytvoříme pomocnou funkci, která může vypadat následovně:
def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s
Příklad použití:
pal = palette_to_buffer(palette)
Následně si necháme (přímo v Pythonu) naalokovat buffer o takové velikosti, aby do něj bylo možné uložit všechny pixely obrázku. Každý pixel je uložen ve čtyřech bajtech, takže velikost bude rovna 4 * width * height. Pro alokaci bufferu použijeme funkci create_string_buffer, která však (i přes své jméno) akceptuje velikost v bajtech:
from ctypes import create_string_buffer def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt)
Způsob konstrukce obou výše zmíněných objektů a volání nativních funkcí může vypadat takto:
pal = palette_to_buffer(palette) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_double(cx), c_double(cy), ) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") ... ... ...
5. Úplný zdrojový kód dnešního prvního demonstračního příkladu: céčková i Pythonní část
Dnešní první demonstrační příklad má dvě části: nativní (céčkovou) a Pythonní. Zdrojový kód céčkové části vypadá následovně:
void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; } void render_mandelbrot(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < height; y++) { cx = xmin; for (x = 0; x < width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < 150) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / width; } cy += (ymax - ymin) / height; } } void render_julia(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels, double cx, double cy) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < height; y++) { zx0 = xmin; for (x = 0; x < width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < 150) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / width; } zy0 += (ymax - ymin) / height; } }
Překlad této části se provede přes Makefile:
.PHONY: all clean CC=gcc CFLAGS=-Wall -pedantic -ansi -O2 all: renderer.so clean: rm -f renderer.so renderer.so: renderer.c $(CC) $(CFLAGS) -shared -Wl,-soname,renderer -o $@ -fPIC $<
Část naprogramovaná v Pythonu bude vypadat následovně:
import sys from ctypes import CDLL, c_double, c_int, create_string_buffer from palette_mandmap import palette import pygame import pygame.locals TITLE = "Renderer" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt) def main(): pal = palette_to_buffer(palette) display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot(c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_double(cx), c_double(cy), ) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
6. Předání většího množství parametrů do nativních funkcí volaných z Pythonu
V dnešním prvním demonstračním příkladu, který jsme si ukázali v předchozích kapitolách, se vyskytovala magická konstanta 150 představující maximální počet iterací při výpočtu barev pixelů v Mandelbrotově nebo Juliových množinách. Tuto konstantu pochopitelně můžeme nahradit za parametr (což by bylo více než vhodné). Obě funkce se nepatrně změní – modifikuje se jejich hlavička (což je pochopitelné) i vnitřní smyčka s výpočtem. Nativní část aplikace se změní následovně (modifikované části jsou podtrženy):
void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; } void render_mandelbrot(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < height; y++) { cx = xmin; for (x = 0; x < width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / width; } cy += (ymax - ymin) / height; } } void render_julia(unsigned int width, unsigned int height, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter, double cx, double cy) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < height; y++) { zx0 = xmin; for (x = 0; x < width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / width; } zy0 += (ymax - ymin) / height; } }
7. Modifikace skriptu psaného v Pythonu, který nativní funkce volá
Samozřejmě je nutné modifikovat i Pythonovský skript, který obě výše zmíněné nativní funkce volá. Změny vypadají takto:
MAXITER = 150 ... ... ... # render Mandelbrot set into buffer renderer.render_mandelbrot( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_int(MAXITER) ) ... ... ... # render Julia set into buffer renderer.render_julia( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), )
Pro úplnost si ukažme, jak vypadá celý skript, který je součástí dnešního druhého demonstračního příkladu. Modifikované části jsou opět podtrženy:
import sys from ctypes import CDLL, c_double, c_int, create_string_buffer from palette_mandmap import palette import pygame import pygame.locals TITLE = "Renderer" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 MAXITER = 150 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt) def main(): pal = palette_to_buffer(palette) display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_int(MAXITER) ) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( c_int(IMAGE_WIDTH), c_int(IMAGE_HEIGHT), pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), ) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
8. Předání struktury do nativních funkcí
Jak jsme mohli vidět v předchozích kapitolách, počet předávaných parametrů do nativních funkcí poměrně rychle narůstá, což vede k několika důsledkům. Zejména se jak zápis nativních funkcí, tak i jejich volání z Pythonu stává poměrně špatně čitelné – kód je „roztažen“ na několik řádků zdrojového kódu atd. A navíc ctypes neprovádí striktní kontroly, zda byly skutečně předány všechny parametry, což může vést k pádům programů, vznikům nekonečných smyček apod.
Jedno z možných (i když jen částečných) řešení tohoto problému spočívá v tom, že některé skupiny parametrů budeme předávat formou struktury, tj. céčku s využitím typu struct.
Například první dva parametry předávané do funkcí render_mandelbrot a render_julia představují rozměry obrázku a proto můžeme namísto nich použít tuto jednoduchou strukturu:
typedef struct { unsigned int width; unsigned int height; } image_size_t;
Zdrojový těchto dvou funkcí se změní následovně (změny jsou opět podtrženy, podobně jako u předchozích příkladů):
void render_mandelbrot(image_size_t image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter) { ... ... ... for (y = 0; y < image_size.height; y++) { ... for (x = 0; x < image_size.width; x++) { ... ... ... cx += (xmax - xmin) / image_size.width; } cy += (ymax - ymin) / image_size.height; ... } } void render_julia(image_size_t image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter, double cx, double cy) { ... ... ... for (y = 0; y < image_size.height; y++) { zx0 = xmin; for (x = 0; x < image_size.width; x++) { ... ... ... zx0 += (xmax - xmin) / image_size.width; } zy0 += (ymax - ymin) / image_size.height; } }
9. Konstrukce struktury na straně skriptu psaného v Pythonu
Nyní nám zbývají vyřešit dva další kroky. Na straně programovacího jazyka Python musíme nějakým způsobem vytvořit hodnotu, která odpovídá nativní céčkové struktuře a následně tuto hodnotu předat do volané nativní funkce. V praxi se jedná o relativně snadno řešitelné kroky. Vytvoříme třídu odvozenou od třídy ctypes.Structure, ve které nadeklarujeme třídní atribut nazvaný _fields_. Tento atribut bude obsahovat seznam dvojic „jméno nativního atributu“+„typ nativního atributu“
class ImageSize(Structure): _fields_ = [ ("width", c_uint), ("height", c_uint) ]
Dále zkonstruujeme instanci této třídy, což je snadné (oba předávané parametry jsou typu int/long):
image_size = ImageSize(IMAGE_WIDTH, IMAGE_HEIGHT)
A následně již můžeme tuto instanci použít při volání obou nativních funkcí:
# render Mandelbrot set into buffer renderer.render_mandelbrot(image_size, pal, buffer, c_int(MAXITER)) ... ... ... # render Julia set into buffer renderer.render_julia( image_size, pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), )
10. Úplný zdrojový kód dnešního třetího demonstračního příkladu: céčková i Pythonní část
Opět se podívejme na úplný zdrojový kód dnešního v pořadí již třetího demonstračního příkladu. Nejdříve začneme s jeho céčkovskou částí:
typedef struct { unsigned int width; unsigned int height; } image_size_t; void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; } void render_mandelbrot(image_size_t image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < image_size.height; y++) { cx = xmin; for (x = 0; x < image_size.width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / image_size.width; } cy += (ymax - ymin) / image_size.height; } } void render_julia(image_size_t image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter, double cx, double cy) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < image_size.height; y++) { zx0 = xmin; for (x = 0; x < image_size.width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / image_size.width; } zy0 += (ymax - ymin) / image_size.height; } }
Následuje část naprogramovaná v Pythonu, ze které se volají výše uvedené funkce render_mandelbrot a render_julia:
import sys from ctypes import ( CDLL, c_double, c_int, c_uint, create_string_buffer, Structure, ) from palette_mandmap import palette import pygame import pygame.locals TITLE = "Renderer" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 MAXITER = 150 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt) class ImageSize(Structure): _fields_ = [("width", c_uint), ("height", c_uint)] def main(): image_size = ImageSize(IMAGE_WIDTH, IMAGE_HEIGHT) pal = palette_to_buffer(palette) display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot(image_size, pal, buffer, c_int(MAXITER)) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( image_size, pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), ) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
11. Nativní funkce akceptující ukazatel na strukturu
Struktura, kterou do funkcí naprogramovaných v jazyku C předáváme, je prozatím velmi malá, protože obsahuje pouze dvě hodnoty typu unsigned int:
typedef struct { unsigned int width; unsigned int height; } image_size_t;
Ovšem v případě, že by se do struktury přidávaly další prvky, je na čase se ptát, zda nebude výhodnější nepředávat celou strukturu hodnotou, ale odkazem (tedy přes ukazatel). V takovém případě se samotné funkce změní jen nepatrně – modifikuje se pochopitelně jejich hlavička a přístup k prvkům struktury bude realizován operátorem → a nikoli operátorem tečky (větší je sémantický rozdíl – nyní je struktura měnitelná):
void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter) { ... ... ... for (y = 0; y < image_size->height; y++) { ... for (x = 0; x < image_size->width; x++) { ... ... ... cx += (xmax - xmin) / image_size->width; } ... cy += (ymax - ymin) / image_size->height; } }
12. Předání ukazatele na strukturu z Pythonu do nativních funkcí
Pokud nativní funkce volané z Pythonu vyžadují jako některý svůj parametr ukazatel na strukturu, musíme být schopní takový ukazatel získat přímo v Pythonu. Celý postup je ve skutečnosti poměrně jednoduchý.
Nejprve musíme naimportovat mj. i funkci pointer z modulu ctypes:
from ctypes import ( CDLL, c_double, c_int, c_uint, create_string_buffer, Structure, pointer, )
Samotná definice třídy představující strukturu se nijak nemění:
class ImageSize(Structure): _fields_ = [("width", c_uint), ("height", c_uint)]
Totéž platí i pro získání instance této třídy:
image_size = ImageSize(IMAGE_WIDTH, IMAGE_HEIGHT)
Ovšem při volání nativních funkcí předáváme ukazatel – viz podtržená část kódu:
pal = palette_to_buffer(palette) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot(pointer(image_size), pal, buffer, c_int(MAXITER))
Naprosto totéž provedeme i při volání nativní funkce pro výpočet a vykreslení Juliovy množiny:
cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( pointer(image_size), pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), )
13. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu: céčková i Pythonní část
Dnešní třetí demonstrační příklad se změnami popsanými v předchozí dvojici kapitol, je opět rozdělený na céčkovou a Pythonní část. Céčková část vypadá takto:
typedef struct { unsigned int width; unsigned int height; } image_size_t; void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; } void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < image_size->height; y++) { cx = xmin; for (x = 0; x < image_size->width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / image_size->width; } cy += (ymax - ymin) / image_size->height; } } void render_julia(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, unsigned int maxiter, double cx, double cy) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < image_size->height; y++) { zx0 = xmin; for (x = 0; x < image_size->width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / image_size->width; } zy0 += (ymax - ymin) / image_size->height; } }
Následuje část naprogramovaná v Pythonu, ze které se volají nativní funkce:
import sys from ctypes import ( CDLL, c_double, c_int, c_uint, create_string_buffer, Structure, pointer, ) from palette_mandmap import palette import pygame import pygame.locals TITLE = "Renderer" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 MAXITER = 150 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt) class ImageSize(Structure): _fields_ = [("width", c_uint), ("height", c_uint)] def main(): image_size = ImageSize(IMAGE_WIDTH, IMAGE_HEIGHT) pal = palette_to_buffer(palette) display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot(pointer(image_size), pal, buffer, c_int(MAXITER)) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") cx = -0.171119200000000013445 cy = 0.657309400000000000000 # render Julia set into buffer renderer.render_julia( pointer(image_size), pal, buffer, c_double(cx), c_double(cy), c_int(MAXITER), ) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
14. Struktura s hodnotami použitými při výpočtech fraktálů
Výpočet Mandelbrotovy množiny a Juliových množin je možné ovlivnit různými parametry. V první řadě se jedná o údaj o maximálním počtu iterací a u Juliových množin taktéž o hodnotu komplexní konstanty c. Tyto parametry jsme prozatím předávali samostatně: jako maxiter, cx a cy (pojmenování reálné a imaginární složky komplexní konstanty c). Ovšem nic nám nebrání v tom, abychom tyto parametry předali společně v jediné (později rozšiřitelné) struktuře. Její tvar může být následující:
typedef struct { unsigned int maxiter; double cx; double cy; } rendering_params_t;
Tuto strukturu můžeme předat referencí, tedy přes ukazatel. Samotné výpočty se změní jen nepatrně:
void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { ... ... ... while (i < rendering_params->maxiter) { ... ... ... } ... ... ... }
a:
void render_julia(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { ... ... ... while (i < rendering_params->maxiter) { ... ... ... zy = 2.0 * zx * zy + rendering_params->cy; zx = zx2 - zy2 + rendering_params->cx; ... ... ... } ... ... ... }
Na straně Pythonu vytvoříme příslušnou třídu, která bude strukturu reprezentovat:
class RenderingParams(Structure): _fields_ = [("maxiter", c_uint), ("cx", c_double), ("cy", c_double)]
Inicializace a předání přes ukazatel bude vypadat následovně:
rendering_params = RenderingParams(MAXITER, cx, cy) ... ... ... # try to load dynamically linked library renderer = CDLL("./renderer.so") ... ... ... # render Mandelbrot set into buffer renderer.render_mandelbrot(pointer(image_size), pal, buffer, pointer(rendering_params)) ... ... ... # render Julia set into buffer renderer.render_julia(pointer(image_size), pal, buffer, pointer(rendering_params)) ... ... ... # finito
15. Úplný zdrojový kód dnešního pátého demonstračního příkladu: céčková i Pythonní část
Dnešní pátý demonstrační příklad se změnami popsanými v předchozí kapitole je opět rozdělený na céčkovou a Pythonní část. Céčková část vypadá takto (povšimněte si definice dvou struktur na jeho začátku):
typedef struct { unsigned int width; unsigned int height; } image_size_t; typedef struct { unsigned int maxiter; double cx; double cy; } rendering_params_t; void putpixel(unsigned char **pixel, const unsigned char *palette, int color_index) { int color_offset = color_index * 3; unsigned char *pal = (unsigned char *)(palette + color_offset); *(*pixel)++ = *pal++; *(*pixel)++ = *pal++; *(*pixel)++ = *pal; (*pixel)++; } void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { int x, y; double cx, cy; double xmin = -2.0, ymin = -1.5, xmax = 1.0, ymax = 1.5; unsigned char *p = pixels; cy = ymin; for (y = 0; y < image_size->height; y++) { cx = xmin; for (x = 0; x < image_size->width; x++) { double zx = 0.0; double zy = 0.0; unsigned int i = 0; while (i < rendering_params->maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + cy; zx = zx2 - zy2 + cx; i++; } putpixel(&p, palette, i); cx += (xmax - xmin) / image_size->width; } cy += (ymax - ymin) / image_size->height; } } void render_julia(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { int x, y; double zx0, zy0; double xmin = -1.5, ymin = -1.5, xmax = 1.5, ymax = 1.5; unsigned char *p = pixels; zy0 = ymin; for (y = 0; y < image_size->height; y++) { zx0 = xmin; for (x = 0; x < image_size->width; x++) { double zx = zx0; double zy = zy0; unsigned int i = 0; while (i < rendering_params->maxiter) { double zx2 = zx * zx; double zy2 = zy * zy; if (zx2 + zy2 > 4.0) { break; } zy = 2.0 * zx * zy + rendering_params->cy; zx = zx2 - zy2 + rendering_params->cx; i++; } putpixel(&p, palette, i); zx0 += (xmax - xmin) / image_size->width; } zy0 += (ymax - ymin) / image_size->height; } }
Část naprogramovaná v Pythonu má tento tvar:
import sys from ctypes import ( CDLL, c_double, c_int, c_uint, create_string_buffer, Structure, pointer, ) from palette_mandmap import palette import pygame import pygame.locals TITLE = "Renderer" SCREEN_WIDTH = 600 SCREEN_HEIGHT = 300 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 MAXITER = 150 def initialize_ui(title, width, height): """Initialize Pygame display, drawing surface, and clocks.""" # set window title pygame.display.set_caption(title) # initialize window display = pygame.display.set_mode([width, height]) display.fill((0, 0, 0)) clock = pygame.time.Clock() return display, clock def palette_to_buffer(p): s = create_string_buffer(len(p) * 3) i = 0 for color in p: s[i] = color[0] s[i + 1] = color[1] s[i + 2] = color[2] i += 3 return s def event_loop(display, image1, image2, clock): while True: for event in pygame.event.get(): if event.type == pygame.locals.QUIT: pygame.quit() sys.exit() if event.type == pygame.locals.KEYDOWN: if event.key == pygame.locals.K_ESCAPE: pygame.quit() sys.exit() # all events has been processed - update scene and redraw the screen display.blit(image1, (30, 20)) display.blit(image2, (60 + image1.get_width(), 20)) # and update the whole display pygame.display.update() clock.tick(25) def image_from_buffer(buffer, width, height, fmt): return pygame.image.frombytes(bytes(buffer), (width, height), fmt) class ImageSize(Structure): _fields_ = [("width", c_uint), ("height", c_uint)] class RenderingParams(Structure): _fields_ = [("maxiter", c_uint), ("cx", c_double), ("cy", c_double)] def main(): image_size = ImageSize(IMAGE_WIDTH, IMAGE_HEIGHT) cx = -0.171119200000000013445 cy = 0.657309400000000000000 rendering_params = RenderingParams(MAXITER, cx, cy) pal = palette_to_buffer(palette) display, clock = initialize_ui(TITLE, SCREEN_WIDTH, SCREEN_HEIGHT) # try to load dynamically linked library renderer = CDLL("./renderer.so") # create buffer for raster image buffer = create_string_buffer(4 * IMAGE_WIDTH * IMAGE_HEIGHT) # render Mandelbrot set into buffer renderer.render_mandelbrot( pointer(image_size), pal, buffer, pointer(rendering_params) ) image1 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") # render Julia set into buffer renderer.render_julia(pointer(image_size), pal, buffer, pointer(rendering_params)) image2 = image_from_buffer(buffer, IMAGE_WIDTH, IMAGE_HEIGHT, "RGBX") event_loop(display, image1, image2, clock) if __name__ == "__main__": main() # finito
16. Správa paměti
Prozatím jsme si ukázali tu nejjednodušší formu komunikace mezi skriptem psaným v Pythonu a nativními funkcemi. Tato komunikace spočívala v tom, že veškeré objekty byly alokovány na straně Pythonu a následně byly předávány do volaných nativních funkcí. Mohlo by se tedy zdát, že se vůbec nemusíme starat o správu paměti. V praxi je to samozřejmě mnohem komplikovanější a mohou nastat situace, kdy je nutné, aby paměť byla alokována na straně céčkovských funkcí (a vrácena přes ukazatel) nebo aby se prováděla explicitní dealokace paměti pro objekty vytvořené jak na straně Pythonu, tak i na straně céčka. To celou komunikaci skutečně komplikuje a vlastně tak do Pythonu (částečně) zavádíme některé postupy, které zde nebylo nutné realizovat. V navazující kapitole si ukážeme některé nekorektní operace, zejména pokus o dealokaci objektů (provedené v céčkových funkcích), které nejsou dealokovatelné nebo jsou naopak explicitně dealokovány na straně Pythonu. Později si ukážeme jedno možné řešení tohoto problému, které ovšem není ani zdaleka ideální.
17. Ukázka nekorektní práce s pamětí
Nativní funkce, které byly použity v předchozích demonstračních příkladech, nyní upravíme, a to konkrétně do takové podoby, aby se z volaly standardní funkce free s předáním ukazatelů na obě datové struktury (image_size_t * a rendering_params_t *). To je pochopitelně zcela nekorektní chování:
#include <stdlib.h> typedef struct { unsigned int width; unsigned int height; } image_size_t; typedef struct { unsigned int maxiter; double cx; double cy; } rendering_params_t; void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { free(image_size); free(rendering_params); } void render_julia(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { free(image_size); free(rendering_params); }
Bude zajímavé zjistit, jak se bude aplikace chovat, pokud obě funkce zavoláme z Pythonu a předáme jim ukazatele na struktury:
pygame 2.6.1 (SDL 2.28.4, Python 3.12.10) Hello from the pygame community. https://www.pygame.org/contribute.html free(): invalid pointer Command terminated
Nekorektní chování – pokus o uvolnění paměti na zásobníku – byl odhalen a aplikace byla ukončena.
Taktéž nebude dovolena dvojí dealokace paměti s bitmapou:
#include <stdlib.h> typedef struct { unsigned int width; unsigned int height; } image_size_t; typedef struct { unsigned int maxiter; double cx; double cy; } rendering_params_t; void render_mandelbrot(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { free(pixels); } void render_julia(image_size_t *image_size, const unsigned char *palette, unsigned char *pixels, rendering_params_t *rendering_params) { free(pixels); }
Chyba bude v tomto případě odlišná, ale opět je snadno detekovatelná standardními knihovnami a jejich runtime systémem:
pygame 2.6.1 (SDL 2.28.4, Python 3.12.10) Hello from the pygame community. https://www.pygame.org/contribute.html double free or corruption (!prev) Command terminated
18. Zarovnání a výplně ve strukturách
V praxi při realizaci komunikace mezi programem psaným v Pythonu a nativními funkcemi psanými (zejména) v jazyku C narazíme ještě na jeden problém: jak zajistit korektní inicializaci struktury, ve které jsou prvky různým způsobem zarovnány a kam jsou popřípadě vloženy nějaké výplně. Prozatím jsme na tento problém nenarazili, protože knihovna ctypes počítá s tím, že u céčkových struktur budou zachována výchozí pravidla pro zarovnání/výplně. Ovšem v praxi můžeme narazit i na struktury, ve kterých je zarovnání ovlivněno programátorem (a to ještě nepočítáme s existencí bitových polí). V tomto případě se pochopitelně musí Python nové struktuře s jinou velikostí a jinými offsety prvků přizpůsobit. Jak toho je možné dosáhnout, si vysvětlíme v samostatném (již mnohem stručnějším) článku.
19. Repositář s demonstračními příklady
V minulém i dnešním článku popsané demonstrační příklady naleznete na GitHubu:
Navíc si pro úplnost uveďme demonstrační příklady použité v článcích o knihovně cffi. I v těchto článcích jsme se totiž o ctypes zmiňovali:
# | 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/call_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/call_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/call_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/call_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/call_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/call_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/call_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/call_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/make_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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/greeter/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/swapper/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/swapper/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/swapper/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/swapper/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/swapper/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/call_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/call_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/make_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 |
33 | greeter_h/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/greeter_h/greeter.c |
34 | greeter_h/greeter.h | prototyp (předběžná deklarace) funkce greeter | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h/greeter.h |
35 | greeter_h/call_via_cffi4.py | zavolání céčkovské knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h/call_via_cffi4.py |
36 | greeter_h/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h/call_via_cffi.sh |
37 | greeter_h/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/greeter_h/make_library.sh |
38 | greeter_h/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_h/clean.sh |
39 | greeter_h2/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/greeter_h2/greeter.c |
40 | greeter_h2/greeter.h | prototyp (předběžná deklarace) funkce greeter obalená v testu na existenci symbolu/makra | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h2/greeter.h |
41 | greeter_h2/call_via_cffi5.py | zavolání céčkovské knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h2/call_via_cffi5.py |
42 | greeter_h2/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h2/call_via_cffi.sh |
43 | greeter_h2/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/greeter_h2/make_library.sh |
44 | greeter_h2/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_h2/clean.sh |
45 | greeter_h3/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/greeter_h3/greeter.c |
46 | greeter_h3/greeter.h | test na existenci symbolu/makra, pokud makro neexistuje, provede se vložení dalšího souboru | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h3/greeter.h |
47 | greeter_h3/_greeter.h | prototyp (předběžná deklarace) funkce greeter bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h3/_greeter.h |
48 | greeter_h3/call_via_cffi5.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h3/call_via_cffi5.py |
49 | greeter_h3/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_h3/call_via_cffi.sh |
50 | greeter_h3/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/greeter_h3/make_library.sh |
51 | greeter_h3/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_h3/clean.sh |
52 | greeter_build/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/greeter_build/greeter.c |
53 | greeter_build/greeter.h | prototyp (předběžná deklarace) funkce greeter bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_build/greeter.h |
54 | greeter_build/call_via_cffi7.py | skript pro překlad céčkovské funkce, vytvoření dynamicky linkované knihovny a zavolání funkce z této knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/greeter_build/call_via_cffi7.py |
55 | greeter_build/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_build/clean.sh |
56 | vector_printer/vector_printer.c | funkce psaná v C, která akceptuje jako svůj parametr strukturu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer/vector_printer.c |
57 | vector_printer/vector_printer.h | prototyp (předběžná deklarace) funkce print_vector bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer/vector_printer.h |
58 | vector_printer/call_via_cffi.sh | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer/call_via_cffi.sh |
59 | vector_printer/call_via_cffi.py | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer/call_via_cffi.py |
60 | vector_printer/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/vector_printer/make_library.sh |
61 | vector_printer/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer/clean.sh |
62 | vector_printer2/vector_printer.c | funkce psaná v C, která akceptuje jako svůj parametr ukazatel na strukturu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer2/vector_printer.c |
63 | vector_printer2/vector_printer.h | prototyp (předběžná deklarace) funkce print_vector bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer2/vector_printer.h |
64 | vector_printer2/call_via_cffi.sh | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer2/call_via_cffi.sh |
65 | vector_printer2/call_via_cffi.py | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer2/call_via_cffi.py |
66 | vector_printer2/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/vector_printer2/make_library.sh |
67 | vector_printer2/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/vector_printer2/clean.sh |
68 | array_printer1/array_printer.c | funkce naprogramovaná v C, která akceptuje pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer1/array_printer.c |
69 | array_printer1/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer1/array_printer.h |
70 | array_printer1/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer1/call_via_cffi.sh |
71 | array_printer1/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer1/call_via_cffi.py |
72 | array_printer1/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/array_printer1/make_library.sh |
73 | array_printer1/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer1/clean.sh |
74 | array_printer2/array_printer.c | funkce naprogramovaná v C, která akceptuje pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer2/array_printer.c |
75 | array_printer2/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer2/array_printer.h |
76 | array_printer2/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer2/call_via_cffi.sh |
77 | array_printer2/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer2/call_via_cffi.py |
78 | array_printer2/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/array_printer2/make_library.sh |
79 | array_printer2/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer2/clean.sh |
80 | array_printer3/array_printer.c | funkce naprogramovaná v C, která akceptuje pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer3/array_printer.c |
81 | array_printer3/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer3/array_printer.h |
82 | array_printer3/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer3/call_via_cffi.sh |
83 | array_printer3/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer3/call_via_cffi.py |
84 | array_printer3/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/array_printer3/make_library.sh |
85 | array_printer3/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer3/clean.sh |
86 | array_printer4/array_printer.c | funkce naprogramovaná v C, která akceptuje pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer4/array_printer.c |
87 | array_printer4/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer4/array_printer.h |
88 | array_printer4/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer4/call_via_cffi.sh |
89 | array_printer4/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer4/call_via_cffi.py |
90 | array_printer4/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/array_printer4/make_library.sh |
91 | array_printer4/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer4/clean.sh |
92 | array_printer5/array_printer.c | funkce naprogramovaná v C, která akceptuje pole s prvky typu vector_t | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer5/array_printer.c |
93 | array_printer5/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer5/array_printer.h |
94 | array_printer5/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer5/call_via_cffi.sh |
95 | array_printer5/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer5/call_via_cffi.py |
96 | array_printer5/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/array_printer5/make_library.sh |
97 | array_printer5/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer5/clean.sh |
92 | array_printer6/array_printer.c | funkce naprogramovaná v C, která akceptuje dvourozměrné pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer6/array_printer.c |
93 | array_printer6/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer6/array_printer.h |
94 | array_printer6/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer6/call_via_cffi.sh |
95 | array_printer6/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer6/call_via_cffi.py |
96 | array_printer6/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/array_printer6/make_library.sh |
97 | array_printer6/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer6/clean.sh |
98 | array_printer7/array_printer.c | funkce naprogramovaná v C, která akceptuje dvourozměrné pole s prvky typu float | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer7/array_printer.c |
99 | array_printer7/array_printer.h | prototyp (předběžná deklarace) funkce print_array bez dalších informací | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer7/array_printer.h |
100 | array_printer7/call_via_cffi.py | zavolání céčkovské funkce z knihovny z jazyka Python | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer7/call_via_cffi.sh |
101 | array_printer7/call_via_cffi.sh | nastavení cest a spuštění Pythonovského skriptu | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer7/call_via_cffi.py |
102 | array_printer7/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/array_printer7/make_library.sh |
103 | array_printer7/clean.sh | skript pro smazání objektového souboru i dynamicky linkované knihovny | https://github.com/tisnik/most-popular-python-libs/blob/master/cffi/array_printer7/clean.sh |
20. Odkazy na Internetu
- ctypes – A foreign function library for Python
https://docs.python.org/3/library/ctypes.html - Pygame: display
https://www.pygame.org/docs/ref/display.html - Pygame: event
https://www.pygame.org/docs/ref/event.html - Pygame: image
https://www.pygame.org/docs/ref/image.html - Pygame: clock
https://www.pygame.org/docs/ref/time.html#pygame.time.Clock - Fraktály v počítačové grafice XII
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xii/ - Fraktály v počítačové grafice XIII
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xiii/ - Fraktály v počítačové grafice XIV
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-xiv/ - CFFI documentation
https://cffi.readthedocs.io/en/latest/ - cffi 1.15.1 na PyPi
https://pypi.org/project/cffi/ - Python Bindings: Calling C or C++ From Python
https://realpython.com/python-bindings-overview/ - Interfacing with C/C++ Libraries
https://docs.python-guide.org/scenarios/clibs/ - Cython, pybind11, cffi – which tool should you choose?
http://blog.behnel.de/posts/cython-pybind11-cffi-which-tool-to-choose.html - Python FFI with ctypes and cffi
https://eli.thegreenplace.net/2013/03/09/python-ffi-with-ctypes-and-cffi - 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/ - 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/ - Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven
https://www.root.cz/clanky/programovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven/ - Programovací jazyk Rust: použití FFI při předávání struktur
https://www.root.cz/clanky/programovaci-jazyk-rust-pouziti-ffi-pri-predavani-struktur/ - Programovací jazyk Rust: použití FFI pro volání funkcí z nativních knihoven (2. část)
https://www.root.cz/clanky/programovaci-jazyk-rust-pouziti-ffi-pro-volani-funkci-z-nativnich-knihoven-2-cast/ - Dynamic-link library
https://en.wikipedia.org/wiki/Dynamic-link_library - Úvod do jazyka C: Deklarace funkcí
https://www.fi.muni.cz/usr/jkucera/pb071/sl5.htm - Using standard library headers with CFFI
https://stackoverflow.com/questions/57481873/using-standard-library-headers-with-cffi - Preparing and Distributing modules
https://cffi.readthedocs.io/en/latest/cdef.html - C Arrays
https://www.programiz.com/c-programming/c-arrays - C Arrays
https://www.w3schools.com/c/c_arrays.php - Array of Structures in C
https://overiq.com/c-programming-101/array-of-structures-in-c/#google_vignette - C Structures (structs)
https://www.w3schools.com/c/c_structs.php - C Structs and pointers
https://www.programiz.com/c-programming/c-structures-pointers