You are assuming that c directly follows b which is not the case. The compiler will pad a few bytes, 2 on x86, in that structure to align c.

The proper way is to declare one-to-one mapping of your structure in ctypes:

from ctypes import *

class object_t(Structure):
    _fields_ = [
        ('a', c_uint8),
        ('b', c_uint8),
        ('c', c_uint32),
        ('d', POINTER(c_uint8)),
    ]

No you can obtain the value of any member thought this type.

C example library:

#include <stdint.h>
#include <stdlib.h>

struct object_t {
  uint8_t a;
  uint8_t b;
  uint32_t c;
  uint8_t* d;
};

static struct object_t object = {'a', 'b', 12345, NULL};

struct object_t * func1(void)
{
  return &object;
}

void func2(void(*callback)(struct object_t *))
{
  callback(&object);
}

Using it from Python:

from ctypes import *

class object_t(Structure):
    _fields_ = [
        ('a', c_uint8),
        ('b', c_uint8),
        ('c', c_uint32),
        ('d', POINTER(c_uint8)),
    ]

callback_t = CFUNCTYPE(None, POINTER(object_t))

lib = CDLL('./file.dll')

func1 = lib.func1
func1.argtypes = None
func1.restype  = POINTER(object_t)

func2 = lib.func2
func2.argtypes = [callback_t]
func2.restype   = None

ret = func1()

a = ret.contents.a
b = ret.contents.b
c = ret.contents.c
d = ret.contents.d

def mycallback(obj):
    a = obj.contents.a
    b = obj.contents.b
    c = obj.contents.c
    d = obj.contents.d

func2(callback_t(mycallback))
Answer from user1129665 on Stack Overflow
🌐
Python
docs.python.org › 3 › library › ctypes.html
ctypes — A foreign function library for Python
This means, if you have POINTER(c_int) in the argtypes list of a function or as the type of a member field in a structure definition, only instances of exactly the same type are accepted.
🌐
Samuelstevens
samuelstevens.me › writing › optimizing-python-code-with-ctypes
Optimizing Python Code with ctypes
The only field is _fields_, which is a list of tuples. Each tuple is (<variable-name>, <ctypes.TYPE>). ctypes has types like c_char (char), and c_char_p (*char). ctypes also includes POINTER() which creates a pointer type from any type passed to it. If you have a recursive definition like in ...
🌐
SageMath
doc.sagemath.org › html › en › thematic_tutorials › numerical_sage › ctypes_examples.html
More complicated ctypes example - Thematic Tutorials
Note that in case you need to refer to a struct before it is completely defined (as in the linked list) you can first declare it with “Pass”, and then specify the field contents as above. Also note the POINTER operator which creates a pointer out of any ctypes type.
🌐
CodersLegacy
coderslegacy.com › home › structs with python ctypes
Structs with Python Ctypes - CodersLegacy
October 30, 2022 - If you do not, then these will remain as local variables, and when you return these pointers to Python and the function ends, then the local memory will be destroyed, and your Python program will receive dangling pointers. We don’t want that, so we declare our memory dynamically which means that it exists until we explicitly free it. And we need to do this for both the Struct, and the string inside it. (Because both objects are not primary datatypes, and cannot be passed by value) import ctypes import os class Student(ctypes.Structure): _fields_ = [("name", ctypes.c_char_p)] path = os.getcwd() clibrary = ctypes.CDLL(os.path.join(path, 'clibrary.so'))
🌐
Stanford
sporadic.stanford.edu › thematic_tutorials › numerical_sage › ctypes_examples.html
More complicated ctypes example — Thematic Tutorials v9.3.beta9
Note that in case you need to refer to a struct before it is completely defined (as in the linked list) you can first declare it with “Pass”, and then specify the field contents as above. Also note the POINTER operator which creates a pointer out of any ctypes type.
🌐
GeeksforGeeks
geeksforgeeks.org › python › using-pointers-in-python-using-ctypes
Using Pointers in Python using ctypes - GeeksforGeeks
July 23, 2025 - Since in Python the concept of pointers is not properly available or used we can't properly create a Void Pointer, as the POINTER function requires one argument i.e the data type. But we can create a Void pointer of a certain data type and then don't point it to any memory address. We are just typecasting it like C/C++ early. We can also create some void pointers of a specific type that doesn't hold any address while creating. ... import ctypes as ct # Creating a variable ptr2 which will act as a void pointer # We will not point it to any other memory location ptr2 = ct.POINTER(ct.c_int) # Trying to print a void pointer print(ptr2)
🌐
tentacles666
tentacles666.wordpress.com › 2012 › 01 › 21 › python-ctypes-dereferencing-a-pointer-to-a-c
Python ctypes: dereferencing a pointer to a C structure | tentacles666
March 19, 2013 - First, you need to define a Python class that represents struct foo: class Foo (ctypes.Structure): _fields_ = [ ("bar", c_int) ] Next, you need to tell ctypes that newfoo() returns a pointer to such a thing:
Find elsewhere
🌐
Hyunyoung2
hyunyoung2.github.io › 2018 › 05 › 11 › How_To_Use_Ctypes_In_Python2
How to use ctypes more deeper to connect function written in c from python
So don’t confuse that c is basically call-by-value and python is basically call-by-reference. Let’s see how to call two ways, pass by value and pass by reference ... be careful of ctypes.POINTER(your_variable), that is because in cross-languae interfaces, memory access and memory management ...
🌐
University of Chicago
people.cs.uchicago.edu › ~amr › 122 › labs › cstructs.html
CMSC 12200 - Structs in C and bridging with Python using CTypes - Februrary 2, 2011
We will acclimate ourselves with Structs and pointers to structs in C. We'll directly compare this to object oriented programming in Java and Python. We'll use CTypes to make fast functions written in C accessible within Python.
🌐
Bytes
bytes.com › home › forum › topic › python
ctypes: how to make a structure pointer to point to a buffer - Post.Byes
November 2, 2015 - > class STUDENT(Structu re): _fields_ = [('name', c_int), ('id', c_int), ('addition', c_ubyte)] > buffer = c_byte * 1024 student_p = cast(buffer, POINTER(STUDENT )) > The parameter of the POINTER must be ctypes type. How could I attach the buffer pointer to the structure STUDENT ?
Top answer
1 of 1
4

