[Программирование, Совершенный код, C++, Отладка, C] Как подключить содержимое любых файлов для использования в коде C / C++

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
08-Мар-2021 05:30

Привет, Хабровчане!Это моя первая статья и у меня есть чем поделиться. Возможно мой велосипед не нов и этим способом пользуется каждый, но когда-то давно искал решения, с ходу найти не получилось.О чем речь?Задача состояла в подключении файлов: HTML, JS, CSS; без специальной подготовки. Так же неудобно подключать бинарные файлы (например картинки) конвертируя их в HEX. Так как не хотелось конвертировать в HEX или разделять на строки, искал способ подключения файла в адресное пространство программы.Как обычно это выглядитПример, c разделением строк:
const char text[] =
    "<html>"            "\r\n"
    "<body>Text</body>" "\r\n"
    "</html>";
Пример, с HEX (больше подходит для бинарных данных):
const char text[] =
    {
        0x3C, 0x68, 0x74, 0x6D, 0x6C, 0x3E, 0x0A, 0x3C,
        0x62, 0x6F, 0x64, 0x79, 0x3E, 0x54, 0x65, 0x78,
        0x74, 0x3C, 0x2F, 0x62, 0x6F, 0x64, 0x79, 0x3E,
        0x0A, 0x3C, 0x2F, 0x68, 0x74, 0x6D, 0x6C, 0x3E,
        0
    };
Видел даже такое:
#define TEXT "<html>\r\n<body>Text</body>\r\n</html>"
const char text[] = TEXT;
Все #define располагались в отдельном .h файле и подготавливались скриптом на Python. С аннотацией, что некоторые символы должны бить экранированы \ вручную в исходном файле. Честно немного волосы дыбом встали от такого мазохизма.А хотелось, чтобы файлы можно было спокойно редактировать, просматривать и при компиляции всё само подключалось и было доступно, например так:
extern const char text[];
Оказалось всё просто, несколько строчек в Assembler.Подключаем файл в Arduino IDEДобавляем новую вкладку или создаём файл в папке проекта с названием text.S, там же размещаем файл text.htm.Содержимое файла text.htm:
<html>
<body>Text</body>
</html>
Содержимое файла text.S:
.global text
.section .rodata.myfiles
text:
    .incbin "text.htm"
    .byte 0
Не забываем нулевой символ \0 в конце, он здесь в строке сам не добавится.Сам скетч:
extern const char text[] PROGMEM;
void setup()
{
    Serial.begin(115200);
    Serial.println(text);
}
void loop() { }
Компилируем, загружаем и смотрим вывод:
Отлично, когда-то бы я от радости прыгал до потолка, от того что всё получилось.Код работает в AVR8, но например в ESP8266 получим аппаратный сбой. Всё потому, что чтение из Flash доступно по 32 бита и по адресам кратным 32 бит. Чтобы было всё хорошо, каждому файлу требуется делать отступ для кратности, код будет выглядеть так:
.global text
.section .rodata.myfiles
.align 4
text:
    .incbin "text.htm"
    .byte 0
Загрузить можно в секцию кода: .irom.text, если не хватает места в .rodata.Для STM32 так же рекомендуется выравнивать по 32 бита, но не обязательно.А как записать размер данных во время компиляции? Например, для бинарных данных не получится остановится по нулевому символу. Так же просто:
.global text, text_size
.section .rodata.myfiles
text:
    .incbin "text.htm"
    text_end:
    .byte 0
text_size:
  .word (text_end - text)
Объявление:
extern const char text[] PROGMEM;
extern const uint16_t text_size PROGMEM;
Осталось написать макрос, для удобства подключения файлов:
.macro addFile name file
    .global \name, \name\()_size
//  .align 4
    \name:
        .incbin "\file"
        \name\()_end:
        .byte 0
//  .align 4
    \name\()_size:
        .word (\name\()_end - \name)
.endm
.section .rodata.myfiles
addFile text1   1.txt
addFile text2   2.txt
addFile text3   3.txt
И макрос для объявления:
#define ADD_FILE(name) \
    extern const char name[] PROGMEM; \
    extern const uint16_t name##_size PROGMEM;
