Nick-M
Nick-M

Reputation: 25

Invalid input type for c_char ctypes in python

In my C header file I have:

long TEST_API test( 
            _IN____ const char arg1,
            _INOUT_ char arg2[512]
    );

I have imported ctypes in my python code and I am passing "kcOpCode" and "szXMLAdditionalParameters" to eftUtility function as following:

utilityXml_bytes = bytearray(b'<xml><test>20</test></xml>')
utilityXMLparams = (ctypes.c_byte*512)(*utilityXml_bytes)

def test():
    eftUtilityRes = lib.test("s", utilityXMLparams)
    if (eftUtilityRes == 10):
        "success"
    elif(eftUtilityRes == -10):
        "type 2 error"
    else:
        "type 3 error"

But I keep getting invalid input type error for "arg1". what am I doing wrong here? could it be encoding problem? am I actually passing s to my function?

Upvotes: 0

Views: 1365

Answers (2)

Michele d&#39;Amico
Michele d&#39;Amico

Reputation: 23741

The signature of your function is (const char arg1 ... ) so you must pass a char as first argument and not a character. That means a byte and not a single char string. To test it I wrote my lib

foo.c

#include <stdio.h>
void foo(const char v)
{
    printf("Hello, I'm a shared library %c\n",v);
}

And compile it by

gcc -c -Wall -Werror -fpic foo.c
gcc -shared -o libfoo.so foo.o

And now call it by python console:

My python script is:

>>> from ctypes import *
>>> lib = cdll.LoadLibrary("./libfoo.so")
>>> a=lib.foo(ord("s"))
Hello, I'm a shared library s
>>> a=lib.foo("s")
Hello, I'm a shared library D
>>> a=lib.foo(b"s")
Hello, I'm a shared library D

It is clear that what you need is ord("s").


One more thing: it is just a guess but maybe the error that you read came from the library that read an incorrect char as first argument.


Follow a lot of edits where we can see how many wrong assumptions or silly errors we can do if we don't use a correct cast by use ctypes to call a C library function. Now what I wrote above it was useful just to understand where the issue was but the real production code that should work either in Python 2 and Python 3 should declare the correct function interface and use b"s" as argument:

>>> lib.foo.argtypes = (c_char,)
>>> lib.foo.restype = None
>>> lib.foo(b"s") 
Hello, I'm a shared library s 

Just for the record:

[EDIT]

As @eryksun has correctly pointed me in the comments it is very unsafe call C function without use a correct cast: it is like in C don't use a prototype and everything become int. So if you don't set function argtype is better use explicit cast like:

>>> a=lib.foo(c_char(ord("s"))) #Work Just on Python 3
Hello, I'm a shared library s
>>> a=lib.foo(c_char("s")) # Just Python 2.7
Hello, I'm a shared library s
>>> a=lib.foo(c_char(b"s")) # Python 2 and 3
Hello, I'm a shared library s

[EDIT]

Follow from your comment: you can also hardcode ord("s") by use either 105 or hex value 0x73

>>> a=lib.foo(105)
Hello, I'm a shared library s
>>> a=lib.foo(0x73)
Hello, I'm a shared library s

[EDIT]

As @Dunes pointed out there is a way to tell to python how do that conversion implicitly by lib.foo.argtypes = (c_char,). But how you build your char by b"s" or simply "s" doesn't mater (on python 2.7).

>>> lib.foo.argtypes = (c_char,)
>>> a=lib.foo(b"s") 
Hello, I'm a shared library s
>>> a=lib.foo("s")  #Python 2.7
Hello, I'm a shared library s

In Python 3 use b"s" if you specify argtypes is mandatory:

>>> lib.foo.argtypes = (c_char,)
>>> a=lib.foo(b"s") 
Hello, I'm a shared library s
>>> a=lib.foo("s")  #Python 3.4
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type

Upvotes: 1

Dunes
Dunes

Reputation: 40778

It's because you're not stating that the first arg is a byte string -- b's' vs 's'. eg.

eftUtilityRes = lib.eftUtility(b"s", utilityXMLparams)

You also need to tell ctypes what arguments the function takes. Sometimes there is ambiguity to how a python object should be converted. In your case, is a single character string meant to be interpreted as char or char* (both of which would be valid interpretations).

lib.eftUtility.argtypes = (ctypes.c_char, ctypes.c_char_p)

In addition your Structure is unused and un-needed, and your way of creating a mutable byte array is long-winded. The following is all that is needed.

xml_data = b'<xml><DisplayWidthInCharacters>20</DisplayWidthInCharacters><JournalKeepDurationInDays>30</JournalKeepDurationInDays></xml>'
utilityXml = ctypes.create_string_buffer(xml_data, 512) 

def eftUtilityPepper():
    eftUtilityRes = lib.eftUtility(b"s", utilityXml)
    if (eftUtilityRes == 10):
        "success"
    elif(eftUtilityRes == -10):
        "type 2 error"
    else:
        "type 3 error"

Upvotes: 1

Related Questions