I think what you may be missing is knowing exactly where you want the struct memory to be allocated. The c code below provides a function that allocates memory for the struct and returns a pointer to it (new_struct()).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int a;
    int b;
} my_struct;

my_struct *new_struct()
{
    my_struct *struct_instance = (my_struct *)malloc(sizeof(my_struct));
    memset(struct_instance, 0, sizeof(my_struct));
    return struct_instance;
}

int modify_struct(my_struct *ms) {
    ms->a = 1;
    ms->b = 2;
    return 0;
}

void print_struct_c(my_struct *ms) {
    printf("my_struct {\n"
           "    a = %d\n"
           "    b = %d\n"
           "}\n", ms->a, ms->b);
}

From Python, to get the pointer, call the C function that does the allocation, then you can pass that to the other C functions that take it as a parameter.

import ctypes

lib_file_path = <<< path to lib file >>>

# Very simple example of how to declare a ctypes structure to twin the
# C library's declaration. This doesn't need to be declared if the Python
# code isn't going to need access to the struct's data members.
class MyStruct(ctypes.Structure):
    _fields_ = [('a', ctypes.c_int),
                ('b', ctypes.c_int)]

def print_struct(s):
    # Print struct that was allocated via Python ctypes.
    print("my_struct.a = %d, my_struct.b = %d" % (s.a, s.b))

def print_struct_ptr(sptr):
    # Print pointer to struct. Note the data members of the pointer are 
    # accessed via 'contents'.
    print("my_struct_ptr.contents.a = %d, my_struct_ptr.contents.b = %d" 
          % (sptr.contents.a, sptr.contents.b))


my_c_lib = ctypes.cdll.LoadLibrary(lib_file_path)

# If you don't need to access the struct's data members from Python, then 
# it's not necessary to declare MyStruct above. Also, in that case,
# 'restype' and 'argtypes' (below) can be set to ctypes.c_void_p instead.
my_c_lib.new_struct.restype     =  ctypes.POINTER(MyStruct)
my_c_lib.modify_struct.argtypes = [ctypes.POINTER(MyStruct)]

# Call C function to create struct instance.
my_struct_c_ptr = my_c_lib.new_struct()
print_struct_ptr(my_struct_c_ptr)

my_c_lib.modify_struct(my_struct_c_ptr)
print_struct_ptr(my_struct_c_ptr)

# Allocating struct instance from Python, then passing to C function.
my_struct_py = MyStruct(0, 0)
print_struct(my_struct_py)

my_c_lib.modify_struct(ctypes.byref(my_struct_py))
print_struct(my_struct_py)