ADD_FILE(text1);
ADD_FILE(text2);
ADD_FILE(text3);
void setup()
{
    Serial.begin(115200);
    Serial.println(text1);
    Serial.println(text1_size);
    Serial.println(text2);
    Serial.println(text2_size);
    Serial.println(text3);
    Serial.println(text3_size);
}
void loop() { }
Вывод:
Таким образом можно подключить любой файл и представить его любым типом, структурой или массивом.Подключаем любой файл и не только, в GNU toolchainПринцип тот же самый, ни чем не отличается для Arduino. В принципе в Arduino используется тот же toolchain от Atmel.Только здесь у нас в руках Makefile и мы можем до компиляции и сборки запустить какой-нибудь скрипт.Для примера возьму код из готового моего проекта на STM32, где автоматически при компиляции увеличивается версия сборки. Так же включаются в проект WEB-интерфейс для последующего использования в LWIP / HTTPD.Скрипт version.sh:
#!/bin/bash
# Version generator
# running script from pre-build
MAJOR=1
MINOR=0
cd "$(dirname $0)" &>/dev/null
FILE_VERSION="version.txt"
FILE_ASM="version.S"
BUILD=$(head -n1 "$FILE_VERSION" 2>/dev/null)
if [ -z "$BUILD" ]; then
  BUILD=0
else
  BUILD=$(expr $BUILD + 1)
fi
echo -n "$BUILD" >"$FILE_VERSION"
cat <<EOF >"$FILE_ASM"
/**
* no editing, automatically generated from version.sh
*/
.section .rodata
.global __version_major
.global __version_minor
.global __version_build
__version_major: .word $MAJOR
__version_minor: .word $MINOR
__version_build: .word $BUILD
.end
EOF
cd - &>/dev/null
exit 0
Создаётся файл version.S в который из version.txt загружается номер версии предыдущей сборки.В Makefile добавляется цель pre-build:
#######################################
# pre-build script
#######################################
pre-build:
  bash version.sh
В цель all надо дописать pre-build:
all: pre-build $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
Объявление и макросы для printf у меня в macro.h:
extern const uint16_t __version_major;
extern const uint16_t __version_minor;
extern const uint16_t __version_build;
#define FMT_VER             "%u.%u.%u"
#define FMT_VER_VAL         __version_major, __version_minor, __version_build
В HTTPD из LWIP немного был удивлён когда увидел, что содержимое файлов надо хранить вместе с заголовками HTTP. Чтобы не менять архитектуру, загрузку делал как это организовано в примере fsdata.c. Использовал fsdata_custom.c, для этого установлен флаг HTTPD_USE_CUSTOM_FSDATA.Код в fsdata_custom.c:
#include "lwip/apps/fs.h"
#include "lwip/def.h"
#include "fsdata.h"
#include "macro.h"
extern const struct fsdata_file __fs_root;
#define FS_ROOT &__fs_root
Сборка файлов fsdata_make.S:
.macro addData name file mime
  \name\():
  .string "/\file\()"
  \name\()_data:
  .incbin "mime/\mime\().txt"
  .incbin "\file\()"
  \name\()_end:
.endm
.macro addFile name next
  \name\()_file:
  .word \next\()
  .word \name\()
  .word \name\()_data
  .word \name\()_end - \name\()_data
  .word 1
.endm
.section .rodata.fsdata
.global __fs_root
/* Load files */
addData __index_htm         index.htm           html
addData __styles_css        styles.css          css
addData __lib_js            lib.js              js
addData __ui_js             ui.js               js
addData __404_htm           404.htm             404
addData __favicon_ico       img/favicon.ico     ico
addData __logo_png          img/logo.png        png
/* FSDATA Table */
addFile __logo_png          0
addFile __favicon_ico       __logo_png_file
addFile __404_htm           __favicon_ico_file
addFile __ui_js             __404_htm_file
addFile __lib_js            __ui_js_file
addFile __styles_css        __lib_js_file
__fs_root:
addFile __index_htm         __styles_css_file
.end
В начале каждого файла загружается заголовок, пару примеров из папки mime.Файл html.txt:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Connection: close
Файл 404.txt:
HTTP/1.1 404 Not found
Content-Type: text/plain; charset=UTF-8
Connection: close
Нужно обратить внимание на пустую строку, чего требует спецификация HTTP для обозначения конца заголовка. Каждая строка должна заканчиваться символом CRLF (\r\n).P.S. Код проекта из ветхого сундука, так что в реализации мог забыть чего ни будь уточнить.В завершенииДолго искал, что изложить в статье полезного. Надеюсь мой опыт пригодится новичку и гуру.Спасибо за внимание, удачных разработок!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_sovershennyj_kod (Совершенный код), #_c++, #_otladka (Отладка), #_c, #_stm32, #_arduino, #_lwip, #_httpd, #_esp8266, #_assembler, #_c/c++, #_avr, #_toolchain, #_gnu, #_programmirovanie (
Программирование
)
, #_sovershennyj_kod (
Совершенный код
)
, #_c++, #_otladka (
Отладка
)
, #_c
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 19-Май 10:26
Часовой пояс: UTC + 5