1

I have a shared C library with a struct I would like to use in my python code

struct my_struct {
  char name[64];
};

so in python I recreate it with

class MyStruct(ctypes.Structure):
  _fields_ = [
    ("name", ctypes.c_char*64)
  ]

when I check the type of MyStruct.name i get 'str', whereas I expected 'c_char_Array_64'.

s=MyStruct()
print type(s.name) # <type 'str'>

So when I set the 'name' and try to use it, C sees it as blank.

s.name="Martin"
lib=ctypes.cdll.LoadLibrary('./mylib.so')
lib.my_func(s) # prints ''

where lib is the shared C library loaded with ctypes and my_func simply prints struct->name

void my_func(struct my_struct *s){
  printf("Hello %s\n", s->name);
}

I would like to know why ctypes.Structure converts the char-array to a string and how to use it in the case specified above.

Thank you

Update & Solution

Tnanks to @CristiFati for the help on debugging this problem. I have marked his answer as correct as it is in fact the answer to the question posted. In my case the problem was that the Structs were NOT of equal lengths in the Python and C program. So to whoever stumbles upon this question in the future, be very meticulous in checking that your Structs are in fact defined equally.

ege
  • 774
  • 5
  • 19
  • Can you show how my_func is defined in C and wrapped in ctypes/python? – deets Jan 08 '19 at 10:23
  • It is already shown above – ege Jan 08 '19 at 10:28
  • You need to define *argtypes* (and *restype*) for your function in *Python* (`lib.my_func.argtypes = [ctypes.POINTER(MyStruct)]`, and call it: `lib.my_func(ctypes.pointer(s))`). Most of the *ctypes* failures are due to this reason. Check https://stackoverflow.com/questions/53182796/python-ctypes-issue-on-different-oses/53185316#53185316 (and tons of other questions) for more details. – CristiFati Jan 08 '19 at 10:31
  • Thank you @CristiFati, I will try this – ege Jan 08 '19 at 10:34
  • Sorry @CristiFati, s->name is still blank. Have any additional approaches I can try? – ege Jan 08 '19 at 10:46

1 Answers1

4

You're doing something wrong, but without looking at the full code I can't say what. So I prepared a small example that works.
I'm also posting [Python 3]: ctypes - A foreign function library for Python as a reference.

dll.c:

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

#if defined(_WIN32)
#  define DLL_EXPORT __declspec(dllexport)
#else
#  define DLL_EXPORT
#endif


typedef struct Struct0_ {
    char name[64];
} Struct0;


DLL_EXPORT void test(Struct0 *ps0){
    printf("Hello %s\n", ps0->name);
}

code.py:

#!/usr/bin/env python3

import sys
import ctypes


DLL = "./dll.dll"

CharArr64 = ctypes.c_char * 64

class Struct0(ctypes.Structure):
    _fields_ = [
        ("name", CharArr64),
    ]


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

    s0 = Struct0()
    s0.name = b"Martin"
    res = test_func(ctypes.pointer(s0))


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

Output:

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>"c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>dir /b
code.py
dll.c

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>cl /nologo /DDLL /MD dll.c  /link /NOLOGO /DLL /OUT:dll.dll
dll.c
   Creating library dll.lib and object dll.exp

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>dir /b
code.py
dll.c
dll.dll
dll.exp
dll.lib
dll.obj

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Hello Martin

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>rem Also run with Python 2.7 ... Not recommended.

(py_064_03.06.08_test0) e:\Work\Dev\StackOverflow\q054089371>"e:\Work\Dev\VEnvs\py_064_02.07.15_test0\Scripts\python.exe" code.py
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32

Hello Martin
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • My best guess is OP didn't set the argtypes for the function and so the struct is being passed by value and not by pointer. That is, the function is interpreting the string `'Martin\0\0'` as pointer to `MyStruct`. eg. compare `libc.printf(b'hello %sn', s)` and `libc.printf(b'hello %s\n', ctypes.byref(s))`. The former prints garbage, whilst the latter works as it passes a pointer to `s` as the second argument (which just so happens to be the same as the pointer to `s->name`). – Dunes Jan 08 '19 at 11:59
  • @Dunes: I specified in one comment how the function should be called, and according to the next comment, that was done. Then I really wanted to see if I could reproduce it, and I posted the answer. – CristiFati Jan 08 '19 at 12:04
  • @CristiFati I've tried your code and it works perfectly. Even tried porting it back to what my code looks like and it still works. I haven't posted 'the entire code' as it is a massive project I'm building on top of, and I thought what I posted would be enough to find the issue. But it is not. There must be somewhere else in the code that interferes with how the 'pass by reference works?' I appreciate the help – ege Jan 08 '19 at 12:09
  • https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value. In this scenario it has everything to do with how the function is called from *Python*. In the code there are 2 places: `ctypes.POINTER` and `ctypes.pointer`, those are the key, you probably missed one of them. Or it could be that you did the changes (in *C* sources), but forgot to rebuild. – CristiFati Jan 08 '19 at 12:25
  • SOLVED. @CristiFati. I found and issue and as you said "without seeing the entire code...". It turns out the structs were not equal lengths in python and C which created a mismatch in where the data were actually located. I will still accept your answer as it is in fact the answer to the question stated. Thank you for your help. – ege Jan 08 '19 at 12:35
  • So, one struct definition had some extra members (care should be taken so that the **2 definitions are always in sync**)? Or it was due to memory alignment (`pragma pack` - although for one member only struct that shouldn't matter)? – CristiFati Jan 08 '19 at 12:40
  • @CristiFati One member was an array with a length that was defined in more than one place with a #ifndef so depending on how much of the program I compiled, this value changed. – ege Jan 08 '19 at 12:47