'How do I copy a part of bytearray to c_void_p-referenced memory and vice versa?

In the Python application, I have a bytearray with some data. My python code is called by some external native library (DLL/so/dylib) (using ctypes and its callback functions). One of the callback functions includes a pointer to the unmanaged buffer, allocated in that external library.

I need to copy a part of the bytearray into this unmanaged buffer or copy contents of the unmanaged buffer into the bytearray at specific location.

To put it simply, I have

def copy_from_python_callback(c_void_p_parameter : c_void_p, offset : int, size : int):
  managed_buf = bytearray(some_size) # suppose we have data in this buffer already
  unmanaged_buf = c_void_p_parameter
  # what do I need to do here?
  # src_buf = pointer_to_specific_byte_in_managed_buf
  memmove(unmanaged_buf, src_buf, size)

def copy_to_python_callback(c_void_p_parameter : c_void_p, offset : int, size : int):
  managed_buf = bytearray(some_size) #some_size is assumed to be larger than offset + size
  unmanaged_buf = c_void_p_parameter
  # what do I need to do here?
  # dst_buf = pointer_to_specific_byte_in_managed_buf
  memmove(dst_buf, unmanaged_buf, size)

In other languages, the answer would be simple - I'd either call the dedicated method (e.g. in .NET Framework's Marshal class), or get the pointer to the specific byte in the bytearray (in native languages like C++ or Pascal) and be done. Unfortunately, I don't see how to do either of these operations in Python without an intermediate bytes() or similar buffer.

I have some methods which use the intermediate bytes() instance, but copying the data just because there's no way to get a pointer seems to be weird to me.

I am looking for version-independent solution, if possible, but can live with python3-only one as well. Thank you in advance.



Solution 1:[1]

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

What you're looking for is possible. Arrays (CTypes) come in handy.
Below it's a "small" example. For demo purposes, the buffers only contain "human friendly" chars.

dll00.c:

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

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

#define C_TAG "From C - "


typedef int (*ReadFunc)(void *ptr, uint32_t offset, uint32_t size);
typedef int (*WriteFunc)(void *ptr, uint32_t offset, uint32_t size);

#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API int testBufferCallbacks(uint32_t bufSize,
                                         ReadFunc read, uint32_t readOffset, uint32_t readSize,
                                         WriteFunc write, uint32_t writeOffset, uint32_t writeSize);

#if defined(__cplusplus)
}
#endif


void prinBuffer(const uint8_t *buf, uint32_t size) {
    printf("%sBuffer (size %d) 0x%016llX\n  Contents: ", C_TAG, size, (uint64_t)buf);
    for (uint32_t i = 0; i < size; i++) {
        printf("%c", ((uint8_t*)buf)[i]);
    }
    printf("\n");
}

int testBufferCallbacks(uint32_t bufSize,
                        ReadFunc read, uint32_t readOffset, uint32_t readSize,
                        WriteFunc write, uint32_t writeOffset, uint32_t writeSize) {
    const uint8_t charOffset = 0x41;
    void *buf = malloc(bufSize);
    uint8_t *cBuf = (uint8_t*)buf;
    for (uint32_t i = 0; i < bufSize; i++) {
        cBuf[i] = (uint8_t)((i + charOffset) % 0x100);
    }
    prinBuffer(cBuf, bufSize);
    if ((read != NULL) && (readSize > 0)) {
        printf("\n%sCalling read(0x%016llX, %u, %u)...\n", C_TAG, (uint64_t)buf, readOffset, readSize);
        read(buf, readOffset, readSize);
    }
    if ((write != NULL) && (writeSize > 0)) {
        printf("\n%sCalling write(0x%016llX, %u, %u)...\n", C_TAG, (uint64_t)buf, writeOffset, writeSize);
        write(buf, writeOffset, writeSize);
        prinBuffer(cBuf, bufSize);
    }
    if ((read != NULL) && (readSize > 0)) {
        printf("\n%sCalling read(0x%016llX, %u, %u)...\n", C_TAG, (uint64_t)buf, readOffset, readSize);
        read(buf, readOffset, readSize);
    }
    prinBuffer(cBuf, bufSize);
    free(buf);
    printf("\n%sDone.\n", C_TAG);
    return 0;
}

