Проект внешней компоненты для 1С:8 (сделай сам)




Принцип обмена данными из 1С с сайтом (на MySQL) и выдачи (публикации) этих данных по запросу.
PHP-Скрипт автоматической загрузки данных из файла данных в формате CSV в базу данных сайта работающего на WordPress.

В продолжение моей темы: 1С:Альфа-Авто Автосалон Автосервис: обмен с сайтом.
С помощью данного скрипта можно загружать в автоматическом режиме, по расписанию, данные сервисных книжек (ремонтов авто) из 1С:Альфа-Авто Автосалон Автосервис.
Также можно загружать данные в ручном режиме: для этого делается скрытая страница, где размещается специальная кнопка.
Комментарии размещенные внутри скрипта разъяснят логику и порядок действия.
Комментарии с "/////    echo" использовались для отладки.
Дополнительно создана таблица для журналирования результатов загрузки данных.
Скрипт включает в себя защиту от SQL инъекций (думаю безопасность соблюдена в полной мере).
В кратце:
1. Пишется скрипт, который запускает этот.
2. Создается регламентное задание в WordPress, по которому запускается скрипт из п.1. 
3. Этот скрипт осуществляет проверку на существование файла обмена в папке.
4. Если данные не новые, загрузка не производится.
5. Если данные новые, очищается таблица сервисных книжек.
6. Загружаются новые данные.

Собственно сам скрипт:

<?php // Полная загрузка сервисных книжек, создан 2024-01-05 12:44:55

global $wpdb2;
global $failure;
global $file_hist;

/////  echo '<H2><b>Старт загрузки</b></H2><br>';

$failure=FALSE;
//подключаемся к базе
$wpdb2 = include_once 'connection.php'; ; // подключаемся к MySQL
// если не удалось подключиться, и нужно оборвать PHP с сообщением об этой ошибке
if (!empty($wpdb2->error))
{
/////   echo '<H2><b>Ошибка подключения к БД, завершение.</b></H2><br>';
$failure=TRUE;
wp_die( $wpdb2->error );
}

$m_size_file=0;
$m_mtime_file=0;
$m_comment='';
/////проверка существования файлов выгрузки из 1С
////файл выгрузки сервисных книжек
$file_hist = ABSPATH.'/_1c_alfa_exchange/AA_hist.csv';
if (!file_exists($file_hist))
{
/////   echo '<H2><b>Файл обмена с сервисными книжками не существует.</b></H2><br>';
$m_comment='Файл обмена с сервисными книжками не существует';
$failure=TRUE;
}

/////инициируем таблицу лога
/////если не существует файла то возврат и ничего не делаем
if ($failure){
///включает защиту от SQL инъекций и данные можно передавать как есть, например: $_GET['foo']
/////   echo '<H2><b>Попытка вставить запись в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>$m_comment));
wp_die();
/////    echo '<H2><b>Возврат в начало.</b></H2><br>';
return $failure;
}
/////проверка лога загрузки, что бы не загружать тоже самое
$masiv_data_file=stat($file_hist);   ////передаем в массив свойство файла
$m_size_file=$masiv_data_file[7];    ////получаем размер файла
$m_mtime_file=$masiv_data_file[9];   ////получаем дату модификации файла
////создаем запрос на получение последней удачной загрузки
////выбираем по штампу времени создания (редактирования) файла загрузки AA_hist.csv, $m_mtime_file

/////   echo '<H2><b>Размер файла: '.$m_size_file.'</b></H2><br>';
/////   echo '<H2><b>Штамп времени файла: '.$m_mtime_file.'</b></H2><br>';
/////   echo '<H2><b>Формирование запроса на выборку из лога</b></H2><br>';
////препарируем запрос
$text_zaprosa=$wpdb2->prepare("SELECT * FROM `vin_logs` WHERE `last_mtime_upload` = %s", $m_mtime_file);
$results=$wpdb2->get_results($text_zaprosa);

