After many tests, it can be concluded that

First:

ctypes.c_char.from_buffer(string)

wouldn't work because the from_buffer method accepts a param that is an array of bytes, so I would need to convert string into array of bytes first like such:

buf = bytearray(string)
ptr = ctypes.c_char.from_buffer(string)

now both returned results of:

ctypes.c_char_p(string)

and

ctypes.c_char.from_buffer(buf)

can be used in memory copying operation function calls like:

ctypes.memmove for linux 

and

RtlMemoryMove for windows 
Answer from 0x5929 on Stack Overflow
🌐
Python
docs.python.org › 3 › library › ctypes.html
ctypes — A foreign function library for Python
This method creates a ctypes instance, copying the buffer from the source object buffer which must be readable. The optional offset parameter specifies an offset into the source buffer in bytes; the default is zero.
Discussions

python - How are the different from_buffer() methods implemented for ctypes types? - Stack Overflow
Copyfrom _ctypes import _SimpleCData ... class c_long(_SimpleCData): _type_ = "l" And the _SimpleCData has the class method of from_buffer(). More on stackoverflow.com
🌐 stackoverflow.com
Access the buffer protocol using ctypes - Ideas - Discussions on Python.org
After scouring the internet for a way to do this, the following ways seem to be recommended: 1- Using from_buffer(): buffer = (ctypes.c_ubyte * length).from_buffer(data) This seems to only work for writable buffers. For example, It will fail if you pass a bytes object. More on discuss.python.org
🌐 discuss.python.org
0
February 8, 2025
ctypes from_buffer no longer accepts bytes
BPO 11427 Nosy @theller, @vstinner, @meadori, @vadmium, @eryksun Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state. Show more det... More on github.com
🌐 github.com
10
March 7, 2011
ctypes from buffer - Python - Stack Overflow
Your date field must be a ctypes type (or a type inheriting from a ctypes type). More on stackoverflow.com
🌐 stackoverflow.com
July 31, 2019
🌐
Things Of Some Value
mattgwwalker.wordpress.com › 2020 › 10 › 15 › address-of-a-buffer-in-python
Address of a buffer in Python – Things Of Some Value
October 15, 2020 - First create a ctypes array class that is of the same size as the original array. Then, use the class method from_buffer() to have the instance reference the original buffer’s memory.
Top answer
1 of 2
1

This is a bit convoluted so bear with me. In _ctypes.c (version 3.8.5), there is on line 5788:

    Py_TYPE(&Simple_Type) = &PyCSimpleType_Type;
    Simple_Type.tp_base = &PyCData_Type;
    if (PyType_Ready(&Simple_Type) < 0)
        return NULL;
    Py_INCREF(&Simple_Type);
    PyModule_AddObject(m, "_SimpleCData", (PyObject *)&Simple_Type);

This exposes the address of Simple_Type as the name _SimpleCData. Simple_Type (a.k.a. _SimpleCData to the Python world) also inherits from PyCDataType (more on that later). First, the definition of Simple_Type is on line 5040:

static PyTypeObject Simple_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_ctypes._SimpleCData",
    sizeof(CDataObject),                        /* tp_basicsize */
    0,                                          /* tp_itemsize */
    0,                                          /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)&Simple_repr,                     /* tp_repr */
    &Simple_as_number,                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    &PyCData_as_buffer,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    "XXX to be provided",                       /* tp_doc */
    (traverseproc)PyCData_traverse,             /* tp_traverse */
    (inquiry)PyCData_clear,                     /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    Simple_methods,                             /* tp_methods */
    0,                                          /* tp_members */
    Simple_getsets,                             /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Simple_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    GenericPyCData_new,                         /* tp_new */
    0,                                          /* tp_free */
};

This is in essence a metaclass used to define new classes. One of the members of this structure (with the comment /* tp_methods */) references the following array defined on line 4996:

static PyMethodDef Simple_methods[] = {
    { "__ctypes_from_outparam__", Simple_from_outparm, METH_NOARGS, },
    { NULL, NULL },

This should be a list of methods that classes created by this metaclass will have. But wait! That is only a single method. Let's look at the class it inherits from, PyCSimpleType_Type on line 2327:

PyTypeObject PyCSimpleType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_ctypes.PyCSimpleType",                                    /* tp_name */
    0,                                          /* tp_basicsize */
    0,                                          /* tp_itemsize */
    0,                                          /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    &CDataType_as_sequence,             /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    "metatype for the PyCSimpleType Objects",           /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    PyCSimpleType_methods,                      /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    PyCSimpleType_new,                                  /* tp_new */
    0,                                          /* tp_free */
};

The methods it brings along are PyCSimpleType_methods defined on line 2318:

static PyMethodDef PyCSimpleType_methods[] = {
    { "from_param", PyCSimpleType_from_param, METH_O, from_param_doc },
    { "from_address", CDataType_from_address, METH_O, from_address_doc },
    { "from_buffer", CDataType_from_buffer, METH_VARARGS, from_buffer_doc, },
    { "from_buffer_copy", CDataType_from_buffer_copy, METH_VARARGS, from_buffer_copy_doc, },
    { "in_dll", CDataType_in_dll, METH_VARARGS, in_dll_doc},
    { NULL, NULL },
};

So it appears that the mataclass builds a new class including all of the methods specified by the tp_methods member of the metaclass's struct definition and its base classes. This should result in the from_buffer method having the same address for all objects for all classes created by the metaclass.

Referring back to your post:

>>> ctypes.c_long.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x16f8fb0>
>>> ctypes._SimpleCData.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x7f19f9b09ae0>

I now believe you are misinterpreting what is being output. 0x16f8fb0 and 0x7f19f9b09ae0 are not the addresses of the from_buffer methods, which should be the same for both objects, but rather the addresses of the ctypes.PyCSimpleType objects themselves that are implementing these types.

Mark Tolonen had it right, but in case you needed convincing (I did too) ...

2 of 2
1

Listing [Python.Docs]: ctypes - A foreign function library for Python.
Also listing [GitHub]: python/cpython - (v3.9.9) This is Python version 3.9.9 (v3.9.9 is the version I'm going to use as an example). Any (Python) source file that I'm going to mention will be relative to this.

I'm not going to insist on different method addresses as it was already covered: it's not the method address (a method address wouldn't make any sense in Python) but the class object's (that encapsulate the method).

All the simple C type wrappers are defined Lib/ctypes/__init__.py (as already stated), as subclasses of _SimpleCData. The only thing that differs among them is an apparently minor detail: the _type_ class field. Check [Python.Docs]: struct - Interpret strings as packed binary data for possible values.

The "real magic" happens in Modules/_ctypes/_ctypes.c (mostly). Let's work things out from top to bottom (concept-wise, not by position in the file). I'm not going to list line numbers (as one could search for any identifier name that will be mentioned):

  • CTypes is based on (external) FFI

  • Simple_Type is the class implementation for _SimpleCData (which is its display name)

  • PyCSimpleType_Type (_ctypes.PyCSimpleType) is Simple_Type (_SimpleCData)'s meta class (set via Py_SET_TYPE). Example:

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q070496724]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe"
    Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import ctypes as ct
    >>> import _ctypes as _ct
    >>>
    >>> SCD = _ct._SimpleCData
    >>> SCD
    <class '_ctypes._SimpleCData'>
    >>> dir(SCD)
    ['__bool__', '__class__', '__ctypes_from_outparam__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_b_base_', '_b_needsfree_', '_objects', 'value']
    >>> SCD.__class__
    <class '_ctypes.PyCSimpleType'>
    
  • PyCSimpleType_methods are (some of) PyCSimpleType_Type's methods

  • CDataType_from_address and CDataType_from_buffer are the 2 method implementations

  • Both of the above methods rely on PyCData_AtAddress (note the type argument). The key line is:

    pd = (CDataObject *)((PyTypeObject *)type)->tp_alloc((PyTypeObject *)type, 0);
    
  • Going back to PyCSimpleType_Type, its instance creator method is PyCSimpleType_new: this is called when creating _SimpleCData class objects, not their instances

    • Notice the _type_ member (although the final name will differ). Does it ring a bell?

    • You can also check SIMPLE_TYPE_CHARS for a familiar set of chars

  • Things can be taken deeper (Modules/_ctypes/stgdict.c), but I think it's enough

So, the c_* classes (simple C type wrappers) don't have separate implementations for those methods, but the implementation lies in their base (meta) class, and they have a different attribute value (which complies with OOP's inheritance concept).

I created a small example.

code00.py:

#!/usr/bin/env python

import ctypes as ct
import sys