# Data members of Python allocated struct can be acessed directly.
my_struct_py.a = 555

my_c_lib.print_struct_c(ctypes.byref(my_struct_py)) # Note use of 'byref()'
                                                    # to invoke c function.

The code above has been updated to include an example of how to allocate a struct instance via Python, and how to access the data members of either the C allocated or Python allocated struct (note differences in the print functions).

🌐
Terryoy
terryoy.github.io › 2014 › 03 › using-ctypes-to-wrap-c-library.html
Use Ctypes to Wrap C Libraries in Python - Hello, World!
We could use _ctypes.POINTER() to define the pointer type of ctype types(fundamental types and structures).
🌐
Notebook Community
notebook.community › xR86 › ml-stuff › labs-python › PythonCourseLab-12-13
C integration - part 1
The default value #will be NULL so they will have to be instantiated). import ctypes ''' struct Numbers { long *n; long count; } ''' class Numbers(ctypes.Structure): _fields_ = [("n", ctypes.POINTER(ctypes.c_long)), ("count",ctypes.c_long)] p = Numbers() p.n = (ctypes.c_long * 5)(1,2,3,4,5) p.count = 5 s = [] for i in range(0,p.count): s += [p.n[i]] print(s)
🌐
Python
docs.python.org › 2 › library › ctypes.html
15.17. ctypes — A foreign function library for Python — Python 2.7.18 documentation
This means, if you have POINTER(c_int) in the argtypes list of a function or as the type of a member field in a structure definition, only instances of exactly the same type are accepted. There are some exceptions to this rule, where ctypes accepts other objects.
🌐
Medium
medium.com › @datasciencefilmmaker › my-god-its-full-of-stars-2-d75d01d2cb94
My God, It’s Full of Stars (2/7) — Accessing C Structures in Python Using Ctypes | by Data Science Filmmaker | Medium
January 18, 2024 - It makes extensive use of structures and pointers to make accessing memory more efficient than the equivalent object-oriented version, and way more efficient than the Python equivalent would be. I wanted to take advantage of the speed of C while still using mostly Python syntax. Enter the “ctypes” library.
Top answer
1 of 2
8

