Tool1CD Beta в деле

Пс, парень! Не хочешь немного сырых байтов?

Для тех, кто на минуту хочет забыть про средиземье, эффективных манагеров и прочие эфемерные сопли про джаваскрипт — Back to the roots! К дикому коду, сырым данным и хардкорному C++.

Предлагаю вашему внимаю краткое пошаговое руководство по подключению библиотеки tool1cd к 1С через внешнюю компоненту. Предполагается, что вы уже умеете разрабатывать внешние компоненты по типу Native, разбираетесь в C++, умеете готовить CMake, а слова «собрать Boost» вызывают у вас приступ иронии и боли, примерно так:

грустный Гарольд за ноутбуком

Приведённый в статье пример разрабатывался в следующих условиях:

  • Версия платформы: 8.3.10.2667 х32
  • Под Windows 10 Home: Microsoft Visual Studio 2024 Community Edition с модулем CMake
  • Под Ubuntu 16.04: CLion 2024.3 (встроенная поддержка CMake), g++ 5.4
  • Boost: 1.65
  • ZLIB: 1.2.8

Пара слов про ограничения выше. Версия платформы значения не имеет: пример будет работать под 8.2 и 8.3 любых версий. Среда разработки может быть любая, лишь бы поддерживала формат проектов CMake: CLion, QtCreator, консоль. Буст: версия 1.66 и выше поддерживается только в CMake начиная с версии 3.11.0, поэтому проверьте версию. Поддержка CMake 3.11 в Visual Studio появилась буквально на днях в версии 15.7 — обновитесь. Если нет возможности обновиться, используйте Буст версии 1.65 и ниже.

Часть 0. Подготовка каркаса

В качестве подготовки надо скачать ZLib и Boost указанных версий и собрать (вы ведь умеете это делать, да?). Буст должен быть собран с параметрами “link=static runtime-link=static”. Все мы помним статью на ИТС и её совет включать зависимости статически? Вот это как-раз для этого.

Собрали? Теперь переходим к вкусному. Создадим компоненту, которая будет делать нечто невообразимое простейшее – получение сведений о конфигурации из файла 1CD: создадим функцию ПолучитьДанныеОФайле / GetFileInfo, которая на вход получает строку – путь к файлу, а на выходе отдаёт строку с описанием конфигурации или число с кодом ошибки (как упрощение).

Итак, скачиваем с ИТС архив VNCOMPS.ZIP, распаковываем, заходим в каталог examples (не template) и вашим любимым гит-клиентом клонируем в этот каталог URL https://github.com/e8tools/tool1cd.git .

Запускаем студию и открываем CMake-проект в каталоге examples. Укажем необходимые параметры для CMake (CMake – Изменить параметры CMake – AddIn):

  1. Нам нужна конфигурация x86-Release
  2. Убедимся, что inheritEnvironments = msvc_x86
  3. Убедимся, что configurationType установлен во что угодно помимо Debug
  4. В разделе variables необходимо указать настройки:

TARGET_PLATFORM_32

YES

BOOST_ROOT

Корневой каталог, где лежит Буст

BOOST_LIBRARYDIR

В Бусте подкаталог stagelib

BOOST_INCLUDEDIR

Корневой каталог Буста

ZLIB_LIBRARY

Полный путь к библиотеке zlibstatic.lib

ZLIB_INCLUDE_DIR

Корневой каталог zlib

NOGUI

YES

Boost_USE_STATIC_LIBS

YES

Boost_USE_STATIC_RUNTIME

YES

 

Теперь подключим библиотеку tool1cd к нашему CMake-проекту. Открываем CMakeLists.txt в корне и после условия if (TARGET_PLATFORM_32) … endif() дописываем немножко волшебства:

if (MSVC)
    foreach (flag_var
                     CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
                     CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
 
              if (${flag_var} MATCHES "/MD")
                     string (REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
              endif (${flag_var} MATCHES "/MD")
 
       endforeach(flag_var)
       set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:MSVCRT")
endif()

Коротко опишу, что здесь мы подменяем сборку с динамическим рантаймом на сборку со статическим (про статическое включение зависимостей помним, да?).

Потом убираем из сборки проекты AddInChrome и AddInIE, чтобы они сейчас нам не мешали, и в самом конце дописываем заветную строчку:

add_subdirectory(tool1cd/src/tool1cd)

Тем самым мы включаем в свой проект исходные коды библиотеки tool1cd и будем их собирать вместе с компонентой. Дико? Дико. Но на данном этапе жизни проекта tool1cd – это самый безболезненный способ избежать подводных камней.

Теперь открываем CMakeLists.txt в каталоге NativeAPI. Там нам надо подключить Буст и tool1cd, дописываем в конце:

find_package (Boost 1.53 REQUIRED COMPONENTS filesystem)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries (${PROJECT_NAME} ${Boost_LIBRARIES})
include_directories(${CMAKE_SOURCE_DIR}/tool1cd/src/tool1cd)
target_link_libraries(${PROJECT_NAME} tool1cd)

а также после SET(AddInNative_SRC) дописать:

if (MSVC)
       set (AddInNative_SRC ${AddInNative_SRC} AddInNative.def)
endif()

Запускаем сборку (CMake – Собрать всё), убеждаемся, что нет ошибок, и идём дальше.

 

Часть 1. Создаём полезную нагрузку

Добавим новую функцию: добавить в enum Methods, g_MethodNames, g_MethodNamesRu, GetNParams, HasRetVal – это всё за рамками статьи.

Добавим в класс CAddInNative функцию GetFileInfo с сигнатурой, совпадающей с CallAsFunc (копипастим объявление). В коде функции CallAsFunc допишем вызов новой функции:

case eMethGetFileInfo:
{
try {
return GetFileInfo(lMethodNum, pvarRetValue, paParams, lSizeArray);
}
catch (std::exception &exc) {
// TODO: сделать addError
TV_VT(pvarRetValue) = VTYPE_I4;
TV_I4(pvarRetValue) = 1;
}
return true;
}

Перенаправляем вызов в новую функцию и ловим все исключения, создавая код возврата 1.

Перейдём к реализации функции GetFileInfo. Вкратце, Алгоритм действий:

      1. Открываем файл с базой
      2. Находим таблицу CONFIG
      3. В таблице CONFIG находим запись с FILENAME=”root”, считываем BINARYDATA
      4. Извлекаем из BINARYDARA гуид конфигурации
      5. В таблице CONFIG находим запись с FILENAME=гуид_конфигурации, считываем BINARYDATA
      6. Извлекаем из BINARYDATA данные о конфигурации

 

Подключим заголовочные файлы:


#include <boost/filesystem.hpp>
#include <Class_1CD.h>
#include <TableIterator.h>

Чтобы открыть файл базы, нам нужно извлечь путь к базе из параметра.  Возьмём за образец готовый кусок кода из eMethLoadPicture и немножко доработаем:

boost::filesystem::path filepath;
// переделанный кусок кода из eMethLoadPicture
{
if (!lSizeArray || !paParams)
return false;

switch (TV_VT(paParams))
{
case VTYPE_PSTR:
filepath = boost::filesystem::path(std::string(paParams->pstrVal));
break;
case VTYPE_PWSTR:
{
wchar_t *wsTmp = nullptr;
::convFromShortWchar(&wsTmp, TV_WSTR(paParams));
filepath = boost::filesystem::path(std::wstring(wsTmp));
delete[] wsTmp;
break;
}
default:
return false;
}
}

// теперь в filepath у нас путь к файлу.

Открываем базу:

// заранее заготовим тип Число для возврата ошибки
TV_VT(pvarRetValue) = VTYPE_I4;

// теперь в filepath у нас путь к файлу.
if (!boost::filesystem::exists(filepath)) {
TV_I4(pvarRetValue) = 2;
return true;
}

T_1CD db(filepath, nullptr, false);
if (!(db.is_open() && db.is_infobase())) {
TV_I4(pvarRetValue) = 3;
return true;
}

 Находим таблицу CONFIG:

// находим таблицу CONFIG
Table *cfg = nullptr;
{
for (int i = 0; i < db.get_numtables(); i++) {
Table *tbl = db.get_table(i);
if (System::EqualIC(tbl->get_name(), "Config")) {
cfg = tbl;
break;
}
}
if (cfg == nullptr) {
// не нашли :(
TV_I4(pvarRetValue) = 4;
return true;
}
}

Находим root, считываем BINARYDATA и разбираем ГУИД конфигурации:

std::string guid;
{
// находим root
TableIterator it(cfg);
while (!it.eof()) {
std::string filename = it.current().get_string("FILENAME");
if (!System::EqualIC(filename, "root")) {
it.next();
continue;
}

// нашли - считываем данные

Field *f_BinaryData = cfg->get_field("BINARYDATA");
System::Classes::TStream *data;
if (!it.current().try_store_blob_data(f_BinaryData, data, true)) {
TV_I4(pvarRetValue) = 4;
return true;
}

// поток data содержит текстовое представление дерева (скобочные данные)
data->Seek(3, soFromBeginning); // пропускаем первые 3 байта - BOM

// извлекаем гуид
std::unique_ptr<Tree> tree = parse_1Cstream(data, "");
guid = (*tree)[0][1].get_value();
break;
}

if (guid.empty()) {
TV_I4(pvarRetValue) = 5;
return true;
}
}

 Последний шаг, ищем файл с описанием конфигурации и извлекаем нужные данные:

{
TableIterator it(cfg);
while (!it.eof()) {
std::string filename = it.current().get_string("FILENAME");
if (!System::EqualIC(filename, guid)) {
it.next();
continue;
}

// нашли нужный файл, извлекаем данные
Field *f_BinaryData = cfg->get_field("BINARYDATA");
System::Classes::TStream *data;
if (!it.current().try_store_blob_data(f_BinaryData, data, true)) {
TV_I4(pvarRetValue) = 6;
return true;
}

// поток data содержит текстовое представление дерева (скобочные данные)
data->Seek(3, soFromBeginning); // пропускаем первые 3 байта - BOM

std::unique_ptr<Tree> tree = parse_1Cstream(data, "");

// цепочки цифр вычислены опытным путём
// широкомасштабного обследования конфигураций не проводилось
std::string config_name = (*tree)[0][3][1][1][1][1][2].get_value();
std::string config_ver = (*tree)[0][3][1][1][15].get_value();

// тут мы получили нужный нам итог в кодировке utf-8
std::string utf8result = config_name + ", " + config_ver;

// во имя кроссплатформенности, нужно перевести его в utf-16
std::vector<uint8_t> result = System::SysUtils::TEncoding::Unicode->fromUtf8(utf8result);

m_iMemory->AllocMemory((void**)&pvarRetValue->pwstrVal, result.size());
memcpy((void*)pvarRetValue->pwstrVal, result.data(), result.size());

// меняем результат на Строка и успешно завершаем работу
TV_VT(pvarRetValue) = VTYPE_PWSTR;
pvarRetValue->wstrLen = result.size() / sizeof(WCHAR_T);

return true;
}
}

// сюда доходим в случае, если не нашли файл метаданных конфигурации
TV_I4(pvarRetValue) = 7;
return true;

На этом наша функция готова.

Тестовый код для 1С:

ПутьККомпоненте = "C:Usersdmpas...RelWithDebInfoAddInNativeWin32.dll";

Если Не ПодключитьВнешнююКомпоненту(ПутьККомпоненте, "CDReader", ТипВнешнейКомпоненты.Native) Тогда

Сообщить("Ошибочка");
Возврат;

КонецЕсли;


ФайлБазы = "C:1C...1Cv8.1CD";
Читалка = Новый("AddIn.CDReader.AddInNativeExtension");
Сообщить("GFI = " + ЗначениеВСтрокуВнутр(Читалка.ПолучитьДанныеОФайле(ФайлБазы)));

Если что-то не заработало, внимательно читаем ещё раз и задаём вопросы в комментариях.

 

Часть 2. Что дальше?

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

Год назад, в апреле 2024, автор проекта Валерий Агеев, опубликовал исходный код своей программы, что должно было стать толчком к её дальнейшему развитию. Для того, чтобы привлечь к разработке больше заинтересованных разработчиков, исходный код должен соответствовать определённым требованиям, некоему «духу опенсурса». Для программ на C++ под этим обычно подразумевают свежий стандарт языка, поддержка разных компиляторов и переносимость на другие платформы. С учётом всех этих факторов исходный формат проекта – C++ Builder – совершенно нас не устраивал и мы, закатав рукава, взялись за адаптацию. Вот теперь, спустя год, мы можем похвастаться:

  • Проект отвязан от C++ Builder и от Windows. Отныне можно работать также под Ubuntu и MacOS, используя любую среду разработки, которая поддерживает CMake.
  • Основной функционал выделен в библиотеку. Что как-раз представлено в статье: есть библиотека tool1cd, а к ней уже можно прикручивать свои оболочки.
  • Настроена инфраструктура проекта. Сборка, тестирование, поддержка через гиттер и гитхаб

 

Соответственно, что дальше — очевидно. Клонируйте проект, дорабатывайте функционал, присылайте ваши доработки!

Сейчас проект в статусе Беты, нам крайне важны ваша помощь и обратная связь.

26 Comments

  1. Evil Beaver

    Эфемерные сопли про джаваскрипт!!!

    Начало потрясное просто.

    Позволю пару слов уточнения для тех, кто не понял что это было… Знаменитая утилита tool1cd Валерия Агеева (R.I.P.) для работы с файловыми базами 1С, в том числе для их спасения, когда они не открываются ничем кроме tool1cd — теперь доступна в виде исходных кодов на c++, доступна на linux и macos (вероятно)

    Теперь если вам нужно прочесть файловую базу 1С в вашем приложении на плюсах (или любом другом языке, который умеет делать вызовы в нативный код, т.е. практически любом языке) — вы можете взять этот проект и использовать.

    Спасибо ребята!

    Reply
  2. Infactum
    Предполагается, что вы уже умеете разрабатывать внешние компоненты по типу Native, разбираетесь в C++, умеете готовить CMake, а слова «собрать Boost» вызывают у вас приступ иронии и боли

    .. и вы почему-то работаете «программистом 1С»

    Понимаю, что статья писалась в основном, чтобы рассказать об успех в наведении порядка в кодовой базе tool1CD, но местная публика, очевидно, ждет готовые бинарники графической утилиты, либо собранную native API обвязку.

    Reply
  3. baton_pk

    (2) таки пока мы в полной бете, то из местной публики хочется выцепить разрабов-плюсовиков — кто готов мириться с неидеальностью бытия. Как выйдем из беты — там будут и бинари, и нескучные обои.

    Reply
  4. artbear

    Очень круто!

    Вышли из тени наконец-то 🙂 ?

    Reply
  5. fishca

    Плюсиков маловато пока…

    Reply
  6. baton_pk

    (5) ты погоди, скоро начнут рассказывать, что это не нужно и что мы фигнёй занимаемся 🙂

    Reply
  7. bulpi

    (6)

    Это очень нужно. Памятник при жизни из чистого золота в полный рост 🙂

    Reply
  8. baton_pk

    (7)

    Памятник при жизни из чистого золота в полный рост

    боюсь, немного не успели.

    Reply
  9. Evil Beaver

    (3) а GUI-бинарники в виде беты может тоже выложить? Пусть фидбек хотя бы люди дадут, ошибки на ГХ накидают…

    Эгеей!! Сотрудники фирмы 1С! Мы знаем, что вы читаете инфостарт, и что вы плюсовики. Даешь контрибьютинг инкогнито! #ДиджиталРезистанс! 🙂 🙂

    Reply
  10. baton_pk

    (9) виндовые гуй-бинари есть на гитхабе: https://github.com/e8tools/tool1cd/releases

    На инфостарт я их побоялся выкладывать — тут уже один раз баннили такое.

    Reply
  11. baton_pk

    (9)

    Даешь контрибьютинг инкогнито!

    эмм… чую, первым пулл-реквестом будет проверка на наличие лицензии 🙂

    Reply
  12. Evil Beaver

    (11) Ну докажут свою троллепригодность, как минимум. Уже хорошо

    Reply
  13. baton_pk

    (12)

    докажут свою троллепригодность

    вот когда появится конфа «1С:Восстановление файловых баз», вот тогда это будет истинное 1С:Трололо.

    Reply
  14. Evil Beaver

    (13) если там будет внешняя компонента из статьи, то да

    Reply
  15. vadim1011985

    (3) а с каким форматом баз работает компонента ? 8.3.8 поддерживает ?

    Reply
  16. baton_pk

    (15)

    8.3.8 поддерживает ?

    да, поддерживает. От 8.2.14 (форматы ниже не проверял, может и работают) до 8.3.8 (с ним могут быть косяки, но пока они не встречались).

    Reply
  17. vadim1011985

    (16) не редко приходится восстанавливать файловые базы , для этого использую связку Tool1d , и библиотеку 1сd_lib ( тоже внешняя компонента так же открывает базу и читает данные + там организована взаимосвязь между метаданными базы и Таблицами, так же в отличии от tools1cd позволят удалять сразу несколько таблиц) , но работает только со старым форматом , что не очень удобно. Есть один алгоритм восстановления базы от ошибки «ошибка формата потока» который хотелось автоматизировать , но так как я не очень знаком с C++ хотелось бы узнать возможно ли с помощью вашей компоненты на уровне 1с осуществить удалять таблицы А так же импортировать и Экспортировать данные таблиц целиком ?

    Reply
  18. baton_pk

    (17)

    возможно ли с помощью вашей компоненты

    с помощью компоненты, что в статье — нет. это просто пример использования библиотеки. Библиотека, да, имеет функционал экспорта/импорта таблиц, их удаления — эти возможности надо ещё проверить и обкатать, потому сейчас я их не показываю. Опишите словами алгоритм и я, возможно, попробую его воспроизвести в одной из следующих статей.

    Reply
  19. vadim1011985

    (18) алгоритм таков

    1) берётся чистая база того же релиза что и поврежденная , из неё удаляются все таблицы данных кроме служебных , за исключением таблиц Params , и DBSHEMA( которые будут перенесены из служебной базы ) имена служебных таблиц известны , желательно перед удалением экспортировать таблицы в виде файлов в каталог ( в Tools1cd кнопка экспорт таблиц данных )

    2) из повреждённой базы выгружаются только таблицы данных , + 2 служебные таблицы Params и DBShema (тоже в виде файлов)

    3) далее в чистую базу грузятся сначала служебные таблицы , а потом таблицы данных ( в tools кнопка — Импорт и создание таблиц)

    В принципе все , единственный момент , что таблицы данных могут быть битыми ( например недавно столкнулся , в одной из таблиц файл BLOB весил 300 МБ ) , и при загрузке Tools валился с ошибкой , и надо было загружать таблицы по несколько шт. что бы отловить на какой происходит ошибка. А для исправления ситуации приходилось искать такую же таблицу из выгруженные таблиц чистой базы , и загружать ее с подменой файла описания таблицы descr из повреждённой базы ( т.е . Грузилась чистая таблица ) .

    Надеюсь , что описал понятно

    Reply
  20. baton_pk

    (19) да, спасибо. Попробую воспроизвести этот сценарий.

    Reply
  21. vadim1011985

    (20) И Вам спасибо , надеюсь получится

    Reply
  22. user811063

    Вот это действительно стоящая статья!!! Побольше бы таких на данном ресурсе!!!

    Reply
  23. baton_pk

    (22) Спасибо! Рад, что понравилось.

    Reply
  24. Teopemuk

    (8)

    Прошу прощения, но что значит «не успели»?

    Reply
  25. nomadon

    «`

    if (MSVC)

    set (AddInNative_SRC ${AddInNative_SRC} AddInNative.def)

    endif()

    «`

    В шаблоне не указано подключение дефа? а как тогда dll работает? или шаблон предназначен не для CMake, он просто рядом что ли?

    Reply
  26. baton_pk

    (25)

    дключение дефа? а как тогда

    В шаблоне для DLL, насколько я помню, предполагается делать по-старинке — через проект Студии. Там всё прописано.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *