LINUX.ORG.RU

Функция на С для python. Нужны советы.

 ,


1

3

Здравствуйте, коллеги!

Нужда приперла реализовать некую функцию на С для последующего использования ее в коде на python. Подтягивать ее намерен через ctypes. Хотя тут могут быть варианты. Открыт вашим предложениям.

Функция довольно заковыристая. Вот ее прототип на С

char * func4py(char * data, int data_size, int pos, char * buf, int buf_size);
   char * res = (char *) malloc(buf_size + pos);
   // ...
   return res;

Функция принимает 2 указателя на области памяти, кое-что магичит с данными и результат закидывет в выделенный res, который и возвращает в качестве указателя.

И тут я сталкиваюсь со всей глубиной своего незнания…

В python я бы хотел вызывать эту функцию в виде:

buf3 = func4py(buf1: bytes, pos: int, buf2: bytes)

Ведь python же всегда знает размеры своих объектов!

Но как передать это сокральное знание с С?

Можно, конечно:

buf3 = func4py(buf1: bytes, buf1_size: int = len(buf1), pos: int, buf2: bytes, buf2_size: int = len(buf2))

Жуть какая-то, на мой взгляд. Можно, конечно, сделать обертку в виде питоновской функции, но может есть способ более элегантный?

Еще непонятка с выделением памяти.

Для результирующего буфера мне память приходится выделять, а python ее в дальнейшем сам корректно освободит?

И еще, наверное, стоит как-то генерировать исключение если malloc вернет NULL.

в общем, прошу совета как все это грамотно реализовать?

Ну и вишенка на торте:

Как вернуть не только указатель на буфер, а еще и измененный pos? Типа как tuple или list

PS Я нормально знаю С, так что с реализацией функции ни каких проблем. Но я никогда еще не писал сишные функции для python.



Последнее исправление: HighMan (всего исправлений: 1)

Но на ctypes наверное попроще - https://chat.openai.com/share/5266b840-6f79-4e3f-bd67-342478203d05

In order to call a C function from Python, you can use the ctypes or cffi libraries, or the Cython tool. Below are steps using ctypes:

  1. C Code Preparation:

    • Create a file named func4py.c with the following content:
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      
      char* func4py(const char* data, int data_size, int pos, const char* buf, int buf_size) {
          char* res = (char*) malloc(buf_size + pos);
          // ...
          return res;
      }
      
  2. Compilation:

    • Compile func4py.c into a shared library:
      gcc -shared -o libfunc4py.so -fPIC func4py.c
      
  3. Python Wrapper:

    • Create a file named wrapper.py with the following content:
      import ctypes
      
      # Load the shared library
      lib = ctypes.CDLL('./libfunc4py.so')
      
      def func4py(buf1, pos, buf2):
          # Convert Python bytes objects to ctypes char pointers
          c_buf1 = ctypes.create_string_buffer(buf1)
          c_buf2 = ctypes.create_string_buffer(buf2)
      
          # Call the C function
          c_res = lib.func4py(c_buf1, len(buf1), pos, c_buf2, len(buf2))
      
          # Convert the result to a Python bytes object
          res_size = len(buf2) + pos
          res = ctypes.string_at(c_res, res_size)
      
          # Free the allocated memory in C (optional, but recommended)
          ctypes.CDLL('libc.so.6').free(c_res)
      
          return res
      
      # Usage
      buf1 = b'data1'
      pos = 5
      buf2 = b'data2'
      buf3 = func4py(buf1, pos, buf2)
      print(buf3)
      

In the func4py Python function, ctypes.create_string_buffer is used to convert Python bytes objects to ctypes char arrays. The ctypes.CDLL function is used to load the shared library, and ctypes.string_at is used to convert the result back to a Python bytes object. Finally, ctypes.CDLL('libc.so.6').free is used to free the allocated memory in C, which is optional but recommended to prevent memory leaks.

Now, you can call func4py from Python as you desired:

buf3 = func4py(buf1, pos, buf2)
ei-grad ★★★★★
()
Последнее исправление: ei-grad (всего исправлений: 1)
Ответ на: комментарий от HighMan

Как-то так:

To create a Python extension module using the C API and PyBytesObject directly, you’ll need to follow these steps:

  1. C Code Preparation: Create a new file named func4py_module.c with the following content:

    #include <Python.h>
    
    static PyObject* func4py(PyObject* self, PyObject* args) {
        PyBytesObject* py_buf1;
        int pos;
        PyBytesObject* py_buf2;
    
        if (!PyArg_ParseTuple(args, "O!iO!", &PyBytes_Type, &py_buf1, &pos, &PyBytes_Type, &py_buf2)) {
            return NULL;
        }
    
        char* buf1 = PyBytes_AS_STRING(py_buf1);
        int buf1_size = PyBytes_GET_SIZE(py_buf1);
    
        char* buf2 = PyBytes_AS_STRING(py_buf2);
        int buf2_size = PyBytes_GET_SIZE(py_buf2);
    
        char* res = (char*) malloc(buf2_size + pos);
        // ... (your processing here)
    
        PyObject* py_res = PyBytes_FromStringAndSize(res, buf2_size + pos);
        free(res);
    
        return py_res;
    }
    
    static PyMethodDef Func4pyMethods[] = {
        {"func4py",  func4py, METH_VARARGS, "Execute func4py."},
        {NULL, NULL, 0, NULL}  // Sentinel
    };
    
    static struct PyModuleDef func4pymodule = {
        PyModuleDef_HEAD_INIT,
        "func4py",   // name of module
        NULL, // module documentation, may be NULL
        -1,   // size of per-interpreter state of the module, or -1 if the module keeps state in global variables.
        Func4pyMethods
    };
    
    PyMODINIT_FUNC PyInit_func4py(void) {
        return PyModule_Create(&func4pymodule);
    }
    
  2. Compilation: Create a setup script setup.py with the following content:

    from distutils.core import setup, Extension
    
    module = Extension('func4py',
                      sources = ['func4py_module.c'])
    
    setup(name = 'Func4py',
          version = '1.0',
          description = 'This is a func4py package',
          ext_modules = [module])
    

    Now run the setup script with:

    python3 setup.py build
    
  3. Python Usage: After building the extension module, you can import it in Python and use the func4py function as follows:

    import func4py
    
    buf1 = b'data1'
    pos = 5
    buf2 = b'data2'
    
    buf3 = func4py.func4py(buf1, pos, buf2)
    print(buf3)
    

In the C code, PyArg_ParseTuple is used to parse the arguments from Python, PyBytes_AS_STRING and PyBytes_GET_SIZE are used to get the data and size from the PyBytesObject, and PyBytes_FromStringAndSize is used to create a new PyBytesObject from the result char*. The PyMODINIT_FUNC PyInit_func4py(void) function and the PyModuleDef structure are used to define the extension module.

ei-grad ★★★★★
()
Ответ на: комментарий от ei-grad

Create a file named wrapper.py with the following content: …

А.. Ну как я и думал. Вызывать через обертку.

Но остаются еще 2 вопроса:

  1. Python нормально освободит память, веделенную malloc, когда число ссылок на объект станет 0?

  2. Как из С функции вертануть не только char * а tuple, типа (buf3: bytes, pos: int)?

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от ei-grad

Как-то так:

To create a Python extension module using the C API and PyBytesObject directly, you’ll need to follow these steps: …

Я правильно понимаю, что примеры кода вам генерит ИИ?

Дайте ссылку, плз.

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от alex0x08

Пять минут гугления: https://github.com/starnight/python-c-extension

Спасибо!

Сейчас пытаюсь адаптировать свой код к этой хераборе.

Получается как-то не очень (

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 3)
Ответ на: комментарий от alex0x08