if ($results)
{   foreach ( $results as $r)
{
////если штамп времени и размер файла совпадают, возврат
if (($r->last_mtime_upload==$m_mtime_file) && ($r->last_size_upload==$m_size_file))
{////echo '<H2><b>Возврат в начало, т.к. найдена запись в логе.</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>$m_mtime_file,'last_size_upload'=>$m_size_file,'comment'=>'Загрузка отменена, новых данных нет, т.к. найдена запись в логе.'));
wp_die();
return $failure;
}
}
}
////если данные новые, пишем в лог запись о начале загрузки
/////echo '<H2><b>Попытка вставить запись о начале загрузки в лог таблицу</b></H2><br>';
$insert_fail_zapros=$wpdb2->insert('vin_logs', array('time_stamp'=>time(),'last_mtime_upload'=>0, 'last_size_upload'=>$m_size_file, 'comment'=>'Начало загрузки'));

////очищаем таблицу
$clear_tbl_zap=$wpdb2->prepare("TRUNCATE TABLE %s", 'vin_history');
$clear_tbl_zap_repl=str_replace("'","`",$clear_tbl_zap);
$results=$wpdb2->query($clear_tbl_zap_repl);
/////   echo '<H2><b>Очистка таблицы сервисных книжек</b></H2><br>';
if (empty($results))
{
/////   echo '<H2><b>Ошибка очистки таблицы книжек, завершение.</b></H2><br>';
//// если очистка не удалась, возврат
$failure=TRUE;
wp_die();
return $failure;
}

////загружаем данные
$table='vin_history';         // Имя таблицы для импорта
//$file_hist Имя CSV файла, откуда берется информация     // (путь от корня web-сервера)
$delim=';';          // Разделитель полей в CSV файле
$enclosed='"';      // Кавычки для содержимого полей
$escaped='\

36 Comments

  1. DoctorRoza

    Для полной ясности как такое работает, нужен хороший пример! Например, реализуйте компоненту, которая делает задержку выполнения кода или, скажем, цикла. Мне как-то такое потребовалось, не помню зачем.

    Reply
  2. ture

    (1) DoctorRoza, Не вопрос!

    Сейчас работаю над простой реализацией. У Вас будет простой вариант наследования базового компонента, а вся рутина будет реализована в базовом классе. Опишу порядок взаимодействия с 1С в той мере, в которой это будет полезно конечному разработчику.

    Reply
  3. Evil Beaver

    А какие преимущества указанного способа по сравнению с шаблоном проекта C++ и методикой «Разработки внешних компонент» с диска ИТС?

    Например, я (как пользователь инфостарта) могу взять шаблон проекта и методику вот отсюда http://infostart.ru/public/184119/. Чем ваш вариант отличается от предлагаемого на ИТС?

    Мне просто не хочется тратить 10sm, чтобы всего лишь выяснить разницу.

    Reply
  4. ture

    (3) Evil Beaver, В чем преимущество моего варианта перед Вашим? Ну… во-первых, я потерял дар речи…

    Я опирался на Ваше описание!!! Нету преимущества здесь.

    Ладно, по сути дела. Для меня оказалось неожиданностью такое внимание к «создать проект библиотеки DLL в студии» (это ведь черновой вариант по моим понятиям). Даже появилась задумка «создать проект консольного приложения (в картинках ;)»

    На диске ИТС лежит готовый проект. И что мало кто знает, как создать его с 0? В общем, это может объяснить популярность.

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

    Reply
  5. ture
    Reply
  6. ture

    В примере выше я использовал функции:

    /*Конвертация wchar_t* -> WCHAR_T* */
    size_t wchar_to_WCHAR(WCHAR_T * &Dest, const wchar_t* Source, size_t len) {
    if(len == 0) {//если размер задан, то и место уже зарезервировано
    len = wchar_len(Source) + 1;
    Dest = new WCHAR_T[len];
    }
    memset(Dest, 0, len*sizeof(WCHAR_T));
    for(size_t i = 0; i < len && Source[i]; ++i)
    Dest[i] = (WCHAR_T)Source[i];
    
    return len;
    }
    /*Конвертация wchar_t* -> char* */
    size_t wchar_to_char(char * &Dest, const wchar_t* Source, size_t len) {
    if(len == 0) {//если размер задан, то и место уже зарезервировано
    len = wchar_len(Source) + 1;
    Dest = new char[len];
    }
    len = wcstombs(Dest, Source, len);
    Dest[len — 1] = ‘’;
    return len;
    }
    /*Конвертация WCHAR_T** -> wchar_t* */
    size_t WCHAR_to_wchar(wchar_t * &Dest, const WCHAR_T* Source, size_t len) {
    if(len==0){//если размер задан, то и место уже зарезервировано
    len = WCHAR_len(Source) + 1;
    Dest = new wchar_t[len];
    }
    memset(Dest, 0, len*sizeof(wchar_t));
    for(size_t i = 0; i < len && Source[i]; ++i)
    Dest[i] = (wchar_t)Source[i];
    
    return len;
    }
    /*Конвертация WCHAR_T** -> char* */
    size_t WCHAR_to_char(char * &Dest, const WCHAR_T* Source, size_t len) {
    wchar_t * temp;
    WCHAR_to_wchar(temp, Source);
    len=wchar_to_char(Dest, temp,len);
    delete[] temp;
    
    return len;
    }
    /*Вычисление длинны строки WCHAR_T* */
    size_t WCHAR_len(const WCHAR_T* Source) {
    size_t res = 0;
    while(Source[res])  ++res;
    
    return res;
    }
    /*Вычисление длинны строки wchar_t* */
    size_t wchar_len(const wchar_t* Source) {
    size_t res = 0;
    while(Source[res])  ++res;
    
    return res;
    }
    
    

    Показать

    Reply
  7. ture

    1С создает экземпляры сразу:

    /*Список доступных типов (регистрируются в RegisterExtensionAs тем же именем)*/
    static const wchar_t Class_Names[] = L»myClass»; //|OtherClass1|OtherClass2
    
    /*ЭКСПОРТИРУЕМЫЕ МЕТОДЫ*/
    /*Получение экземпляра по имени*/
    long GetClassObject(const WCHAR_T* ex_name, Base** pInterface) {
    if(!*pInterface) {
    wchar_t * name = nullptr;
    WCHAR_to_wchar(name, ex_name);
    if(!wcscmp(name, L»myClass»))
    *pInterface = new myClass();
    delete[] name;
    return (long)*pInterface;
    }
    return 0;
    }
    /*Получение списка возможных типов*/
    const WCHAR_T* GetClassNames() {
    static WCHAR_T* names;
    wchar_to_WCHAR(names, Class_Names);
    
    return names;
    }

    Показать

    Reply
  8. ture
    Reply
  9. ture
    Reply
  10. Evil Beaver

    (4) ture, нет никакого «моего» варианта. Я просто документировал тот шаблон, который лежит на ИТС.

    А вопрос мой был про то — что из статьи непонятно, что именно лежит в архиве. Отличается ли оно от ИТС-ного шаблона и в какую сторону.

    Reply
  11. ture

    (10) Evil Beaver, в архиве просто компоненты использованные в статье:

    adapter.h

    memory_adapter.h

    base.h

    com.h

    types.h

    SuperI.def

    MANIFEST.XML

    Эти файлы наиболее близки к тому, что на ИТС. Шаблон 1С весьма своеобразен. Особенно порадовал базовый класс корпорации.

    Вы документировали механизм взаимодействия 1с со своими компонентами 😉 Я уже третий день рою эту тему… уже владею основами. Сейчас усердно протираю глаза и пытаюсь понять «как это нельзя массив передать в native dll?! шел год 2016»

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

    Reply
  12. TSSV

    «Я не добавлял в архив человеческую реализацию базового класса, который можно легко наследовать.» Как использовать скачанный архив, посоветуйте пожалуйста.

    Reply
  13. ture
    Reply
  14. ture
    Reply
  15. ture
    Reply
  16. ture

    (12) TSSV, пример 1С, который я прокомментировал и вставил в архив, то же можно использовать. Однако пример 1С предназначен только для ознакомления и выяснения принципа взаимодействия. Ну я совсем все карты уже раскрыл.

    Reply
  17. ture

    (15) ture, этот метод

    {L»Функция1″, L»Func1″, nullptr, nullptr, 1},

    не реализован.

    Reply
  18. ture

    (12) TSSV, выше я привел другую реализацию базового класса и вообще много переделал.

    Идея простая:

    1) пишем имя класса

    /*объявляем имя класса доступное из 1С*/
    Base::fill_name(L»myClass»);

    2) указываем свойства

    /*объявляем свойства доступные из 1С*/
    Base::Prop Props[] = {
    {L»Свойство_int»   , L»Prorp0″, true, true},
    {L»Свойство_double», L»Prorp1″, true, true},
    {L»Свойство_pchar» , L»Prorp2″, true, true},
    {L»Свойство_bool»  , L»Prorp3″, true, true},
    {L»Свойство_tm»    , L»Prorp4″, true, true}
    };

    русское, английское, можно читать, можно менять

    3) доступ к свойствам

    virtual bool ADDIN_API GetPropVal(const long num, tVariant* var) override {….
    virtual bool ADDIN_API SetPropVal(const long num, tVariant * var) override {

    4) указываем методы

    /*объявляем методы доступные из 1С*/
    Base::Method Methods[] = {
    {L»Функция1″, L»Func1″, nullptr, nullptr, 1},
    {L»Функция2″, L»Func2en», (void*)&std::bind(&myClass::Func2, this, _1, _2), nullptr, 2},
    {L»Процедура1″, L»Proc1en», nullptr, (void*)&std::bind(&myClass::Proc1, this, _1, _2), 1, true}
    };        

    русское, английское, указатель на функцию, указатель на процедуру, признак процедуры

    (void*)&std::bind(&myClass::Proc1, this, _1, _2) здесь надо менять только название Proc1

    (функцию еще не тестировал сам)

    5) реализация процедур и функций

    bool Proc1(tVariant* paParams, const long lSizeArray) {
    ….
    return true;
    }
    bool Func2(tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray) {
    ….
    return true;
    }
    Reply
  19. ture

    Я еще работаю над реализацией базового класса.

    Reply
  20. TSSV

    (18) ture, понятно, спасибо.

    Reply
  21. Synoecium

    (3) Evil Beaver, Ваша статья идеально подходит для новичков, которым надо разобраться как создавать внешние компоненты. Здесь же написан какой-то сумбур. Вроде я понимаю о чем речь, но как то смутно, при этом я сам ковырялся с внешними компонентами. В общем автору пока минус, в надежде на то, что он выложит волшебный класс, про который много говорит. За что 10 sm вообще не понятно.

    Reply
  22. ture

    (21) Synoecium, всегда приятно читать такие комменты, они расслабляют и позволяют не напрягаться. Компонент я выложил в своих комментах.

    Reply
  23. MherArsh

    Привет всем!

    В принципе ничего сложного, я тоже когда то делал так, писал компоненту для работы с картинками.

    Могу дать один совет начинающим, используйте C++Builder (Embarcadero) вместо Visual C++, там реально дохрена всяких классов под любое требование, начиная с простого до веб сервисов например .

    Reply
  24. Ivon

    Вроде бы все красиво и понятно, но в заголовке нужно добавить «для С++». Например я С++ не знаю, но знаю C#. При этом должен отметить, что подобные внешние компоненты, которые не интегрируются в интерфейс 1С, не так сложно начать писать и на C#. Мне удалось собрать по крупицам информацию, достаточную для написания интерфейсных ВК и даже написать такую ВК для собственных нужд (чтение изображения из базы MSSQL и отображение этого изображения в самой компоненте, так как поле картинки в 1С при смене картинки в поле не освобождает память, в итоге клиент можно крашануть просто меняя в поле 2 картинки одну на другую 100500 раз), но процесс очень сложно назвать легким для понимания. Поэтому я отслеживаю подобные статьи в надежде на поиск нормальной инструкции. Сам инструкцию пока написать не могу, так как некоторые манипуляции делаются без понимания, зачем они нужны, но со знанием, что если этого не сделать, то ВК не заработает.

    Reply
  25. skyadmin

    (24) Ivon, аналогично сдлал, только на VB.NET.

    Если бы был пример на C#, то можно было бы через Convert .NET преобразовать C# в VB.NET, попробовать.

    Reply
  26. ture

    (25) skyadmin, пример на c#?

    В прошлом году возился с шарпом. Все компоненты через COM требуют регистрации. С админами не договориться без ружья на медведя… Чуть что серьезное и на шарпе нету, надо собирать dll из исходников на с/с++/java…. а то и вообще выковыривать из опенсорс под линукс. Короче говоря, шарп решил бросить.

    Reply
  27. talych

    Есть внешняя компонента, написанная для 1С 8.1 В ней использовал CDialogEx

    Можно ли использовать их в Native API? Клиенты работают только на винде. Пытаюсь выполнить код, вылетает в assert

      if(!m_pDlg)
    {
    m_pDlg = new CMainDlg();
    m_pDlg->Create(IDD_DIALOG2);
    }
    
    Reply
  28. ture

    Чуток промазал в исходниках. Правильней так.

    size_t wchar_to_char(char * &Dest, const wchar_t* Source, size_t len) {
    /*if(len == 0) {//если размер задан, то и место уже зарезервировано
    len = wchar_len(Source) +1;
    Dest = new char[len];
    }
    wcstombs(Dest, Source, len);
    Dest[len — 1] = ‘’;*/
    
    if(len == 0) {
    len = WideCharToMultiByte(
    1251,   // Code page
    0,      // Default replacement of illegal chars
    Source, // Multibyte characters string
    -1,     // Number of unicode chars is not known
    NULL,   // No buffer yet, allocate it later
    0,      // No buffer
    NULL,   // Use system default
    NULL    // We are not interested whether the default char was used
    );
    if(len == 0)
    return 0;
    else
    Dest = new char[len];
    }
    
    len = WideCharToMultiByte(
    1251,    // Code page
    0,       // Default replacement of illegal chars
    Source,  // Multibyte characters string
    -1,      // Number of unicode chars is not known
    Dest,    // Output buffer
    len,     // buffer size
    NULL,    // Use system default
    NULL     // We are not interested whether the default char was used
    );
    
    if(len == 0) {
    delete[] Dest;
    return 0;
    }
    
    return len;
    }

    Показать

    Reply
  29. so-quest

    Примерно представляя сколько труда надо вложить в статью, говорю — спасибо. Написано немного сумбурно, но читаемо. Единственное что хотел бы добавить — если не стоит цели заработать старманей, то стоит подумать об использовании github или ему подобных серверов.

    Reply
  30. ture

    (30) so-quest, нет цели зарабатывать монетки. Я, как и все, складирую полезные вещи с описанием. В какой-то момент статья была в тренде… пришлось поподробней и побольше написать.

    Reply
  31. ture

    На сервачке:

     Объект_=РеквизитФормыВЗначение(«Объект»);
    адрес = ПоместитьВоВременноеХранилище(Объект_.ПолучитьМакет(«SuperI»));
    res=ПодключитьВнешнююКомпоненту(адрес,»VK»,ТипВнешнейКомпоненты.Native);
    obj = Новый(«AddIn.VK.myClass»);
    Reply
  32. ture

    На клиенте:

    &НаСервере
    Функция GetInf()
    Перем стРезультат;
    
    oSQL=Объект.Код;
    бРезультат=ВыполнитьSQL(oSQL,»select 1 as [q]»);
    Если бРезультат тогда
    Объект_ = РеквизитФормыВЗначение(«Объект»);
    стРезультат=Новый Структура(«Адрес,Сервер,База,Пользователь,Пароль»,
    ПоместитьВоВременноеХранилище(Объект_.ПолучитьМакет(«Макет_v2»),Новый УникальныйИдентификатор),
    oSQL.Server,
    oSQL.Base,
    «***»,»***»);
    КонецЕсли;
    
    Возврат стРезультат;
    КонецФункции
    
    &НаКлиенте
    Процедура xmlНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
    Перем Criterion;
    
    СтандартнаяОбработка=Ложь;
    стРезультат=GetInf();
    Если стРезультат<>Неопределено тогда
    Попытка
    res=ПодключитьВнешнююКомпоненту(стРезультат.Адрес, «VK», ТипВнешнейКомпоненты.Native);
    Criterion = Новый(«AddIn.VK.Criterion»);
    Исключение
    УстановитьВнешнююКомпоненту(стРезультат.Адрес);
    res=ПодключитьВнешнююКомпоненту(стРезультат.Адрес, «VK», ТипВнешнейКомпоненты.Native);
    Criterion = Новый(«AddIn.VK.Criterion»);
    КонецПопытки;
    УдалитьИзВременногоХранилища(стРезультат.Адрес);
    
    Criterion.Сервер=стРезультат.Сервер;
    Criterion.База  =стРезультат.База;
    Criterion.КритерийОтбора =»»;
    Criterion.ПользовательSQL=стРезультат.Пользователь;
    Criterion.ПарольSQL      =стРезультат.Пароль;
    Объект.xml=Criterion.ПолучитьXMLфайл();
    КонецЕсли;
    КонецПроцедуры
    

    Показать

    В макет складываем манифест и все прописанные в нем dll (достаточно под 32 и 64 бита на ведре)

    Reply
  33. IgorLee

    Всем привет !

    (1С8.3.5.1625)

    Кто либо сталкивался с проблемой, что компонента «сама выгружается» от 1С или 1С её выгружает сама без какой либо причины ?

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

    Примечание: в компоненту добавил логирование, соотв. из логов делаю вывод

    Reply
  34. IgorLee

    (34) IgorLee,

    В общем вроде разобрался.

    Дело вот в чем — 1С8 сама выгружает компоненту ровно через 20 минут, проверял кучу раз (расхождение не более 15 сек. между «опытами») !

    Но что я раскопал…

    Оказывается если у «IAddInDefBaseEx» запросить интерфейс «IMsgBox» и/или «IPlatformInfo» через метод «GetInterface(…)» — и хранить значение этого интерфейса, к примеру, в локальной переменной класса компоненты — то 1С не выгружает Вашу компоненту !

    Оч. странно вышло 🙁

    Reply
  35. ture

    (35) IgorLee, меня тут в sap окунули с головой. И как-то склоняться я к мысли стал, что 1С не игрок на мировом рынке. Больше того 1С не игрок и на своем рынке после того свинства с переходом с 1С7.7 к 1С8. Я имею ввиду то, что пришлось не просто код программ целиком переписывать, а даже самих программистов переучивать. Теперь старожилы 1С не сильно вникают в тонкости и детали платформы, потому что знают что однажды всё снова изменится и заплатят они дорого за своё «тайное» и выстраданное знание, не желая от него отказаться. В итоге в 1С остаются только лошки, которые и программистами называют себя с натяжкой и знают, что доход их складывается от знания бредовой реализации типовых конфигураций, а не программирования и знания внутреннего языка. Такого в SAP не было сколько мне известно. Вот такие вот тонкости.

    Reply

Leave a Comment

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