Преобразовать float в строку и отправить из кода C в Python через именованный канал

#python #c #linux #named-pipes

#python #c #linux #именованные каналы

Вопрос:

Я хотел бы отправлять значения с плавающей запятой из кода C в код Python, используя именованные каналы. Я печатаю полученные значения в терминал на стороне Python, однако наряду с самим значением также отображаются символы тарабарщины.

Открытие канала:

 void Init_FIFO(void)
{
    // FIFO file path
    char * bldc_fifo = "/tmp/bldc_fifo";

    // Creating the named FIFO -- mkfifo(<pathname>, <permission>)
    mkfifo(bldc_fifo, 0666);

    // Open FIFO to write/read data
    fd_fifo = open(bldc_fifo, O_RDWR | O_NONBLOCK);
    //fd_fifo = open(bldc_fifo, O_WRONLY | O_RDONLY | O_NONBLOCK);
}
  

Для преобразования float в строку я использую sprintf, и код приведен ниже,

 void SendDataOverFifo(float angle)
{
    char str[64];
    unsigned char writeBuffer[] = "Hello!";

    Init_FIFO();

    sprintf(str, "%fn", angle);
    write(fd_fifo, str, sizeof(str));
    //write(fd_fifo, writeBuffer, sizeof(writeBuffer));
    close(fd_fifo);
}
  

Затем для получения кода на стороне Python я использую это

 #!/usr/bin/python

import os
import errno
import time

FIFO = '/tmp/bldc_fifo'

try:
    os.mkfifo(FIFO)
except OSError as oe: 
    if oe.errno != errno.EEXIST:
        raise

print("Opening FIFO...")
with open(FIFO, encoding='utf-8', errors='ignore') as fifo:
    print("FIFO opened")
    while True:
        time.sleep(0.1)
        data = fifo.read()
        print(data)
  

Результат, который я получаю, выглядит примерно так

i-W?UOeiEU11.417070

Где правильный результат должен быть:

11.417070

Примечание: Если я попытаюсь отправить только «Привет!», это сработает без каких-либо проблем.

Чего мне здесь не хватает? Заранее спасибо.

Комментарии:

1. Можете ли вы использовать экспоненциальный формат, подобный sprintf(str, "%en", angle); ?

Ответ №1:

Первый красный флаг находится в sprintf вызове; он не знает, насколько велик ваш целевой буфер str , поэтому может переполниться, если вы не будете осторожны. С одним float и 64 байтами этот шаг должен быть выполнен нормально.

Однако вы не сохранили возвращаемое значение, поэтому на данный момент вы не знаете, насколько велик форматированный текст. Затем вы использовали sizeof , который сообщает вам, насколько велик буфер, а не сколько данных вы только что поместили в него. Вы могли бы использовать функцию на основе строк (поскольку sprintf написана строка, заканчивающаяся нулем), такую как strlen (для измерения строки) или fputs (для записи строки в файл).

Гораздо более простым способом могло бы быть использование fprintf в первую очередь, и не нужно выделять отдельный буфер (скорее всего, он использует встроенный в FILE ) для хранения форматированной строки.

Возможно, хотя и не обязательно переносимое или безопасное, преобразование между файловыми дескрипторами (такими как write и close use) и FILE (такими как fprintf uses) с помощью таких функций, как fdopen .

Комментарии:

1. Я думаю, что я не могу использовать fprintf, потому что я использую именованные каналы, а не ФАЙЛ

2. … вот почему в последнем абзаце объясняется, что вы можете обернуть файловый дескриптор. Вы находитесь в системе типа Unix, все является файлом (именованный канал — это просто немного особенный файл).

Ответ №2:

Строка:

 write(fd_fifo, str, sizeof(str));
  

вызывает запись неинициализированной памяти в fifo. Вы не хотите записывать весь str буфер, только размер строки, которую вы хотите передать. И вы можете узнать это по snprintf возвращаемому значению с помощью strlen(str) .

 int ret = sprintf(str, "%f", ...);
assert(ret > 0); // just to be safe
write(fd_fifo, str, ret);
  

Использование sprintf небезопасно для вас, потому что. Используйте snprintf для защиты от переполнения стека.

 int ret = snprintf(str, sizeof(str), ....
// no changes
  

Таким образом, sprintf в буфер никогда не будет записано больше sizeof(str) символов.

Однако лучший способ — не иметь статически выделенного буфера. Вы можете использовать fdopen :

 FILE *f = fdopen(fd_fifo, "w");
if (f == NULL) {
      // handle error
}
int ret = fprintf(f, "%f", ...);
if (ret < 0) {
      // handle error
}
fclose(f);
  

или заранее узнайте размер буфера, снова вызовите malloc и snprintf:

 int ret = sprintf(NULL, "%f", ...);
assert(ret > 0);
char *str = malloc(ret * sizeof(char));
if (str == NULL) { 
      // handler error
}
ret = snprintf(str, "%f", ...);
write(fd_fifo, str, ret);
free(str);
  

Комментарии:

1. snprintf возвращает количество символов, которое было бы записано, поэтому, если вы действительно хотите защититься от переполнения буфера, вы также должны проверить ret <= sizeof str . asprintf , если доступно, позволяет выделить достаточный буфер за один вызов.

Ответ №3:

Я решил проблему, решением было изменение этой строки

write(fd_fifo, str, sizeof(str));

Для

write(fd_fifo, str, strlen(str));

Комментарии:

1. Это одно из примерно трех решений, указанных мной и Камилом.

2. @YannVernier извините, я не понял предложения strlen, которое вы, ребята, дали, я хотел спать, я думаю, моя вина…