def test_from_addr(addr):
    print("\nTest from_address(0x{:016X}) method for various simple C types".format(addr))
    types = (
        ct.c_ulonglong,
        ct.c_uint,
        ct.c_ushort,
        ct.c_ubyte,
    )

    for typ in types:
        print("{:s} representation: {:}".format(typ.__name__, hex(typ.from_address(addr).value)))


def _simple_c_type_factory(spec):
    class SimpleCType(ct._SimpleCData):
        _type_ = spec

    return SimpleCType


def test_simple_types(addr):
    print("\nTest simple C type instances creation from address: 0x{:016X}".format(addr))
    specs = "QLIHB"
    for spec in specs:
        typ = _simple_c_type_factory(spec)
        print("{:s} representation: {:}".format(spec, hex(typ.from_address(addr).value)))


def main(*argv):
    ull0 = ct.c_ulonglong(0x1234567890ABCDEF)
    aull0 = ct.addressof(ull0)

    test_from_addr(aull0)
    test_simple_types(aull0)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q070496724]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32


Test from_address(0x00000199612CAE08) method for various simple C types
c_ulonglong representation: 0x1234567890abcdef
c_ulong representation: 0x90abcdef
c_ushort representation: 0xcdef
c_ubyte representation: 0xef

Test simple C type instances creation from address: 0x00000199612CAE08
Q representation: 0x1234567890abcdef
L representation: 0x90abcdef
I representation: 0x90abcdef
H representation: 0xcdef
B representation: 0xef

Done.