Кое что стало получаться, но внезапно натолкнулся на непонятку.

У меня обрабатываются 2 байтовых массива и на этом основании формируется третий. Все вроде нормально, пока я не запихиваю байт 0 (C отрывок):

res[i] = 0
res[i + 1] = 'k'

В сишном коде все отрабатывает нормально, а вот получая результирующий буфер в python, его размер обрезается. Т.е. все данные, которые идут за нулевым байтом python отбрасывает.

Тогда я сымитировал сишный код в python и обнаружил странную вещь: когда в python

res = bytearray(len(buf))
#.....
res[i] = 0
res[i + 1] = 'k'

То в отладчике vscode в res добавляется \x00T\ и все нормально. Результирующий буфер прежнего размера.

Что нужно сделать с С коде, что бы нулевой байт записывался в буфер как \x00T?

Понимаю, что вопрос задал очень сумбурно, но пока не придумал как его лучше сформулировать

HighMan
() автор топика
Ответ на: комментарий от alex0x08

Не поверишь: экранировать.

Извините, можно чуть подробнее?

Как экранировать нулевой байт в сишном коде?

res[i] = '\0';

Так?

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от HighMan

Говорит “я нормально знаю C”, а потом «Как экранировать нулевой байт в сишном коде?» Напиши res[i] = 0;

ai написал мусор какой-то с двойными аллокациями, копированием памяти в память. На фоне питона конечно и так сойдёт, но вроде на C модули для питона пишут когда надо сделать нормально

cobold ★★★★★
()
Ответ на: комментарий от cobold

Говорит “я нормально знаю C”, а потом «Как экранировать нулевой байт в сишном коде?» Напиши res[i] = 0;

ai написал мусор какой-то с двойными аллокациями, копированием памяти в память. На фоне питона конечно и так сойдёт, но вроде на C модули для питона пишут когда надо сделать нормально

Я спрашивал как экранировать 0 для понимания питоном, не как окончание массива, а как элемент этого массива.

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от cobold

Ты из C-шной функции в питон какой тип возвращаешь?

Tuple (bytes, int)

И вот bytes python обрезает, как только встречает 0.

Я посмотрел в отладчике и там python вместо \x0\ записывает \x00T\ в таком виде обрезание не происходит. Но как в С ему объяснить - не понимаю.

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от cobold

Покажи код

PyObject * crypto(PyObject *self, PyObject *args){
    int key_size, key_pos, buf_size;
    char * key = NULL, * buf = NULL;

    if(!PyArg_ParseTuple(args, "yiiyi", &key, &key_size, &key_pos, &buf, &buf_size))
        return NULL;
    if(key_pos > key_size)
        key_pos = 0;
    char * data = malloc(buf_size);
    memset(data, 0, buf_size);
    int b, k = 0;
    for(b = 0, k = key_pos; b < buf_size; b += 1, k += 1){
        if(k == key_size - 1)
            k = 0;
        if(buf[b] == key[k]){
        // Тут и кроется проблема
        // если buf[b] == key[k] то xor на них дает 0
        // а python этот 0 считает концом массива \x0\
        // если же этот код перевести в python то при получении
        // 0 в массив записывается \x00T\
        // По крайней мере так показывает отладчик в vscode
            data[b] = '\0';
            continue;
        }
         data[b] = buf[b] ^ key[k];
    }
   
    return Py_BuildValue("yi", data, k);
}
HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 3)
Ответ на: комментарий от cobold

Ошибка в “yi” перечитай доку

Хм… «s*i»?

Я переделал, но фуннкция перестала вызываться bp Python

bad format char passed to Py_BuildValue

Поменял 2 строки:

PyArg_ParseTuple(args, "s*iis*i", &key, &key_size, &key_pos, &buf, &buf_size);
return Py_BuildValue("s*i", data, k);

При вызове из Python:

*** stack smashing detected ***: terminated
HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 2)