The problem is that the higher level "POINTER" from ctypes is, in Python, a different object than "a generic pointer" (ctypes.CArgObject by ctypes.byref)which is returned or a single number representing a memory address (which is what is returned by ctype's adrresof) - you can either annotate your function to receive a `ctypes.c_voidp and call it with _lib.mymethod(ctypes.addressof(a)) instead -

Or if you want to work on the stronged-typed side to avoid errors that would crash Python (a type error raises a Python exception instead - a wrong parameter passed to a C unction would cause a segmentation fault on the Python interpreter itself), you have to create a variable to hold the new "type" which is a POINTER to your structure - and then create an instance of this type with the address of your structure:

mystruct_pointer = ctypes.POINTER(mystruct)
_lib.mymethod.argtypes = (mystruct_pointer,)
_lib.mymethod.restype = ctypes.c_int

s = mystruct()

_lib.mymethod(mystruct_pointer.from_address(ctypes.addressof(s)))
2 of 2
6

(I know that this is an old question, but I think the accepted answer is an unnecessary workaround, so I want to leave this here for posterity.)

Actually ctypes should explicitly support using byref() to pass a pointer like that:

ctypes exports the byref() function which is used to pass parameters by reference. The same effect can be achieved with the pointer() function, although pointer() does a lot more work since it constructs a real pointer object, so it is faster to use byref() if you don’t need the pointer object in Python itself.

The likely cause of this is that you have defined your struct in more than one place (e.g. in different modules) - if the argtypes assignment sees one definition and the function call sees the other, this confusing error arises. In other words, ctypes tries to match two mystruct types that are (probably) identical in contents, and have the exact same name, but they are not the same type. As long as the base struct type is a single type object, it doesn't matter if you construct a pointer to it using pointer(), byref() or POINTER()() - ctypes will detect that the underlying (pointed-to) type is the same.

To verify if this is the case, try assert(_lib.mymethod.argtypes[0]._type_ == type(s)) right before calling the external function.

🌐
Runebook.dev
runebook.dev › en › docs › python › library › ctypes › ctypes.Structure
Mastering ctypes.Structure in Python: Common Pitfalls and Alternatives
Solution Use ctypes.POINTER(SomeType). For recursive structures, you need to first define the class, then set the _fields_ after the class definition.
Top answer
1 of 3
38

Here's a working example. It looks like you are passing the wrong type to the function and the structure was declared incorrectly for the Data member. Note below how to declare the type of a fixed-size array when declaring the Python structure.

test.c ("cl /W4 /LD .c" on Windows):

#include <stdio.h>

#ifdef _WIN32
#   define SMBUS_API __declspec(dllexport)
#else
#   define SMBUS_API
#endif

#define SMB_MAX_DATA_SIZE 5

typedef void* SMBUS_HANDLE;

typedef struct _SMB_REQUEST {
    unsigned char Address;
    unsigned char Command;
    unsigned char BlockLength;
    unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;

SMBUS_API int SmBusReadByte(SMBUS_HANDLE handle, SMB_REQUEST *request) {
    for(unsigned char i = 0; i < request->BlockLength; ++i)
        request->Data[i] = i;
    return request->BlockLength;
}

SMBUS_API SMBUS_HANDLE OpenSmbus(void) {
    return (void*)0x12345678;
}

test.py:

import ctypes as ct

SMB_MAX_DATA_SIZE = 5
ARRAY = ct.c_ubyte * SMB_MAX_DATA_SIZE

class SMB_REQUEST(ct.Structure):
    _fields_ = (('Address', ct.c_ubyte),
                ('Command', ct.c_ubyte),
                ('BlockLength', ct.c_ubyte),
                ('Data', ARRAY))

dll = ct.CDLL('./test')
smbus_read_byte = dll.SmBusReadByte
smbus_read_byte.argtypes = ct.c_void_p, ct.POINTER(SMB_REQUEST)
smbus_read_byte.restype = ct.c_int
open_smbus = dll.OpenSmbus
open_smbus.argtypes = ()
open_smbus.restype = ct.c_void_p

handle = open_smbus()
print(f'{handle = :08X}h')

smb_request = SMB_REQUEST(1, 2, 5)

print(f'returned = {smbus_read_byte(handle, smb_request)}')
print(f'Address = {smb_request.Address}')
print(f'Command = {smb_request.Command}')
print(f'BlockLength = {smb_request.BlockLength}')
for i, b in enumerate(smb_request.Data):
    print(f'Data[{i}] = {b:02X}h')

Output:

handle = 12345678h
returned = 5
Address = 1
Command = 2
BlockLength = 5
Data[0] = 00h
Data[1] = 01h
Data[2] = 02h
Data[3] = 03h
Data[4] = 04h
2 of 3
7

You're almost there. You should use c_char * SMB_MAX_DATA_SIZE as the type for the definition of Data. This works for me on Mac OS X:

Shared library:

$ cat test.c
#include <stdio.h>

#define SMB_MAX_DATA_SIZE 16

typedef struct _SMB_REQUEST
{
  unsigned char Address;
  unsigned char Command;
  unsigned char BlockLength;
  unsigned char Data[SMB_MAX_DATA_SIZE];
} SMB_REQUEST;

int SmBusReadByte(void *handle, SMB_REQUEST *request)
{
  printf("SmBusReadByte: handle=%p request=[%d %d %d %s]\n", handle, 
      request->Address, request->Command, request->BlockLength, request->Data);
  return 13;
}

$ gcc test.c -fPIC -shared -o libtest.dylib

Python driver:

$ cat test.py
import ctypes

SMB_MAX_DATA_SIZE = 16

class SMB_REQUEST(ctypes.Structure):
    _fields_ = [("Address", ctypes.c_ubyte),
                ("Command", ctypes.c_ubyte),
                ("BlockLength", ctypes.c_ubyte),
                ("Data", ctypes.c_char * SMB_MAX_DATA_SIZE)]

libtest = ctypes.cdll.LoadLibrary('libtest.dylib')

req = SMB_REQUEST(1, 2, 3, 'test')

result = libtest.SmBusReadByte(ctypes.c_voidp(0x12345678), ctypes.byref(req))

print 'result: %d' % result

$ python test.py
SmBusReadByte: handle=0x12345678 request=[1 2 3 test]
result: 13

UPDATE

You're having problems because you need to set the result type of open_smbus to void*. By default, ctypes assumes that functions return ints. You need to say this:

open_smbus.restype = ctypes.c_void_p

You were getting an error because you were using c_void_p() (note the extra parentheses). There's an important distinction between c_void_p and c_void_p(). The former is a type, and the latter is an instance of a type. c_void_p represents the C type void*, whereas c_void_p() represents an actual pointer instance (with a default value of 0).