Если вы разрабатываете на C/C++ какое-либо ПО для операторов (администраторов) больниц, магазинов, сервисов проката гироскутеров, ремонта сотовых телефонов, то наверняка сталкивались с задачей создания отчетов, чтобы печатать их на принтере, ну или хотя бы в PDF. Существует множество сторонних пакетов для Embarcadero RAD Studio, которые позволяют это делать. Такие как FastReport, QuickReport, Crystal Reports и т.д. Но на мой взгляд все эти пакеты требуют глубокого вникания в свой механизм и интерфейс. Много времени уходит на их изучение, и что самое главное, код для генерирования даже самого простого отчета будет состоять из огромного числа строк. Когда я впервые столкнулся с задачей генерирования отчетов под C/C++, то начал с FastReport и понял, что этот инструмент мне абсолютно не нравится.

В этот самый момент в голову пришла замечательная мысль: как круто бы было взять простой EXCEL-файл, добавить в него статическую информацию и отформатировать под свои нужды. В программе останется только открыть этот файл, наполнить его динамическими данными и сохранить или отправить на печать! Это послужило отправной точкой к моему изучению OLE механизма работы с файлами MS Office из программ, разрабатываемых в Embarcadero RAD Studio.

По ходу деятельности удалось достаточно глубоко вникнуть в тему и сейчас могу с уверенностью сказать, что все те инструменты, которые предоставляет MS Office и OLE, перекрывают все мои потребности по созданию отчетов. Ну а раз так, то наверно и для других разработчиков это был бы самодостаточный, простой и удобный инструмент. Поэтому было принято решение написать DLL и упаковать в него перечень всех часто используемых функций MS Excel, с которыми приходится сталкиваться по ходу создания документов Excel. Это очень удобно, ведь для того, чтобы создать отчет, не нужно изучать кучу мануалов или OLE. Всё, что требуется, это только подгрузить DLL и входящие в нее функции. Ну а с DLL работать умеют многие.

Вот сайт самого проекта.

На сайте проекта достаточно подробно расписан механизм работы с DLL, есть пример и описание всех функций.

DLL поставляется без заголовочного файла и без статической LIB библиотеки. Таким образом, подключать DLL необходимо динамически с использованием функции LoadLibrary. ZIP архив с библиотекой включает следующие файлы:

  • "light_report.dll" — собственно сама DLL библиотека;
  • "DLLTest.cpp" — пример использования библиотеки;
  • "LPDLL.h" — заголовочный файл для примера использования библиотеки (это не заголовочный файл DLL);
  • "Report.xlsx" — документ MS EXCEL для примера использования библиотеки.

Заголовочный файл для примера использования библиотеки "LPDLL.h" содержит следующие объявления:

  • перечисления, которые используются в качестве аргументов функций DLL;
  • типы функций DLL;
  • экземпляры функций DLL;
  • дескриптор загружаемой библиотеки DLL ("HINSTANCE DLL_Handle;");
  • функция LoadLightReportDLL, которая динамически загружает DLL и все входящие в нее функции;
  • функция FreeLightReportDLL, которая выгружает DLL.

Файл «LPDLL.h» уже содержит всё необходимое для работы с DLL и ее функциями. Но вы также можете его отредактировать или взять только самое необходимое для вашего проекта.

Представим, что вы просто подключили к вашему проекту заголовочный файл «LPDLL.h».

Тогда в самом проекте вам просто необходимо:

1) Объявить переменную дескриптор вашего отчета:

Variant report;

?2) Динамически загрузить DLL и все ее функции:

if(!LoadLightReportDLL("C:\\LightReport\\light_report.dll"))return;

3) Далее в конструкции try-catch открыть заранее подготовленный шаблон отчета (файл MS Excel):

report=OpenReport("C:\\LightReport\\Report.xlsx",0);

4) Добавить в отчет какие-нибудь данные:

WriteCell(report, "Sheet1", 9, 1, "Hello world!");


5) Сохранить файл или напечатать:

Save(report);
SaveAs(report, "C:\\LightReport\\Report copy.xlsx");
ExportToPDF(report, "C:\\LightReport\\Report.pdf", false);
PrintOut(report);

6) Закрыть файл:

CloseReport(report);

7) Выгрузить DLL библиотеку:

FreeLightReportDLL();

И всё! Абсолютно ничего лишнего! Есть лишь один нюанс, но и то, он проявляется только при отладке. В работающем приложении всё будет работать хорошо. Я имею ввиду ситуацию, при которой возникает какая-либо ошибка при работе с отчетом. Дело в том, что OLE механизм работы с документами предполагает только выброс исключений при возникновении ошибок. Вот почему при работе с функциями DLL необходимо применять конструкцию try-catch. В блоке catch необходимо без сохранения закрывать отчет:

catch(...){CloseReport(report);}
.
При отладке, когда возникает ошибка, вы можете остановить работу программы. При этом процесс MS Excel останется запущенным, и закрыть его можно будет только через диспетчер задач. Поэтому при многократной отладке приложения может быть запущено несколько экземпляров процесса MS Excel в зависимости от того, как часто вы приостанавливали работу программы при возникновении ошибки, не дожидаясь выполнения кода в catch. За этим надо следить.

В релизе же, при возникновении ошибки обязательно сработает код, указанный в блоке catch, отчет закроется и процесс MS Excel будет завершен. Никаких висящих в системе процессов MS Excel наблюдаться не будет. Но все же стараются писать безошибочный код, поэтому надеюсь у вас не возникнет такая ситуация в работающем приложении.

Напоследок необходимо добавить:

  1. В DLL применяются платформонезависимые типы данных, такие как, unsigned short, unsigned long и char. Это понятно почему.
  2. Пример написан в среде Embarcadero Builder C++ 10. Соответственно, весь код соотносится с этой средой и, возможно, вам необходимо будет внести кое-какие изменения в код примера, чтобы всё заработало в вашем окружении.
  3. Узким местом этой DLL является использование дескритора файла отчета в формате Variant. Это довольно специфический формат и, подозреваю, что могут возникнуть трудности с использованием библиотеки вне Embarcadero RAD Studio. Честно, не проверял.

Поэтому на будущее планируется упаковать весь функционал в класс и скрыть этот формат Variant внутри класса, чтобы наружу пользователю предоставлялись только общепринятые форматы C/C++. Не пробовал ни разу упаковывать классы в DLL, читал, что с этим обязательно возникнут трудности. Тем не менее будем разбираться! А пока, спасибо за внимание, буду очень рад, если эта статья и DLL кому-то помогут.

Ссылка на проект.

Комментарии (16)


  1. 0xf0a00
    14.06.2019 15:09

    Когда я впервые столкнулся с задачей генерирования отчетов под C/C++, то начал с FastReport и понял, что этот инструмент мне абсолютно не нравится.

    А что именно вас отпугнуло в FastReport? Я испробовал много разных генераторов отчетов, но круче чем FastReport ничего нету по моему мнению. Встроенные скрипты, подотчеты, датасеты пользовательского типа… шик просто.



    1. lightreport Автор
      17.06.2019 11:39

      FastReport гибкий инструмент, и вследствие этого избыточен для большинства задач.


      1. berez
        18.06.2019 00:34

        Следуя вашей логике, LightReport подходит для большинства задач ввиду своей потрясающей негибкости. :)


  1. berez
    14.06.2019 15:55
    +2

    В DLL применяются платформонезависимые типы данных, такие как, unsigned short, unsigned long и char. Это понятно почему.

    Мне вот не очень понятно. У вас библиотека явно не заточена под использование в линуксе или на маке. Про 64-битную версию тоже ни слова. Ну и зачем?


    1. StriganovSergey
      14.06.2019 18:36
      +1

      Учится программировать человек. Вот и весь смысл. Видимо, нас ждут ещё откровения :)


  1. Emulyator
    14.06.2019 16:14

    Я так понял, что требуется наличие MS Exel для работы такой системы? Это сильно снижает круг потенциальных клиентов, хотя кого-то выручит при определенных условиях.


  1. savostin
    14.06.2019 17:36

    DLL поставляется без заголовочного файла и без статической LIB библиотеки

    Ну, с .lib еще понятно, а .h чего зажали-то?


    1. mapron
      15.06.2019 04:01

      И без исходников. Т.е. проект закрытый. Таким образом, это вроде как нарушает правила Хабра о рекламе своих проектов, они должны быть в «я пиарюсь», или я не прав?


  1. sidorovmax
    14.06.2019 18:04

    Может сделать генерацию отчета напрямую в шаблонный .xlsx-файл?


  1. skymal4ik
    14.06.2019 18:46

    Я для репортов поднял простенький веб сервер на php и bootstrap.

    Заполняешь нужные поля (js проверяет валидность и считает), жмешь сабмит. Php копирует template.tex в новый файл, меняет в нем данные на данные из формы и вызывает tex2 pdf, и перенаправляет на готовый pdf.
    По желанию сразу шлет емейл клиенту.

    Кросплатформенно, доступно из браузера откуда угодно и хранится вся история отчётов. Теплейт отчёта легко изменить через ssh и git.

    Но да, согласен, решение не очень гибкое в плане изменения лейаута документа.


    1. pvsur
      14.06.2019 19:16

      Помнится, году так в 2005 компонента cxGrid для bcb6 умела прекрасно рисовать себя в табличку екселеву без самого екселя, и даже не использовала xml для этого. Со всеми форматированиями и цветами. И ооооочень быстро :)


  1. Playa
    15.06.2019 01:13

    А почему готовые библиотеки не рассматривали? xlnt например.
    https://github.com/tfussell/xlnt


  1. mapron
    15.06.2019 04:17

    Автор, уберите статью в черновики, не позорьтесь.
    1) Библиотека ничего не предлагает сверх того, что предлагает весьма обширный OLE интерфейс Excel. По сути, это минималистичная обвязка над этим самым API
    2) Эта обвязка собрана под одну конкретную платформу. Исходники закрыты. Использовать под 64 бита нет возможности.
    3) Хидер файл не компилябельный, не определен класс String
    4) пример в архиве не компилябельный. НЕ хватает хидеров как минимум. Плюс это издевательски — в пример для своей библиотеки включать абсолютные пути.
    5) Библиотека без этого самого более обширного API OLE неюзабельна
    Ладно, черт с ними с закрытыми исходниками (я так понимаю, там на VCL все сейчас?). Теперь по самому решению:
    6) вы в начале статьи описываете использование шаблона xls файла. При этом судя по коду, никакой шаблонизации как таковой нет — файл просто используется как скелет, ты должен сам ручками рассчитывать местоположение данных. Опять же, 0 преимущество перед использованием OLE самому
    7) C++ API без намека на RAII, в C стиле? серьезно? либо трусы, либо крестик: либо обзывайте свою обвязку чисто сишной, и примеры соотстветсттвующие, либо хотя бы какое-то подобие ООП с RAII.
    8) Согласованность API тоже вызывает вопросы. Какого черта куча однобразных параметров везде, там какой-то struct CellRange напрашивается с ходу.

    В общем, выше уже мягко намекнули, что это «поделие» не уровень Хабра, не знаю чего там НЛО расщедрилось)


  1. Wolf4D
    15.06.2019 23:24
    +1

    Для этого в Qt ещё с версии 4.6.4 есть готовый компонент. Кроссплатформенный. Открытый. Функционалом не блещет, зато бесплатен, без зависимостей, и базовые вещи делает как надо. Использовали в двух проектах, не жалеем.


  1. raiSadam
    16.06.2019 17:51

    Зачастую в проекте нужен не просто механизм создания отчётов, а ещё и механизм создания отчётов из шаблонов, да и чтобы в редактируемом виде, так что ход мысли в статье верный. Был такой проект docxfactory, кроссплатформенный на c++, к сожалению заглох. Я его форкнул _https://github.com/bas524/Docxfactory, сделал сборку через cmake. Собирать сложно, но можно. Поддерживаются шаблоны в формате docx, используются только открытые библиотеки, работает в продакшене под Win и Linux.