Разбить большой файл на более мелкие файлы

У меня был очень большой файл .ino, и я решил разбить его на подфайлы.

Итак, у меня были файл .ino, файл globals.h и файл functions.h.

Я переместил все глобальные переменные и функции в соответствующие файлы. Вот что происходит внутри:

//.ino
#include "globals.h"
#include "functions.h"

//globals.h
* Имеет защиту заголовка
*Все переменные здесь внешние
*Содержит прототипы функций — определение находится в файле function.h.
* Содержит объекты (например, softWareSerial).

//functions.h
#include "globals.h"
*содержит определения функций

Однако мой код не будет работать. Я получаю огромный список ошибок, последняя из которых касается SoftwareSerialglobals.h)

'SoftwareSerial' does not name a type; did you mean 'SoftwareSerial_h'?

Действительна ли структура кода, которую я опубликовал ранее?

РЕДАКТИРОВАТЬ: Я спрашиваю здесь, а не, например, в StackOverflow, потому что у IDE есть свои особенности, например, сначала загружается .ino, а затем все остальные файлы в алфавитном порядке.

>

РЕДАКТИРОВАТЬ 2: я последовал совету пользователя Эдгара Бонета (спасибо). Я куда-то добрался, но теперь получаю ошибки несколько определений. Это псевдокод, который нужно показать для отображения структуры моего кода:

    //string_handling_functions.h
    HEADER_GUARD
    #include <SoftwareSerial.h> //используется позже. Если бы этот вызов произошел в основном .ino (даже до вызова этого заголовочного файла код не работал бы)
    #include <model_definitions.h> //используется в определении функции в файле .cpp
    byte clear_Buffer(bool, SoftwareSerial*);


    //string_handling_functions.cpp
    #include <Arduino.h>
    #include "string_handling_functions.h"
    FUNCTION DEFINITION GOES HERE



    //model_definitions.h
    HEADER_GUARD
    #defines
    SoftwareSerial object_definition
    Other Objects Definitions (libs called in main .ino file)
    const char example_var PROGMEM = {0};


    //main_ino.ino
    #include "string_handling_functions.h"

Сейчас я получаю такие ошибки:

\sketch\string_handling_functions.cpp.o (symbol from plugin): In function `GSM_Module_SW_Serial':
(.text+0x0): multiple definition of `GSM_Module_SW_Serial'
\sketch\main_code.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

Все ошибки представляют собой множественные определения. Жертвами этой ошибки являются объекты и массивы.

Несколько дополнительных вопросов:

  1. Все переменные в файлах .h должны быть extern?
  2. Могут ли они иметь значение в заголовочном файле? (например, значение внешнего символа = {"0"};)

Обновлено 3: После соблюдения всех правил мне пришлось создать множество файлов (.h и .cpp), чтобы сгруппировать все вместе. Это последние ошибки, которые я получаю, и они связаны с SoftwareSerial:

In file included from \sketch_general_vars.cpp:2:0:
model_definitions.h:498:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial Ter_1_SS_RXOnly (TER_1_RX_PIN, TER_1_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial
model_definitions.h:499:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial Ter_2_SS_RXOnly (TER_2_RX_PIN, TER_2_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial
model_definitions.h:536:1: error: 'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?
 SoftwareSerial GSM_Module_SW_Serial(GSM_RX_PIN, GSM_TX_PIN);
 ^~~~~~~~~~~~~~
 HardwareSerial

exit status 1
'SoftwareSerial' does not name a type; did you mean 'HardwareSerial'?

Когда я #include <SoftwareSerial.h> в этом файле, появляется очень много ошибок. Это игра в кошки-мышки.

Прежде всего, предыдущие вызовы объекта теперь вызывают множественные ошибки определений, например множественные определения GSM_Module_SW_Serial'`.

Во-вторых, я получаю бесчисленное количество таких сообщений (не только для READ_GPS): ino:1817: неопределенная ссылка на READ_GPS'`

READ_GPS определен в globals_non_model.h, определен в globals_non_model.cpp, включен в string_handling_functions.h . Затем это включается в основной ino (#include "string_handling_functions.h")

, 👍2

Обсуждение

Спасибо за сокращение вашего опубликованного кода. Но в этом случае обрезка может быть слишком сильной. Возможно, здесь достаточно информации, чтобы определить, что пошло не так. Но я этого не вижу. (Кроме того, большую часть времени я просматриваю и исправляю только первую ошибку компилятора. Часто это исправляет многие другие ошибки.), @st2000

@st2000 Спасибо за комментарий! Сначала я постараюсь распределить ошибки по категориям и обновлю вопрос., @user1584421

возможно, просто переименуйте файл Functions.h в Functions.ino и удалите строку #include "functions.h". https://arduino.github.io/arduino-cli/0.33/sketch-build-process/#pre-processing, @Juraj

Полагаю, что «SoftwareSerial» не называет тип; предполагает, что определение объекта SoftwareSerial было обнаружено компилятором до того, как он прочитал SoftwareSerial.h. См. ответ @edgarbonet, в частности, о разделении большого файла проекта на отдельно скомпилированные файлы. Кажется, что все ваши файлы по-прежнему компилируются за один прогон, что оставляет порядок компиляции вне вашего контроля. Это может привести к тому, что первое использование такого символа, как SoftwareSerial, появится перед его объявлением., @JRobert

Включение для SoftwareSerial должно быть помещено в файл .ino (основной файл проекта). Таким образом, он включен правильно. Пожалуйста, смотрите мой второй ответ., @Nick Gammon

Вот чистая модель разделения большого проекта Arduino на отдельные файлы .cpp и .h: https://forum.arduino.cc/t/how-to-properly-include-functions-writing-on-other-sketch. -табс/565672/4, @6v6gt


5 ответов


4

Это неправильный способ разделить большой файл .ino. Вместо этого вам следует мыслите в терминах функциональных единиц, т.е. программных «модулей», которые адресуют конкретные опасения. Например, если подмножество вашего кода предназначено для управлять гипердвигателем, то вы можете перенести его на выделенный модуль «гипердвигатель»:

// Hyperdrive.h — интерфейс модуля
* variable declarations (all extern, no initializers)
* function declarations (i.e. prototypes only)
* class definitions
// Hyperdrive.cpp — реализация модуля
#include <Arduino.h>     // необходимо, если вы используете API Arduino
#include "hyperdrive.h"  // для проверки совместимости с общедоступным интерфейсом
* определения переменных (без extern, с инициализаторами, если это необходимо)
* определения функций (с телами)
* определения методов класса
// main.ino
#include "hyperdrive.h"
* код основной программы

Изменить 1: ответ на ИЗМЕНЕНИЕ 2 вопроса.

Все ошибки представляют собой множественные определения

Обычно это происходит, когда вы определяете переменную в заголовочном файле (.h): переменная определяется столько раз, сколько включен этот заголовок где-то. Не следует этого делать. Единственное, что вам следует когда-либо в заголовке определяются типы, включая классы. Все else (функции и переменные) следует объявлять только в заголовке, и определено в сопроводительном файле .cpp.

Все переменные в файлах .h должны быть extern?

Да. Без ключевого слова extern у вас была бы переменная определение и определения переменных принадлежат соответствующему .cpp файлу. файл. Ключевое слово extern необходимо для записи переменной. объявление.

Могут ли [переменные] иметь значение в заголовочном файле?

Нет. Начальное значение принадлежит определению переменной, а не ее декларация.

Изменение 2: ответ на ИЗМЕНЕНИЕ 3 вопроса.

model_definitions.h:498:1: error: 'SoftwareSerial' does not name a type

Если файл заголовка ссылается на определенные типы или объекты в другом месте он должен иметь соответствующие строки #include. В этом случае вам нужно:

#include <SoftwareSerial.h>
multiple definition of `GSM_Module_SW_Serial'

Эта строка:

SoftwareSerial GSM_Module_SW_Serial(GSM_RX_PIN, GSM_TX_PIN);

— определение переменной. Рискую повториться: вам следует не помещать определения переменных в файлы заголовков. Заголовок должен содержать только декларация:

extern SoftwareSerial GSM_Module_SW_Serial;

тогда как определение должно находиться в соответствующем файле .cpp.

,

Спасибо! Я последовал твоему совету и определенно добился чего-то. Я получаю только ошибки «множественного определения». Я обновил свой вопрос в соответствии с моей текущей стратегией., @user1584421

Я запустил баунти, не увидев правок с вашей стороны (возможно, я видел их, но забыл?). Я начну воплощать в жизнь то, что вы сказали. Спасибо!, @user1584421

Я создал часть 3 в истории редактирования. Это похоже на игру в кошки-мышки., @user1584421

Спасибо. Вина моя, так как я не понял, что то же самое нужно делать и с определениями объектов. Остается только длинный список «неопределенных ссылок»…, @user1584421


2

У меня была такая же проблема. Это решение неверно, но я попробовал все типы решений и в конечном итоге столкнулся с той же проблемой. Я поместил извлеченный код в другой файл и пометил его как .ino в той же папке, что и исходный файл, и все заработало. Когда-нибудь они могут добавить .insert"имя файла" команда. В мире ассемблера он отлично работал.

,

«Когда-нибудь они, возможно, добавят команду .insert"имя_файла".» - Чем это будет отличаться от #include < ... >?, @JRobert

.insert вставит файл, в котором находится оператор. Это может быть код, что угодно, что было правильно отформатировано. Даже комментарии, если они правильно разграничены. Это было очень полезно во времена ассемблера., @Gil

#include то же самое, @Juraj

#include — это НЕ одно и то же. Он будет включать только определенные типы файлов. Файл с простым оператором печати не будет включен в точку вставки, он просто говорит, что не может его найти. " W_Gil_Base:220:10: фатальная ошибка: Test.gil: такого файла или каталога нет. #include "Test.gil" ^~~~~~~~~~ компиляция прекращена. статус выхода 1 Test.gil: нет такого файла или каталога «Он находится в том же каталоге, что и файл .ino., @Gil

Правильная работа #include — то же самое; Вы обнаружили ошибку компилятора (сообщили) в IDE v2.1.0 (может быть, во всех IDE 2.x?). Это работает, если вы укажете полный путь к включаемому файлу (в этом нет необходимости). Он работает для «распознанных» расширений файлов (в которых также нет необходимости. В IDE 1.8.19 директива #include работает правильно., @JRobert


3

Если вы разделите файл на несколько файлов .ino, перед компиляцией компилятор объединит их все в один длинный файл .ino. Сначала будет помещен тот, который соответствует названию проекта, а остальные в алфавитном порядке. Это может вызвать проблемы, если что-то будет использоваться в этом длинном файле до того, как оно будет определено.

Вы можете начинать имена файлов .ino с цифр или чего-то еще и контролировать порядок их включения, или вы можете переместить этот код в файлы .h и .cpp и включить его таким образом. Файлы заголовков будут включены в том порядке, в котором вы включили их в код.

,

сборщик Arduino добавляет предварительное объявление для функций и перемещает определения классов и структур в начало объединенного файла., @Juraj

Да, но не всегда все получается правильно. Я могу привести десятки примеров, которые не компилируются из-за того, что IDE что-то поставила не в том порядке. Это особенно происходит, если вы определяете структуру и функцию, которая либо возвращает этот тип, либо принимает его в качестве аргумента. Я видел много-много случаев, когда предварительное объявление помещалось перед определением структуры, и вы получали ошибку типа not name., @Delta_G

эта проблема не связана с несколькими файлами ino. это может случиться и с одним ino. и это единственная проблема с созданием форвардных объявлений. генерирование форвардных объявлений C++ — самое удобное, что делает для нас сборщик, @Juraj

Спасибо, но, как сказал Юрай, проблема сохраняется независимо от имен файлов., @user1584421

Да, но проблема НЕ возникает при использовании файлов .h и .cpp вместо файлов .ino. В этом случае включение происходит там, где вы указываете его включить, и все работает так, как можно было бы ожидать при использовании других инструментов., @Delta_G

Я использую файлы .h и .cpp. Однако проблемы до сих пор не решены., @user1584421

Я думаю, что мы с @Juraj говорили о проблеме другого типа., @Delta_G


1

Я переместил все глобальные переменные и функции в соответствующие файлы. Вот что происходит внутри:

Как сказал Delta_G, объединенный файл — это сначала основной файл (тот, который содержит имя проекта), за которым следуют другие файлы в алфавитном порядке. Это может привести к тому, что глобальные переменные будут объявлены после того, как они будут использованы в других файлах. Я бы оставил глобальные переменные в основном файле.

Однако, как сказал Edgar_Bonet в своем ответе, вероятно, было бы лучше сделать все модульным.


См. мой ответ Как IDE организует вещи - там это объясняется более подробно.

SoftwareSerial не называет тип; вы имели в виду «SoftwareSerial_h»?

Поэтому такие вещи, как включение библиотек, должны размещаться в основном файле - размещение их во вложенных файлах нарушает включение этих библиотек, отсюда и это сообщение об ошибке.

,

1

Файл .ino необходимо рассматривать как файл проекта. Просто укажите, что он включает в себя такие вещи, как Software Serial. Это заставляет IDE включать соответствующие библиотеки. Не вводите в него никакой другой код. Помещайте объявления в файлы .h, а определения (т. е. значения переменных и функции) в файлы .cpp.

Если вы делаете это таким образом, убедитесь, что вы соблюдаете обычное соглашение C и C++. Делайте предварительные объявления функций на случай, если вы попытаетесь использовать функцию до того, как будет найдено ее определение. Обычно они помещаются в файлы .h.

Несколько дополнительных вопросов:

Все переменные в файлах .h должны быть внешними?

Если вы не хотите множественных ошибок при объявлении, вам следует объявлять, но не определять переменные в ваших файлах .h. Так что да, они должны быть внешними.

Могут ли они иметь значение в заголовочном файле? (например, значение внешнего символа = {"0"};)

Нет, потому что, вероятно, вам придется включать его несколько раз. На самом деле вы не можете иметь значение для внешнего устройства, это не имеет смысла. Использование extern означает, что он определен где-то еще.


Поместите объявления в файлы .h, поставив перед ними extern. Затем они должны быть где-то определены (т. е. заданы значения). Вы можете создать файл .cpp специально для этой цели.

Вы прочитали ссылку, которую я дал? Это рассматривается более подробно.


//.ino
#include "globals.h"
#include "functions.h"

//globals.h
* Имеет защиту заголовка
*Все переменные здесь внешние
*Содержит прототипы функций — определение находится в файле function.h.
* Содержит объекты (например, softWareSerial).

Включения для SoftwareSerial необходимо поместить в файл .ino, потому что именно его сканирует IDE, чтобы увидеть, какие библиотеки можно связать с вашим проектом.

Просто поместите библиотеки (включая их) в файл .ino, и ничего больше.


Защитники заголовков просто предотвращают включение нескольких копий одного и того же файла .h в один и тот же модуль компиляции. Они не препятствуют включению нескольких файлов .h в разные единицы компиляции (несколько файлов .cpp). Так что здесь они, возможно, многого не добьются.

,

Спасибо! Я использую правильный формат, однако ошибки остаются. У меня есть файлы .h и эквивалентные им файлы cpp с определениями функций и переменных. Однако я получаю такие ошибки, как «неопределенная ссылка на «Battery_level» в основном файле «.ino». Эта переменная (например, одна из многих переменных) объявлена в файле .h и определена в файле .cpp. Заголовок включен в другой заголовок, и этот еще один заголовок включен в основной файл ino. Что странно, так это то, что другие переменные в файле header/cpp в порядке. Затрагиваются только некоторые переменные. Есть два заголовка, переменные которых выдают ошибки в основном файле ino., @user1584421

И даже если включить эти файлы заголовков непосредственно в основной файл ino (как я уже сказал, они включены в другой заголовок, который затем включается в файл ino), проблемы все равно останутся и будут точно такими же., @user1584421

Пожалуйста, создайте [Минимальный воспроизводимый пример](https://stackoverflow.com/help/minimal-reproducible-example) этой проблемы. Просто создайте два или три (небольших) файла, воспроизводящих проблему, и ничего больше, и опубликуйте их полное содержимое здесь., @Nick Gammon

@user1584421 user1584421 В процессе создания этого MRE вы, вероятно, достигнете точки, когда перед выполнением X он компилируется, а после выполнения X вы получаете сообщения об ошибках. Тогда вы увидите, что проблема в X. Если вы не понимаете, **почему** X является проблемой, мы можем вам помочь., @Nick Gammon