0

I have the following C code which I would use from a python script.

This is just an excerpt of an auto-generated huge library which I'm not able to change, unfortunately. Here I just wanted to print the structure elements to a console to demonstrate what is going wrong.


// CFunc.h
#include <stdio.h>

typedef struct
{
    int npar;
    struct
    {
        int id;
        int value;
    } params[10];
} Data_t;

void Cfunc( const Data_t * d);


// CFunc.c

#include "CFunc.h"

void Cfunc( const Data_t * d)
{
    int inpar = 0;
    int maxnpar = 0;

    printf("%d:\n", d->npar);
    maxnpar = d->npar;

    inpar=0;
    while (maxnpar > inpar)
    {
        printf("  %d: %08x %08x\n", inpar, d->params[inpar].id, *(int*)&d->params[inpar].value);
        inpar++;
    }
}

It is compiled and linked to a share library with:

gcc -fPIC -c CFunc.c -o CFunc.o
gcc -shared -lrt -Wl,-soname,libCFunc.so.1 -o libCFunc.so CFunc.o

So I did the following implementation using ctypes:

from ctypes import *

lib = CDLL('./libCFunc.so')


class Data_2(Structure):
    pass
class Data_t(Structure):
    def __init__(self, list):
        self.npar = len(list)
        self.params = (Data_2 * self.npar)(*list)

Data_2._fields_ = [
    ('id', c_int),
    ('value', c_int),
]
Data_t._fields_ = [
    ('npar', c_int),
    ('params', POINTER(Data_2)),
]

def pyFunc(d):
    lib.Cfunc.argtypes = (POINTER(Data_t),)

    lib.Cfunc(byref(d))

    return

So I'm initializing the structure out of a list of given tuples, in this case just 2 and calling the C-function to see its output.

paramlist = ( 
    ( 0x050000000, 0x00000000 ),  
    ( 0x050000001, 0x447a0000 ) )
temp = Data_t(paramlist)

pyFunc(temp)

Unfortunately the output is not as expected:

2:
0: 00000000 79948ef0
1: 00007fe5 00000000

Any thoughts what I am missing ?

2
  • regarding: typedef struct { int npar; struct { int id; int value; } params[10]; } Data_t; 1) always use a 'tag' name on every struct as that is what most debuggers use to be able to access the individual fields in the struct. 2) for flexibility, separate the definition if the struct from the typedef for the struct Commented Feb 26, 2019 at 19:27
  • The code I posted is just an example of a more complex library and your concerns are covered... Thank you for that remark. Commented Feb 27, 2019 at 15:18

1 Answer 1

1

[Python 3]: ctypes - A foreign function library for Python.

  • The structs from C and Python don't match
    • params is an array not a pointer
  • Because of the above inconsistency, you overcomplicated things in Python:
    • You don't have incomplete types, so your structures can be statically defined

I restructured your code a bit.

dll.h:

#pragma once


typedef struct Data_ {
    int npar;
    struct
    {
        int id;
        int value;
    } params[10];
} Data;


void test(const Data *d);

dll.c:

#include "dll.h"
#include <stdio.h>


void test(const Data *d) {
    int inpar = 0;
    int maxnpar = 0;

    printf("%d:\n", d->npar);
    maxnpar = d->npar;

    inpar = 0;
    while (inpar < maxnpar)
    {
        printf("  %d: %08x %08x\n", inpar, d->params[inpar].id, *(int*)&d->params[inpar].value);
        inpar++;
    }
}

code.py:

#!/usr/bin/env python3

import sys
import ctypes


DLL = "./libdll.so"


class DataInner(ctypes.Structure):
    _fields_ = [
        ("id", ctypes.c_int),
        ("value", ctypes.c_int),
    ]


DataInnerArr10 = DataInner * 10


class Data(ctypes.Structure):
    _fields_ = [
        ("npar", ctypes.c_int),
        ("params", DataInnerArr10),
    ]

    def __init__(self, data):
        self.npar = len(data)
        self.params = DataInnerArr10(*data)


def main():
    dll_dll = ctypes.CDLL(DLL)
    test_func = dll_dll.test
    test_func.argtypes = [ctypes.POINTER(Data)]

    param_list = (
        (0x050000000, 0x00000000),
        (0x050000001, 0x447a0000),
    )

    d = Data(param_list)

    test_func(ctypes.byref(d))
    print("Done.")


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Output:

[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> ls
code.py  dll.c  dll.h
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> gcc -shared -fPIC -o libdll.so dll.c
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> ls
code.py  dll.c  dll.h  libdll.so
[cfati@cfati-5510-0:/cygdrive/e/Work/Dev/StackOverflow/q054888242]> python3 code.py
Python 3.6.4 (default, Jan  7 2018, 15:53:53)
[GCC 6.4.0] on cygwin

2:
  0: 50000000 00000000
  1: 50000001 447a0000
Done.
Sign up to request clarification or add additional context in comments.

2 Comments

Many thanks, that works! I was persuaded, that array initialisation is done similar to self.params = (Data_2 * self.npar)(*list) ... at least that was my understanding cross-reading on SE. I also completely missed that self.params needs to be of fixed element size and cannot be initialised dynamically using ...* self.npar. Of course, I had also self.params = Data_2 * self.npar(*list) in my mind which however is not legal syntax. Would you mind elaborate on the differences to DataInnerArr10 = DataInner * 10 + self.params = DataInnerArr10(*data) ? That's still not clear to me :-/
Youi're welvome! DataInnerArr10 (as its name suggests) is a type, an array of 10 DataInne structures. When doubt always use print (print(type(DataInnerArr10), dir(DataInnerArr10))). The second is the initializer, it's a tuple unpacking: basically it passes 2 values tuples from the main matrix (2D) to DataInner constructor in order to initialize its 2 members.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.