sourcenouveau
sourcenouveau

Reputation: 30504

Converting between bytes and POINTER(c_ubyte)

How do I convert between bytes and POINTER(c_ubyte) in Python?

I want to pass a bytes object to a C function as a POINTER(c_ubyte) argument, and I want to work with a returned POINTER(c_ubyte) as bytes.

Right now I am using:

data = b'0123'
converted_to = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte))
converted_from = bytes(converted_to)

This doesn't seem quite right. I get a warning in PyCharm on data in the converted_to line that says:

Expected type 'Union[_CData, _CArgObject]', got 'bytes' instead

Upvotes: 5

Views: 5591

Answers (1)

Neitsa
Neitsa

Reputation: 8166

Here's a simple C++ function:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT unsigned char* DoBytesStuff(const unsigned char* buffer, size_t buffer_size)
{
    const auto new_buffer = new unsigned char[buffer_size];
    memcpy(new_buffer, buffer, buffer_size);

    for(size_t idx = 0; idx < buffer_size; idx++)
    {
        new_buffer[idx] += 1;
    }

    return new_buffer;
}

EXTERN_DLL_EXPORT void FreeBuffer(const unsigned char* buffer)
{
    delete[] buffer;
}

So basically, it takes an unsigned char* buffer in input, copy it, add 1 to each element in the copy and returns the copied buffer.

Now for python:

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


def main():
    dll = ctypes.WinDLL(r"TestDll.dll")

    dll.DoBytesStuff.argtypes = [ctypes.POINTER(ctypes.c_ubyte), ctypes.c_size_t]
    dll.DoBytesStuff.restype = ctypes.POINTER(ctypes.c_ubyte)
    dll.FreeBuffer.argtypes = [ctypes.POINTER(ctypes.c_ubyte)]

    buffer = bytes(range(0x100))  # 0 to 0xff
    # ctypes instance that shares the buffer of the source object; use from_buffer_copy() to not share it
    # note that we must use a bytearray because bytes object are immutable (and therefore not writable).
    ubuffer = (ctypes.c_ubyte * len(buffer)).from_buffer(bytearray(buffer))

    result = dll.DoBytesStuff(ubuffer, len(buffer))
    b_result = ctypes.string_at(result, len(buffer))
    print(b_result)

    dll.FreeBuffer(result)

if __name__ == "__main__":
    main()

(ctypes.c_ubyte * len(buffer)) create an array of c_ubyte which is then initialized with the from_buffer function. from_buffer accepts only a writable object and thus we can't use bytes.

As for the return, string_at directly returns a bytes object from the pointer.

Upvotes: 4

Related Questions