classification
Title: syntactic sugar: type coercion on pointer assignment
Type: enhancement Stage: needs patch
Components: ctypes Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: PeterASilva, amaury.forgeotdarc, belopolsky, meador.inge, vladris
Priority: normal Keywords:

Created on 2009-02-04 01:20 by PeterASilva, last changed 2020-10-25 22:49 by iritkatriel.

Messages (5)
msg81123 - (view) Author: Peter A Silva (PeterASilva) Date: 2009-02-04 01:20
tough% cat toy.py

from ctypes import *

class foo(Structure):
  _fields_ = [ ( "bar", c_char_p ) ]

foofoo = foo()

babar = create_string_buffer(32)

foofoo.bar = babar

tough% python toy.py
Traceback (most recent call last):
  File "toy.py", line 11, in <module>
    foofoo.bar = babar
TypeError: incompatible types, c_char_Array_32 instance instead of
c_char_p instance
tough%


would be nice if ctypes automagically turned the assignment into

   foofoo.bar = cast(babar,c_char_p)

There doesn't seem to be any other possible intent of this syntax.
Color me stupid, but being new to ctypes, it took me approximately 30
hours to discover a method of such assignment that worked (babar.raw is
not rejected, but not correct, debugging that was fun.  I actually fell
upon addressof(babar), which does also works, but is probably
undesirable.)  

It would be very friendly if ctypes would just do the right thing.
It doesn't seem like there is any other possible intent of such 
a syntax.
msg141747 - (view) Author: Vlad Riscutia (vladris) Date: 2011-08-07 21:10
The main reason for this issue is that internally ctypes doesn't consider c_char_p as a pointer type. This is a bit counter-intuitive, but c_char_p is treated as a simple type and has some other sugar built-in, like easier integration with Python strings. Alternative pointer type is POINTER(c_char), which allows assignment from array.

I would make this clear in the documentation. Even now, documentation says following about c_char_p:

Represents the C char * datatype when it points to a zero-terminated string. For a general character pointer that may also point to binary data, POINTER(c_char) must be used. The constructor accepts an integer address, or a string.

So there already is a hint that c_char_p has limited use for convenience. We should maybe make documentation more clear. If you want equivalent of C char* behavior, POINTER(c_char) is the way to go.
msg143452 - (view) Author: Meador Inge (meador.inge) * (Python committer) Date: 2011-09-03 05:04
This is busted for plain old assignment too:

Python 3.3.0a0 (default:6374b4ffe00c, Sep  2 2011, 23:50:39) 
[GCC 4.6.0 20110603 (Red Hat 4.6.0-10)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> buff = create_string_buffer(b'foo')
>>> p = c_char_p()
>>> p.value = addressof(buff)
>>> print(p.value)
b'foo'
>>> p.value = buff.value
>>> print(p.value)
b'foo'
>>> p.value = buff
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: string or integer address expected instead of c_char_Array_4 instance

I think having the conversion is an entirely reasonable request.  It is the equivalent of:

    char buff[128] = "foo";
    char *p = buff;

in C.  I imagine a lot of C programmers would expect this behavior.

Also, 'ctypes' already does this type of conversion for function parameters.  Consider a function exported from a shared library 'libfoo' called 'print_string':

void print_string (char *str)
{
  printf ("%s\n", str);
}

The following all work fine:

>>> libfoo = CDLL("./libfoo.so.1.0")
>>> buff = create_string_buffer("foo")
>>> libfoo.print_string(buff)
foo
>>> libfoo.print_string("foo")
foo
>>> libfoo.print_string(addressof(buff))
foo

I am working on a patch for this.  I will post it soon.
msg143657 - (view) Author: Vlad Riscutia (vladris) Date: 2011-09-07 00:12
I believe there is a deeper issue here in ctypes design. Basically we provide both c_char_p and POINTER(c_char) which should behave exactly the same since both are the equivalent of char* in C but internally they have different implementations.

c_char_p is considered a "simple type" and I believe supports some conversions to and from Python strings while POINTER(c_char) is considered a pointer type which supports assignment from array etc.

I think a better fix would be to deprecate p_char_p or make it an equivalent of POINTER(c_char), otherwise we will have to do work on c_char_p to make it more like POINTER(c_char) when issues like this get opened and probably also make POINTER(c_char) more like c_char_p. Why not just have POINTER(c_char) which works as expected? I don't have all the historical context on why this pseudo-simple type was provided but I saw a couple of issues where people expect it to behave like a C char* but it won't because it is implemented as a convenience type with limited support.
msg143659 - (view) Author: Meador Inge (meador.inge) * (Python committer) Date: 2011-09-07 01:01
Vlad, I agree that having both 'c_char_p' and 'POINTER(c_char)' will just be more work for two seemingly identical constructs.  I don't fully understand why both would be needed either (or the implications of removing one of them or making them synonyms).  I guess a little software archeology is in order.
History
Date User Action Args
2020-10-25 22:49:48iritkatrielsetversions: + Python 3.10, - Python 3.2
2011-09-07 01:01:28meador.ingesetmessages: + msg143659
2011-09-07 00:12:38vladrissetmessages: + msg143657
2011-09-03 05:04:03meador.ingesetnosy: + amaury.forgeotdarc, meador.inge, belopolsky, - theller
messages: + msg143452

assignee: theller ->
stage: test needed -> needs patch
2011-08-07 21:10:20vladrissetnosy: + vladris
messages: + msg141747
2010-07-31 11:41:36BreamoreBoysetstage: test needed
type: enhancement
versions: + Python 3.2, - Python 2.6, Python 3.0, Python 3.1, Python 2.7
2009-02-04 01:20:02PeterASilvacreate