code00.py:

#!/usr/bin/env python3

import sys
import ctypes as ct


DLL_NAME = "./dll00.dll"

ReadFunc = ct.CFUNCTYPE(ct.c_int, ct.c_void_p, ct.c_uint32, ct.c_uint32)
WriteFunc = ct.CFUNCTYPE(ct.c_int, ct.c_void_p, ct.c_uint32, ct.c_uint32)


def create_bytearray(size, offset_char=0x61):
    contents = "".join(chr(i) for i in range(offset_char, offset_char + size))
    return bytearray(contents.encode())


def read_c_buf(buf : ct.c_void_p, offset : ct.c_uint32, size : ct.c_uint32):
    print("C buf: 0x{0:016X}".format(buf))
    ba = create_bytearray(0x1A)
    print("Python initial buffer: {0:}".format(ba))
    UCharArr = ct.c_uint8 * size
    uchar_arr = UCharArr.from_buffer(ba, offset)  # Shared memory
    ct.memmove(uchar_arr, buf, size)
    print("Python final buffer: {0:}\n".format(ba))
    return 0


def write_c_buf(buf : ct.c_void_p, offset : ct.c_uint32, size : ct.c_uint32):
    print("C buf: 0x{0:016X}".format(buf))
    ba = create_bytearray(size + offset, offset_char=0x30 - offset)
    print("Python buffer: {0:}\n".format(ba))
    UCharArr = ct.c_uint8 * size
    uchar_arr = UCharArr.from_buffer(ba, offset)  # Shared memory
    ct.memmove(buf, uchar_arr, size)
    return 0


def main(*argv):
    dll00 = ct.CDLL(DLL_NAME)
    testBufferCallbacks = dll00.testBufferCallbacks
    testBufferCallbacks.argtypes = (ct.c_uint32, ReadFunc, ct.c_uint32, ct.c_uint32, WriteFunc, ct.c_uint32, ct.c_uint32)
    testBufferCallbacks.restype = ct.c_int

    read_callback = ReadFunc(read_c_buf)
    buf_size = 0x1A
    read_offset = 10
    read_size = 16
    write_callback = WriteFunc(write_c_buf)
    write_offset = 5
    write_size = 10
    res = testBufferCallbacks(buf_size, read_callback, read_offset, read_size, write_callback, write_offset, write_size)
    print("\n{0:s} returned: {1:d}".format(testBufferCallbacks.__name__, res))


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\q059255471]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2017\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.17
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'

[prompt]> dir /b
code00.py
code01.py
dll00.c

[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
code01.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code00.py
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32

From C - Buffer (size 26) 0x0000016BFE546EB0
  Contents: ABCDEFGHIJKLMNOPQRSTUVWXYZ

From C - Calling read(0x0000016BFE546EB0, 10, 16)...
C buf: 0x0000016BFE546EB0
Python initial buffer: bytearray(b'abcdefghijklmnopqrstuvwxyz')
Python final buffer: bytearray(b'abcdefghijABCDEFGHIJKLMNOP')


From C - Calling write(0x0000016BFE546EB0, 5, 10)...
C buf: 0x0000016BFE546EB0
Python buffer: bytearray(b'+,-./0123456789')

From C - Buffer (size 26) 0x0000016BFE546EB0
  Contents: 0123456789KLMNOPQRSTUVWXYZ

From C - Calling read(0x0000016BFE546EB0, 10, 16)...
C buf: 0x0000016BFE546EB0
Python initial buffer: bytearray(b'abcdefghijklmnopqrstuvwxyz')
Python final buffer: bytearray(b'abcdefghij0123456789KLMNOP')

From C - Buffer (size 26) 0x0000016BFE546EB0
  Contents: 0123456789KLMNOPQRSTUVWXYZ

From C - Done.

testBufferCallbacks returned: 0

Done.

Needless to say that going outside (C) buffer's bounds, would yield Undefined Behavior (and my code doesn't perform that kind of checks).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1