Skip to content

Instantly share code, notes, and snippets.

@danieljfarrell
Created February 26, 2021 18:28
Show Gist options
  • Save danieljfarrell/bc455548ae7ff9133104766ff55bb168 to your computer and use it in GitHub Desktop.
Save danieljfarrell/bc455548ae7ff9133104766ff55bb168 to your computer and use it in GitHub Desktop.
Idioms for handling C struct in Cython
# Idiom - C struct container simple values
#
# Idiom - Use ctypedef so we can can pass this
# easily in and out of functions.
#
cdef struct book:
int uid
ctypedef book book_t
# Idiom - C struct of arrays
#
# Struct owns a pointer to the data type
# and we include `count` so we know how
# many items are in the array.
#
cdef struct shelf:
book_t * books
int count
ctypedef shelf shelf_t
# Idiom - Struct create function
#
# This will return a pointer to a
# dynamically created struct.
cdef book_t * create_book(int)
# Idiom - Struct free function
#
# This takes the pointer created by the
# create function and frees the memory.
cdef void free_book(book_t*)
# Idiom - A container C struct can be created by copying the
# buffer of items. Use this if you want to maintain your
# own copy of the data.
cdef shelf_t * create_shelf_by_copying(book_t * books, int count)
# Idiom - A container C struct can be created by referencing
# the items in the input buffer. Use this if you want to
# share the items.
cdef shelf_t * create_shelf_by_referencing(book_t * books, int count)
cdef void free_shelf(shelf_t*)
from libc.stdlib cimport malloc, free
from libc.string cimport memcpy
cdef book_t * create_book(int uid):
# Idiom - Create function allocate memory
# Create function dynamically allocate memory to for
# one C struct. Later this "create" call should be
# paired with a "free" call to avoid a memory leak.
cdef book_t * mybook = <book_t*>malloc(sizeof(book_t))
# Idiom - Cython allows direct access to the attributes
# of the C struct eventhrough it is a pointer to the
# C struct
mybook.uid = uid
return mybook
cdef void free_book(book_t* mybook):
# Idiom - Structs need to be freed. All "create" calls
# must be paired with a "free" call a the end of the
# struct's life.
free(mybook)
cdef shelf_t * create_shelf_by_copying(book_t * books, int count):
# Idiom - Container structs can be created by copying the
# contents of another buffer into dynamically allocated memory
# Dynamically allocate the shelf
cdef shelf_t * shelfptr = <shelf_t *> malloc(sizeof(shelf_t))
# Dynamically allocate space on the shelf
shelfptr.books = <book_t *> malloc(sizeof(book_t) * count)
# Idiom - Note that eventhrough shelfptr is (shelf_t *) Cython allow
# us to access attribute directly using the dot notation. If coming
# from C background this is confusing because you would need to use
# `->` notiation.
# Copy the books buffer into out dynamically created buffer
memcpy(<book_t*> shelfptr.books, <book_t*>books, sizeof(book_t) * count)
return shelfptr
cdef shelf_t * create_shelf_by_referencing(book_t * books, int count):
# Idiom - Container structs can be created by reference the
# contents of another buffer! Useful to save space but only
# use if you know other threads are not writing to the buffer.
cdef shelf_t * shelfptr = <shelf_t *> malloc(sizeof(shelf_t))
shelfptr.books = books
return shelfptr
cdef void free_shelf(shelf_t* shelf):
free(shelf)
cdef _c_test_shelf_create():
print("Running Demo")
# Idiom - Dynamic allocation of array of C struct
cdef book_t * books = <book_t*> malloc(3 * sizeof(book_t))
# Idiom - Use `[0]` to get the value of the pointer
#
# `create_book` returns a `book_t *`. To assign the value
# being pointed at we need to use [0] because Cython does
# not have the `*` operator.
#
# Moreover, in C in you can do:
#
# book_t * bookptr = create_book(42)
# book[0] = *bookptr
# book[0] = bookptr[0]
#
# Cython only supports the latter.
books[0] = create_book(42)[0]
books[1] = create_book(43)[0]
books[2] = create_book(44)[0]
print("Dynamically created 3 books!")
print("First book in books array has UID: {}".format(books[0].uid))
print("Second book in books array has UID: {}".format(books[1].uid))
print("Third book in books array has UID: {}".format(books[2].uid))
print("")
print("Create shelf by COPYING contents of books array")
cdef shelf_t * copied_shelf = create_shelf_by_copying(books, 3)
print("First book in copied shelf has UID: {}".format(copied_shelf.books[0].uid))
print("Second book in copied shelf has UID: {}".format(copied_shelf.books[1].uid))
print("Third book in copied shelf has UID: {}".format(copied_shelf.books[2].uid))
print("")
print("Mutating first book in copied shelf to have UID 99")
copied_shelf.books[0].uid = 99
print("First book in copied shelf has UID: {}".format(copied_shelf.books[0].uid))
print("First book in books array _still_ has UID: {}".format(books[0].uid))
print("")
print("Create shelf by REFERENCE contents of books array")
cdef shelf_t * ref_shelf = create_shelf_by_referencing(books, 3)
print("First book in reference shelf has UID: {}".format(copied_shelf.books[0].uid))
print("Second book in reference shelf has UID: {}".format(copied_shelf.books[1].uid))
print("Third book in reference shelf has UID: {}".format(copied_shelf.books[2].uid))
print("")
print("Mutating first book in reference shelf to have UID 99")
ref_shelf.books[0].uid = 99
print("First book in reference shelf has UID: {}".format(ref_shelf.books[0].uid))
print("First book in books array _now_ has UID: {}".format(books[0].uid))
print("")
def _test_shelf_create():
_c_test_shelf_create()
@danieljfarrell
Copy link
Author

Output

Running Demo
Dynamically created 3 books!
First book in books array has UID: 42
Second book in books array has UID: 43
Third book in books array has UID: 44

Create shelf by COPYING contents of books array
First book in copied shelf has UID: 42
Second book in copied shelf has UID: 43
Third book in copied shelf has UID: 44

Mutating first book in copied shelf to have UID 99
First book in copied shelf has UID: 99
First book in books array _still_ has UID: 42

Create shelf by REFERENCE contents of books array
First book in reference shelf has UID: 99
Second book in reference shelf has UID: 43
Third book in reference shelf has UID: 44

Mutating first book in reference shelf to have UID 99
First book in reference shelf has UID: 99
First book in books array _now_ has UID: 99

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment