Tool1CD Beta в деле

Программирование - Внешние компоненты

tool_1cd tool1cd C++ 1cd файловые базы

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

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

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

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

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

  • Версия платформы: 8.3.10.2667 х32
  • Под Windows 10 Home: Microsoft Visual Studio 2017 Community Edition с модулем CMake
  • Под Ubuntu 16.04: CLion 2017.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

В Бусте подкаталог stage\lib

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:\Users\dmpas\...\RelWithDebInfo\AddInNativeWin32.dll";

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

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

КонецЕсли;


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

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

 

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

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

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

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

 

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

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

89

Скачать файлы

Наименование Файл Версия Размер
AddInNativeWin32.dll
.dll 1,89Mb
08.05.18
4
.dll 1,89Mb 4 Скачать
VNCOMP83-tool1cd.zip
.zip 800,31Kb
08.05.18
13
.zip 800,31Kb 13 Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. Evil Beaver 5374 09.05.18 22:47 Сейчас в теме
Эфемерные сопли про джаваскрипт!!!

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

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

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

Спасибо ребята!
Gureev; Uncore; nytlenc; fav@2ncom.ru; nporrep; Terve!R; Bukaska; kuntashov; корум; RegrZ; amon_ra; jif; rpgshnik; nakh72; yukon; fishca; artbear; Йожкин Кот; ktb; pythonchik; 1ceo_2015; baton_pk; awk; JohnyDeath; nixel; karpik666; TIS_08; vikad; +28 Ответить
2. Infactum 268 10.05.18 12:53 Сейчас в теме
Предполагается, что вы уже умеете разрабатывать внешние компоненты по типу Native, разбираетесь в C++, умеете готовить CMake, а слова «собрать Boost» вызывают у вас приступ иронии и боли

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

Понимаю, что статья писалась в основном, чтобы рассказать об успех в наведении порядка в кодовой базе tool1CD, но местная публика, очевидно, ждет готовые бинарники графической утилиты, либо собранную native API обвязку.
Magister; klinval; RegrZ; +3 Ответить
3. baton_pk 376 10.05.18 13:05 Сейчас в теме
(2) таки пока мы в полной бете, то из местной публики хочется выцепить разрабов-плюсовиков - кто готов мириться с неидеальностью бытия. Как выйдем из беты - там будут и бинари, и нескучные обои.
bulpi; artbear; +2 Ответить
9. Evil Beaver 5374 11.05.18 09:09 Сейчас в теме
(3) а GUI-бинарники в виде беты может тоже выложить? Пусть фидбек хотя бы люди дадут, ошибки на ГХ накидают...

Эгеей!! Сотрудники фирмы 1С! Мы знаем, что вы читаете инфостарт, и что вы плюсовики. Даешь контрибьютинг инкогнито! #ДиджиталРезистанс! :) :)
корум; JohnyDeath; +2 Ответить
10. baton_pk 376 11.05.18 09:10 Сейчас в теме
(9) виндовые гуй-бинари есть на гитхабе: https://github.com/e8tools/tool1cd/releases
На инфостарт я их побоялся выкладывать - тут уже один раз баннили такое.
11. baton_pk 376 11.05.18 09:23 Сейчас в теме
(9)
Даешь контрибьютинг инкогнито!

эмм... чую, первым пулл-реквестом будет проверка на наличие лицензии :)
12. Evil Beaver 5374 11.05.18 09:25 Сейчас в теме
(11) Ну докажут свою троллепригодность, как минимум. Уже хорошо
13. baton_pk 376 11.05.18 09:34 Сейчас в теме
(12)
докажут свою троллепригодность

вот когда появится конфа "1С:Восстановление файловых баз", вот тогда это будет истинное 1С:Трололо.
корум; JohnyDeath; +2 Ответить
14. Evil Beaver 5374 11.05.18 13:10 Сейчас в теме
(13) если там будет внешняя компонента из статьи, то да
15. vadim1011985 56 16.05.18 09:01 Сейчас в теме
(3) а с каким форматом баз работает компонента ? 8.3.8 поддерживает ?
16. baton_pk 376 16.05.18 09:08 Сейчас в теме
(15)
8.3.8 поддерживает ?

да, поддерживает. От 8.2.14 (форматы ниже не проверял, может и работают) до 8.3.8 (с ним могут быть косяки, но пока они не встречались).
17. vadim1011985 56 16.05.18 12:01 Сейчас в теме
(16) не редко приходится восстанавливать файловые базы , для этого использую связку Tool1d , и библиотеку 1сd_lib ( тоже внешняя компонента так же открывает базу и читает данные + там организована взаимосвязь между метаданными базы и Таблицами, так же в отличии от tools1cd позволят удалять сразу несколько таблиц) , но работает только со старым форматом , что не очень удобно. Есть один алгоритм восстановления базы от ошибки «ошибка формата потока» который хотелось автоматизировать , но так как я не очень знаком с C++ хотелось бы узнать возможно ли с помощью вашей компоненты на уровне 1с осуществить удалять таблицы А так же импортировать и Экспортировать данные таблиц целиком ?
18. baton_pk 376 16.05.18 12:10 Сейчас в теме
(17)
возможно ли с помощью вашей компоненты

с помощью компоненты, что в статье - нет. это просто пример использования библиотеки. Библиотека, да, имеет функционал экспорта/импорта таблиц, их удаления - эти возможности надо ещё проверить и обкатать, потому сейчас я их не показываю. Опишите словами алгоритм и я, возможно, попробую его воспроизвести в одной из следующих статей.
19. vadim1011985 56 16.05.18 12:48 Сейчас в теме
(18) алгоритм таков
1) берётся чистая база того же релиза что и поврежденная , из неё удаляются все таблицы данных кроме служебных , за исключением таблиц Params , и DBSHEMA( которые будут перенесены из служебной базы ) имена служебных таблиц известны , желательно перед удалением экспортировать таблицы в виде файлов в каталог ( в Tools1cd кнопка экспорт таблиц данных )
2) из повреждённой базы выгружаются только таблицы данных , + 2 служебные таблицы Params и DBShema (тоже в виде файлов)
3) далее в чистую базу грузятся сначала служебные таблицы , а потом таблицы данных ( в tools кнопка - Импорт и создание таблиц)
В принципе все , единственный момент , что таблицы данных могут быть битыми ( например недавно столкнулся , в одной из таблиц файл BLOB весил 300 МБ ) , и при загрузке Tools валился с ошибкой , и надо было загружать таблицы по несколько шт. что бы отловить на какой происходит ошибка. А для исправления ситуации приходилось искать такую же таблицу из выгруженные таблиц чистой базы , и загружать ее с подменой файла описания таблицы descr из повреждённой базы ( т.е . Грузилась чистая таблица ) .

Надеюсь , что описал понятно
baton_pk; +1 Ответить
20. baton_pk 376 16.05.18 12:52 Сейчас в теме
(19) да, спасибо. Попробую воспроизвести этот сценарий.
21. vadim1011985 56 16.05.18 12:56 Сейчас в теме
(20) И Вам спасибо , надеюсь получится
4. artbear 1099 10.05.18 16:37 Сейчас в теме
Очень круто!

Вышли из тени наконец-то :) ?
5. fishca 1129 10.05.18 16:49 Сейчас в теме
Плюсиков маловато пока...
6. baton_pk 376 10.05.18 17:24 Сейчас в теме
(5) ты погоди, скоро начнут рассказывать, что это не нужно и что мы фигнёй занимаемся :)
7. bulpi 137 10.05.18 19:30 Сейчас в теме
(6)
Это очень нужно. Памятник при жизни из чистого золота в полный рост :)
8. baton_pk 376 10.05.18 20:18 Сейчас в теме
(7)
Памятник при жизни из чистого золота в полный рост

боюсь, немного не успели.
CSiER; nixel; user811063; NecroJew; +4 Ответить
24. Teopemuk 15.08.18 17:08 Сейчас в теме
(8)
Прошу прощения, но что значит "не успели"?
22. user811063 25.06.18 04:29 Сейчас в теме
Вот это действительно стоящая статья!!! Побольше бы таких на данном ресурсе!!!
23. baton_pk 376 25.06.18 07:52 Сейчас в теме
(22) Спасибо! Рад, что понравилось.
25. nomadon 341 16.08.18 13:08 Сейчас в теме
```
if (MSVC)
set (AddInNative_SRC ${AddInNative_SRC} AddInNative.def)
endif()
```
В шаблоне не указано подключение дефа? а как тогда dll работает? или шаблон предназначен не для CMake, он просто рядом что ли?
26. baton_pk 376 16.08.18 13:49 Сейчас в теме
(25)
дключение дефа? а как тогда

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