Skip to content

Latest commit

 

History

History
241 lines (164 loc) · 6.79 KB

oop-structs.rst

File metadata and controls

241 lines (164 loc) · 6.79 KB

Structs -- Almost Object-Oriented Programming

Nothing makes you miss object-oriented programming more than being stuck without it. Classes are C++ only (which doesn't support) but you can get pretty close using Structures.

does relatively little to help here. All it does is parse struct definitions from your source code and turn them into classes based on the ctypes.Structure template class.

Throughout this page snippets of an example C file will be included. You may access the whole file with the link below:

demo-stubs/cards

Defining a Struct

We'll start by writing a simple C file containing a struct definition:

../demos/structs/cards.c

Note that the simpler struct Person {...}; form of a struct definition is intentionally not supported. You must use the above typedef form.

Note

If you define a struct in a header file, you should add that file to your cslug.CSlug sources.

Using a Struct in Python

Let's compile the above code and play with it.

from cslug import CSlug
slug = CSlug("cards.c")

Structs are available as attributes in exactly the same way as functions. The structure class forms a neat bucket class in Python:

>>> slug.dll.Card(6, 2)
Card(face=6, suit=2)

With sensible (albeit non-configurable) defaults:

>>> slug.dll.Card(suit=1)
Card(face=0, suit=1)

And writeable, type-checked attributes corresponding to struct fields:

>>> slug.dll.Card(4).face
4

Passing a Struct from Python to C

First let's extend cards.c to define some uninspiring functions which take our struct as an argument. (Remember to call slug.make() after modifying the C code.) We can either pass a structure by value or with a pointer. The following adds a function for each case.

../demos/structs/cards.c

To pass by value you can just pass the struct as-is to a C function:

>>> slug.dll.get_card_face(slug.dll.Card(face=6))
6

To pass by pointer you need its memory address which is kept for convenience in a _ptr attribute. Its value is just the output of ctypes.addressof(card)<ctypes.addressof>.

>>> card = slug.dll.Card(face=7) >>> slug.dll.get_card_ptr_face(card._ptr) 7

Warning

Using slug.dll.Card(face=6)._ptr causes the card itself to be immediately deleted, leaving a . You must retain a reference to the original object until after you no longer need the pointer.

Passing a Struct from C to Python

By Value

Returning a struct by value from a C function is straight forward:

../demos/structs/cards.c

By Pointer

Returning a struct by pointer is not straight forward. In fact it's highly discouraged and considered bad C. If you're feeling brave, curious or foolhardy enough to dismiss this then read on:

The following naive approach creates a !Card and returns its address. But the card is deleted at the end of the function leaving another (your compiler should detect and warn you about this):

../demos/structs/cards.c

We can improve the situation by using malloc(), to reserve memory beyond the scope of this function call:

../demos/structs/cards.c

This has two problems. First doesn't implicitly dereference pointers:

>>> slug.dll.make_card_ptr_safer(1, 2)
90846685936

But this is easily solved:

>>> slug.dll.Card.from_address(slug.dll.make_card_ptr_safer(1, 2))
Card(face=1, suit=2)

Secondly, this is a because we allocate structs but never free them again:

# Watch Python's memory usage go up and up...
while True:
    slug.dll.make_card_ptr_safer(1, 2)

To deallocate, use free() to get the memory back once we no longer need the struct:

../demos/structs/cards.c

# This doesn't leak memory.
while True:
    card = slug.dll.Card.from_address(slug.dll.make_card_ptr_safer(1, 2))
    # Do something with the card.
    # Then delete it safely:
    slug.dll.delete_card(card._ptr)

As delete_card() is just an alias for free() there's no need to create a function for it. Instead use the free <stdlib-free> function directly:

from cslug.stdlib import free

while True:
    card = slug.dll.Card.from_address(slug.dll.make_card_ptr_safer(1, 2))
    # Do something with the card.
    # Then delete it safely:
    free(card._ptr)

Warning for Structs Containing Pointers

Be vary careful for if you're struct contains pointers. This harmless looking structure, containing a string pointer, is deceptively dangerous:

../demos/structs/person.c

../demos/structs/person.py

Creating a !Person in Python is Ok:

>>> slug.dll.Person("Bill")
Person(name='Bill')

But creating a !Person in a C function isn't.

>>> slug.dll.make_person("Bill") Person(name='璀L$')

What's happened here is that the string buffer we passed a pointer to is deleted as soon as make_person() exits, leaving the name attribute a dangling pointer. If you're calling make_person() from Python like above you can get around this by maintaining a reference to the buffer in Python:

>>> import ctypes
>>> name = ctypes.create_unicode_buffer("Bill")
>>> slug.dll.make_person(name)
Person(name='Bill')

But bear in mind that this reference is mutable:

>>> person = slug.dll.make_person(name)
>>> person
Person(name='Bill')
>>> name.value = "Bob"  # Must be no longer than 'Bill' (<= 4 characters)
>>> person
Person(name='Bob')

And easily deletable:

>>> del name
>>> person
Person(name='㟐]$')

This is more subtle when you remember that local variables are deleted at the end of functions in Python:

def make_person(name):
    name = ctypes.create_unicode_buffer(name)
    return slug.dll.make_person(name)  # `name` is automatically deleted here.