Ctypes is a special library in Python used to integrate C/C++ functions and other features into your Python code. In simpler words, Ctypes allows you to execute C/C++ functions from Python code. Ctypes also provides with other features such Pointers and other C/C++ data types that you can use in your Python code.
In this tutorial we will focus on the memory management and pointer related features of Ctypes. Let’s get started.
Memory Management with Pointers in Python
Everyone knows that Python is a language without pointers. Thus we also do not have to worry about things like memory management (allocation and deallocation of memory).
C/C++ however, do have pointers. In order to make Python compatible with C/C++ functions that require or return pointers the Ctypes library introduces the Pointer data type in Python. We can also use them normally in Python like we would in C/C++.
The easiest way to create a pointer in Python using ctypes is the pointer()
function. Take a look at the following code:
variable = ctypes.c_int(10)
ptr = ctypes.pointer(variable)
print(ptr.contents.value)
First we create a variable using ctypes, then we pass this variable into the pointer()
function. The pointer()
function will then return a pointer object. By using the .contents
attribute, we can dereference a pointer and gain access to the original object it was pointing to.
To obtain the value of any ctypes datatype however, we need to use the .value
attribute. So ultimately to acquire the value to which the pointer points to, we need to do ptr.contents.value
.
Note: You cannot pass regular Python datatypes into the pointer()
function.
If you want to make a pointer change the object it is pointing to, then update the .contents
attribute with the new object.
num1 = ctypes.c_int(100)
num2 = ctypes.c_int(200)
ptr = ctypes.pointer(num1)
print(ptr.contents.value)
ptr.contents = num2
print(ptr.contents.value)
100
200
You can also create a pointer which doesn’t point to any object (upon it’s initialization). But you will have to specify what the “data-type” of pointer it is. e.g: A integer pointer or character pointer etc.
ptr1 = ctypes.POINTER(ctypes.c_int)
ptr2 = ctypes.POINTER(ctypes.c_char_p)
Sending and Receiving Pointers from C/C++
Here are the two C++ functions that we will be practicing on in this tutorial.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char* alloc_memory(void) {
char* str = strdup("Hello World");
printf("Memory allocated...\n");
return str;
}
void free_memory(char* ptr) {
printf("Freeing memory...\n");
free(ptr);
}
The first function alloc_memory
allocates memory dynamically to a string using the strdup()
function. strdup()
has the same purpose malloc()
(to allocate dynamic memory), but it is only for strings. Its syntax is easier to use, hence we are using it instead. Once this memory is created, it returns a pointer which we then return from the function.
The second function is free_memory()
which receives a pointer as an argument and then deallocates it.
Now first we need to compile our C-file into a shared library using the following command. The first parameter is the name of the shared library (.so
) and the second is the name of the .c
file where we wrote our code.
gcc -fPIC -shared -o clibrary.so clibrary.c
Refer to our ctypes guide if you aren’t familiar with some of the basic concepts here.
Next we will open up our Python file and begin writing python code.
import ctypes
clibrary = ctypes.CDLL('clibrary.so')
The first thing we will do is to import the ctypes module. After that we need to load our c-library into our Python code using the ctypes.CDLL()
function. All you need to do is pass a filepath/filename to it, and it will return a module object, just like a regular import.
# Defining alloc function and it's return type
alloc_func = clibrary.alloc_memory
alloc_func.restype = ctypes.POINTER(ctypes.c_char)
# Defining free function and it's return type
free_func = clibrary.free_memory
free_func.argtypes = [ctypes.POINTER(ctypes.c_char)]
Here we create the function definitions for our two functions from the C file. The .restype
attribute defines the return type, where as .argtypes
defines the arguments. (argtypes
must be a list, as there can be several arguments)
# Using the function to return a string
cstring_pointer = alloc_func()
str = ctypes.c_char_p.from_buffer(cstring_pointer)
print(str.value)
# Freeing memory
free_func(cstring_pointer)
Finally, we can use the functions from the C-library now. First we call the allocation function, which returns a pointer. We can use .contents.value
to deference it, but instead we used a different approach to create a new string object from the pointer. We can access this string’s value by using .value
attribute.
At the end, make sure to call the free memory function, else there will be a memory leak.
Here is the complete compiled code.
import ctypes
clibrary = ctypes.CDLL('clibrary.so')
# Defining alloc function and it's return type
alloc_func = clibrary.alloc_memory
alloc_func.restype = ctypes.POINTER(ctypes.c_char)
# Defining free function and it's return type
free_func = clibrary.free_memory
free_func.argtypes = [ctypes.POINTER(ctypes.c_char)]
# Using the function to return a string
cstring_pointer = alloc_func()
str = ctypes.c_char_p.from_buffer(cstring_pointer)
print(str.value)
# Freeing memory
free_func(cstring_pointer)
The output:
Memory allocated...
b'Hello World'
Freeing memory...
This marks the end of the Pointers with Ctypes and Python Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.