Notes:

  • I only used unsigned types because the memory representation is clearer (things are exactly the same for signed ones)
  • The results are a bit surprising (the part at the end matches). This is because my CPU (Intel pc064) is little endian. On a big endian system things would be a lot clearer. Check more details on this topic: [SO]: Python struct.pack() behavior (@CristiFati's answer).
🌐
Python.org
discuss.python.org › ideas
Access the buffer protocol using ctypes - Ideas - Discussions on Python.org
February 8, 2025 - After scouring the internet for a way to do this, the following ways seem to be recommended: 1- Using from_buffer(): buffer = (ctypes.c_ubyte * length).from_buffer(data) This seems to only work for writable buffers. For example, It will fail if you pass a bytes object.
🌐
Python
bugs.python.org › issue11427
Issue 11427: ctypes from_buffer no longer accepts bytes - Python tracker
This issue tracker has been migrated to GitHub, and is currently read-only. For more information, see the GitHub FAQs in the Python's Developer Guide · This issue has been migrated to GitHub: https://github.com/python/cpython/issues/55636
🌐
GitHub
github.com › python › cpython › issues › 55636
ctypes from_buffer no longer accepts bytes · Issue #55636 · python/cpython
March 7, 2011 - assignee = 'https://github.com/theller' closed_at = <Date 2011-11-29.17:57:48.767> created_at = <Date 2011-03-07.07:11:24.551> labels = ['ctypes', 'type-bug', 'invalid'] title = 'ctypes from_buffer no longer accepts bytes' updated_at = <Date 2014-07-31.15:41:14.691> user = 'https://bugs.python.org/benrg' bugs.python.org fields: activity = <Date 2014-07-31.15:41:14.691> actor = 'eryksun' assignee = 'theller' closed = True closed_date = <Date 2011-11-29.17:57:48.767> closer = 'vstinner' components = ['ctypes'] creation = <Date 2011-03-07.07:11:24.551> creator = 'benrg' dependencies = [] files =
Author: python
🌐
Runebook
runebook.dev › english › articles › python
Mastering ctypes.from_buffer() in Python: Examples and Best Practices
Cause You're passing something to from_buffer() that isn't a buffer-like object. ... Check Object Type Make sure you're using bytes, bytearray, array.array, or a similar object. Don't try to pass a list, tuple, or other non-buffer types. NumPy If using NumPy, ensure you're passing the underlying data buffer of the array, not the array object itself in some cases. numpy.ndarray objects can be coerced to the correct type. ... import ctypes class MyStruct(ctypes.Structure): _fields_ = [("x", ctypes.c_int)] my_list = [1, 2, 3, 4] # Not a buffer # my_buffer = bytearray(my_list) # Won't work directly my_buffer = bytearray(4) # Need to create a bytearray of the correct size my_struct = MyStruct.from_buffer(my_buffer) # Correct
Find elsewhere
Top answer
1 of 1
2

1 - How can I work with datetime? Fild 'date' is not working.

Your date field must be a ctypes type (or a type inheriting from a ctypes type). This means you have to find a way to express a date as a number (int, float, double, whatever you want, but it can not be a non-ctypes python type).

In this example I used the well known Unix Epoch (which can be represented on a ctypes.c_uint32)

class DataFromBuffer(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('id', ctypes.c_char * 1),
        ('name', ctypes.c_char * 30),
        ('value', ctypes.c_double),
        ('size', ctypes.c_uint16),
        ('date', ctypes.c_uint32),  # date as a 32-bit unsigned int.
    ]

# snip

    now_date_time = datetime.datetime.now()
    now_int = int(now_date_time.timestamp())  # now as an integer (seconds from the unix epoch)
    print(f"Now - datetime: {now_date_time!s}; int: {now_int}")

    test_buffer = (b"A" + # id
        # snip
        now_int.to_bytes(4, "little")  # date
    )

As for the conversion to a datetime, I simply added a function member to the structure so it can convert the date (a ctypes.c_uint32) to a datetime:

    def date_to_datetime(self) -> datetime.datetime:
        """Get the date field as a python datetime.
        """
        return datetime.datetime.fromtimestamp(self.date)

2 - Field 'size', for some reason is BigEndian. Is it possible change structure just for this field?

No it's not possible. A possible way is to have a function or property to access the field as you want it to be (performing some sort of conversion under the hood):

    def real_size(self) -> int:
        """Get the correct value for the size field (endianness conversion).
        """
        # note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
        high = self.size & 0xff
        low = (self.size & 0xff00) >> 8
        return high | low

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import ctypes
import math
import datetime

class DataFromBuffer(ctypes.LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('id', ctypes.c_char * 1),
        ('name', ctypes.c_char * 30),
        ('value', ctypes.c_double),
        ('size', ctypes.c_uint16),
        ('date', ctypes.c_uint32),
    ]

    def date_to_datetime(self) -> datetime.datetime:
        """Get the date field as a python datetime.
        """
        return datetime.datetime.fromtimestamp(self.date)

    def real_size(self) -> int:
        """Get the correct value for the size field (endianness conversion).
        """
        # note: there multiple way of doing this: bytearray.reverse() or struct.pack and unpack, etc.
        high = self.size & 0xff
        low = (self.size & 0xff00) >> 8
        return high | low

if __name__ == '__main__':
    name = b"foobar"
    now_date_time = datetime.datetime.now()
    now_int = int(now_date_time.timestamp())  # now as an integer (seconds from the unix epoch)
    print(f"Now - datetime: {now_date_time!s}; int: {now_int}")

    test_buffer = (b"A" + # id
        name + (30 - len(name)) * b"\x00" +  # name (padded with needed \x00)
        bytes(ctypes.c_double(math.pi)) +  # PI as double
        len(name).to_bytes(2, "big") +  # size (let's pretend it's the name length)
        now_int.to_bytes(4, "little")  # date (unix epoch)
    )

    assert ctypes.sizeof(DataFromBuffer) == len(test_buffer)

    data = DataFromBuffer.from_buffer(bytearray(test_buffer))
    print(f"date: {data.date}; as datetime: {data.date_to_datetime()}")
    print(f"size: {data.size} ({data.size:#x}); real size: {data.real_size()} ({data.real_size():#x})")

output:

Now - datetime: 2019-07-31 14:52:21.193023; int: 1564577541
date: 1564577541; as datetime: 2019-07-31 14:52:21
size: 1536 (0x600); real size: 6 (0x6)
🌐
Runebook.dev
runebook.dev › en › docs › python › library › ctypes › ctypes._CData.from_buffer
Python ctypes: Common Issues and Alternatives to from_buffer()
The from_buffer() class method, often available on specific ctypes types like ctypes.c_int or ctypes.Structure subclasses, is designed to create a ctypes object that shares the memory of an existing Python buffer object (like a bytearray, ...
🌐
Reddit
reddit.com › r/python › this has dogged me for years: why is it so hard to parse binary file formats in python? what's the "proper" way to do this?
r/Python on Reddit: This has dogged me for years: why is it so hard to parse binary file formats in Python? What's the "proper" way to do this?
December 16, 2014 -

For example, parsing an ELF or PE file. It seems like using ctypes would be the way to go, but... damn... how do you do this?

Lets take a simple example: I have a PE file and I want to parse a few headers to get to the export tables. This is easy in C, no-brainer in C++, but a nightmare in Python (where is should be easy!).

Manually struct.unpack seems silly, ctypes looks like it would make sense, but I can't seem to apply it here.

What am I missing? Python 2.7 solutions preferred (dark ages, I know).

Edit: The solution to a simple problem, which I hope exists, would solve a lot of my problems: how can I convert a byte buffer into a ctypes structure? You can go from structure to buffer, but I can't find the other direction. I'm trying to avoid reinventing something I feel probably exists :)

Also, I listed PE and ELF formats as an example, same could go for any binary format, including network protocols.

Edit2: u/gargantuan provided an answer for the buffer -> ctypes question

(oh, as did some others....)

Also, u/john_m_camara suggested using CFFI, which is new to me but looks intriguing.

Good suggestions so far, could always use more!

Top answer
1 of 5
9
import ctypes class Point(ctypes.LittleEndianStructure): _fields_ = [ ("x", ctypes.c_int32), ("y", ctypes.c_int32) ] buffer = "\x01\x00\x00\x00\x02\x00\x00\x00" point = Point.from_buffer_copy(buffer) assert point.x == 1 assert point.y == 2 buffer = bytearray(ctypes.sizeof(Point)) assert buffer == bytearray("\x00\x00\x00\x00\x00\x00\x00\x00") point = Point.from_buffer(buffer) point.x = 1 point.y = 2 assert buffer == bytearray("\x01\x00\x00\x00\x02\x00\x00\x00") point = Point(1, 2) buffer = ctypes.string_at(ctypes.addressof(point), ctypes.sizeof(point)) assert buffer == "\x01\x00\x00\x00\x02\x00\x00\x00" Edit: import io # BytesIO as file buffer = io.BytesIO("\x01\x00\x00\x00\x02\x00\x00\x00") point = Point() buffer.readinto(point) assert point.x == 1 assert point.y == 2 point = Point(1, 2) buffer = io.BytesIO() buffer.write(point) buffer.seek(0) assert buffer.read() == "\x01\x00\x00\x00\x02\x00\x00\x00"
2 of 5
8
You're almost certainly looking for Construct: https://github.com/MostAwesomeDude/construct Construct is a powerful declarative parser for binary data. It is based on the concept of defining data structures in a declarative manner, rather than procedural code: Simple constructs can be combined hierarchically to form increasingly complex data structures. It's the first library that makes parsing fun, instead of the usual headache it is today. Construct features bit and byte granularity, symmetrical operation (parsing and building), component-oriented declarative design, easy debugging and testing, an easy-to-extend subclass system, and lots of primitive constructs to make your work easier
🌐
GitHub
gist.github.com › alexander-hanel › a3eeef8f665ddd0a80cb59a3da8848b8
ctypes from buffer example · GitHub
ctypes from buffer example. GitHub Gist: instantly share code, notes, and snippets.
Top answer
1 of 1
12

Here's a class that will let you create ctypes arrays using the buffer interface exported by Python 2 memoryview objects.

from ctypes import *

pyapi = PyDLL("PythonAPI", handle=pythonapi._handle)

PyBUF_SIMPLE   = 0
PyBUF_WRITABLE = 0x0001
PyBUF_FORMAT   = 0x0004
PyBUF_ND       = 0x0008
PyBUF_STRIDES  = 0x0010 | PyBUF_ND

PyBUF_C_CONTIGUOUS   = 0x0020 | PyBUF_STRIDES
PyBUF_F_CONTIGUOUS   = 0x0040 | PyBUF_STRIDES
PyBUF_ANY_CONTIGUOUS = 0x0080 | PyBUF_STRIDES
PyBUF_INDIRECT       = 0x0100 | PyBUF_STRIDES

PyBUF_CONTIG_RO  = PyBUF_ND
PyBUF_CONTIG     = PyBUF_ND | PyBUF_WRITABLE

PyBUF_STRIDED_RO = PyBUF_STRIDES
PyBUF_STRIDED    = PyBUF_STRIDES | PyBUF_WRITABLE

PyBUF_RECORDS_RO = PyBUF_STRIDES | PyBUF_FORMAT
PyBUF_RECORDS    = PyBUF_STRIDES | PyBUF_FORMAT | PyBUF_WRITABLE

PyBUF_FULL_RO = PyBUF_INDIRECT | PyBUF_FORMAT
PyBUF_FULL    = PyBUF_INDIRECT | PyBUF_FORMAT | PyBUF_WRITABLE

Py_ssize_t = c_ssize_t
Py_ssize_t_p = POINTER(Py_ssize_t)

class pybuffer(Structure):
    """Python 3 Buffer Interface"""
    _fields_ = (('buf', c_void_p),
                ('obj', c_void_p), # owned reference
                ('len', Py_ssize_t),
                # itemsize is Py_ssize_t so it can be pointed to
                # by strides in the simple case.
                ('itemsize', Py_ssize_t),
                ('readonly', c_int),
                ('ndim', c_int),
                ('format', c_char_p),
                ('shape', Py_ssize_t_p),
                ('strides', Py_ssize_t_p),
                ('suboffsets', Py_ssize_t_p),
                # static store for shape and strides of
                # mono-dimensional buffers.
                ('smalltable', Py_ssize_t * 2),
                ('internal', c_void_p))

    def get_buffer(self, obj=None, flags=PyBUF_SIMPLE):
        self.release_buffer()
        Structure.__init__(self)
        if obj is not None:
            pyapi.PyObject_GetBuffer(obj, byref(self), flags)

    def make_release_buffer():
        import ctypes
        PyBuffer_Release = pyapi.PyBuffer_Release
        memset = ctypes.memset
        byref = ctypes.byref
        sizeof = ctypes.sizeof
        def release_buffer(self):
            if self.obj:
                PyBuffer_Release(byref(self))
                memset(byref(self), 0, sizeof(self))
        return release_buffer

    __init__ = get_buffer
    __del__ = release_buffer = make_release_buffer()
    del make_release_buffer        

    @property
    def as_ctypes(self):
        if self.obj and self.buf:
            arr = (c_char * self.len).from_address(self.buf)
            if self.readonly:
                arr = type(arr).from_buffer_copy(arr)
            else:
                obj = py_object.from_buffer(c_void_p(self.obj)).value
                arr._obj = obj
            return arr


pyapi.PyObject_GetBuffer.argtypes = (py_object,          # obj
                                     POINTER(pybuffer),  # view
                                     c_int)              # flags
pyapi.PyBuffer_Release.argtypes = POINTER(pybuffer),     # view

__all__ = [n for n in list(globals()) if n.startswith('PyBUF')]
__all__.append('pybuffer')

Examples:

>>> data = memoryview(b'012')
>>> buf = pybuffer(data)
>>> buf.readonly
1
>>> array = buf.as_ctypes
>>> array[0] = '9'
>>> data[0]
'0'
>>> data = memoryview(bytearray(b'012'))
>>> buf = pybuffer(data)
>>> buf.readonly
0
>>> array = buf.as_ctypes
>>> array[0] = '9'
>>> data[0]
'9'
🌐
GitHub
github.com › python › typeshed › issues › 10777
Issue with ctypes Structure.from_buffer_copy class method · Issue #10777 · python/typeshed
September 25, 2023 - # And for some reason this works (and it should). SomeStructureArray = SomeStructure*10 SomeStructureArray.from_buffer_copy(bytes([0]*160)) # is okay and should be okay
Author: python
🌐
dbader.org
dbader.org › blog › python-ctypes-tutorial
Extending Python With C Libraries and the “ctypes” Module – dbader.org
September 5, 2017 - String_buffers are mutable, and they are passed to C as a char * as you would expect. # The ctypes string buffer IS mutable, however.
🌐
CodersLegacy
coderslegacy.com › home › python › python ctypes tutorial
Python ctypes Tutorial - CodersLegacy
October 27, 2022 - As you can see, the string was properly modified. Creating a string buffer with ctypes, gives us mutable memory which we can use with C functions.
🌐
SourceForge
sourceforge.net › home › browse › ctypes › mailing lists
Re: [ctypes-users] Initialising a struct from a buffer | ctypes
ctypes-commit · ctypes-devel · ctypes-users · View entire thread · ✕ · Thanks for helping keep SourceForge clean.
🌐
Finxter
blog.finxter.com › converting-python-bytearray-to-ctypes-structure-top-methods-explored
Converting Python Bytearray to CTypes Structure: Top Methods Explored – Be on the Right Side of Change
February 24, 2024 - An example input would be a Python bytearray containing raw data that we want to cast into a CTypes structure representing a specific memory layout. The from_buffer() method allows direct data transfer from objects that support the buffer protocol (like bytearray) into a CTypes structure.