Можно ли в 1С не соблюдать технологию внешних компонент? Или Как поздравить коллег с помощью 1С? +9


Возникла тут идея поздравить нашего главного бухгалтера более-менее оригинально, например, с помощью ее любимой программы 1С? Но как?

После некоторых размышлений, пришла мысль использовать для поздравлений фоновое изображение в клиентской области обычных форм для конфигураций на 1С77–1С82 либо во внешнем окне для управляемых форм 1С82 и во всех случаях для 1С83. На нем вывести нужное сообщение и дать ссылки на поздравительное видео, как показано на рисунке.

Поздравление в 1С

Часть первая — результирующая


Очевидно, что данная идея не нова. Так, в 2011 году предлагалось похожее решение на базе FormEx.dll, Алексея Фёдорова aka АЛьФ. А вопросы как этого достичь задавались еще в далеком 2008 году.

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

Такая компонента была написана (тоже, только для bmp-файлов, с использованием, при необходимости, укладки «черепицей»). Там применялась функция WinAPI LoadImage(). Эта dll-ка не конфликтовала с FormEx.dll, была простой, достаточно шустрой и служила долго.

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

В данной статье, мы не касаемся вопросов создания мультимедийных файлов. Это не наша специализация. Ограничимся только некоторыми нюансами программирования внешних компонент для 1С.

1C77


Поскольку версии платформы 1С могут быть разные, то и вариантов решений может быть несколько. В нашем случае это были конфигурации на 1С77 (рис. 1).

Рис. 1. Поздравительное изображение в тестовой конфигурации на 1С77
Рис. 1. Поздравительное изображение в тестовой конфигурации на 1С77.

Видео здесь хоть и собственное, но идея его создания почерпнута у Анны Шияновой, под ником «Особый случай». У этой девчонки есть талант, ей можно подражать, но полностью повторить стиль вряд ли получится. В данном случае просто хотелось хоть какого-то элемента творчества.

Если кому-то из коллег уже надоест смотреть на чужие поздравления, то они могут перегрузить картинку по «Alt+I» (рис. 2-3).

Рис. 2. Выбор другого фонового изображения в меню «Файл / Выбрать фон» либо по «Alt+I»
Рис. 2. Выбор другого фонового изображения в меню «Файл / Выбрать фон» либо по «Alt+I».

А заодно посмотреть информацию о используемом модуле по «Alt+L» (рис. 3).

Рис. 3. Перегруженное фоновое изображение вместе с информацией о программе  («Помощь / О модуле LionExt32.dll» либо «Alt+L»)
Рис. 3. Перегруженное фоновое изображение вместе с информацией о программе («Помощь / О модуле LionExt32.dll» либо «Alt+L»).

1C82, обычные формы


Естественно, что большинство сейчас ориентированы на «восьмерку» (1С8х). Однако работать с фоновым изображением в 1С можно только на обычных формах в версии 8.2 и меньше и то, если не используются какие-либо обработки, запускаемые в режиме «рабочего стола», которые будут просто полностью перекрывать наш фон (рис. 4).

Рис. 4. Поздравительное изображение в тестовой конфигурации на обычных формах 1С82
Рис. 4. Поздравительное изображение в тестовой конфигурации на обычных формах 1С82.

Заметим, что ссылки на рис. 4 указывают не на наше видео. Они показаны просто для теста.

В обычных формах 1С82 уже не получается стандартным способом получить доступ к меню, поскольку оно там не системное, как в «семерке», а «собственное» (хотя системное можно создать, но зачем нам два главных меню?). Однако горячие клавиши можно использовать. По тому же «Alt+I» мы в своей компоненте вызываем диалог, как на рис.2 и загружаем другой фон (рис. 5).

Рис. 5. Перегруженное фоновое изображение в «толстых» формах 1С82
Рис. 5. Перегруженное фоновое изображение в «толстых» формах 1С82.

Аналогично можно получить информацию о модуле по клавише «Alt+L», как на рис. 3.

1C82, управляемые формы


Для управляемых форм в 1С82 еще можно отыскать на седьмом уровне вложенности нужное нам окно, типа «V8FormElement» и рисовать на нем, но как-то не интересно.

Для нас из этих рассуждений следует то, что проще создать внешнее окно с поздравительным сообщением (рис. 6), чем обрабатывать каждый отдельный случай. Само окно можно будет закрывать, точнее, сворачивать по «Esc», «Ctrl+F4», «Alt+F4» либо нажатием мышкой на «крестик».

Рис. 6. Поздравительное изображение в тестовой конфигурации на управляемых формах 1С82
Рис. 6. Поздравительное изображение в тестовой конфигурации на управляемых формах 1С82.

Причем свернутое окно (рис. 7), можно развернуть снова.

Рис. 7. Свернутое изображение внешнего окна на управляемых формах 1С82
Рис. 7. Свернутое изображение внешнего окна на управляемых формах 1С82.

Размеры и относительное расположение внешнего окна можно менять, здесь все как обычно (см. увеличенные изображения внешних окон на рис. 6 и рис. 10). Отметим, что горячие клавиши действуют только, если внешнее окно активно.

1C83, обычные формы


В 1С83 дочерних окон уже нет вообще, что может служить критерием версии 1С в нашей dll. Причем, «толстые» формы являются фреймовым окном (рис. 8), а управляемые формы – безфреймовом (рис. 9). Т.е., всё, что не фрейм может быть перерисовано. Фрейм тоже может быть перерисован, но только как системный элемент.
Рис. 8. Фреймовое окно в «толстых» формах 1С83 Рис. 9. Безфреймовое окно в управляемых формах 1С83
Рис. 8. Фреймовое окно в «толстых» формах 1С83. Рис. 9. Безфреймовое окно в управляемых формах 1С83.
Здесь мы с помощью динамической библиотеки создали тестовое окно и подчинили его главному окну 1С. Различие в поведении видно на рисунках.

1C83, управляемые формы


В случае 1С83, как и в управляемых формах 1С82 мы будем рисовать свои поздравления не на фоне, а в отдельном окне, прототип которого указан на рис. 8-9. В итоге нужная компонента (LionExt32.dll либо LionExt64.dll) даст следующий результат (рис. 10-12).

Рис. 10. Фоновое изображение во внешнем окне для обычных форм 1С83
Рис. 10. Фоновое изображение во внешнем окне для обычных форм 1С83.

Рис. 11. Фоновое изображение во внешнем окне управляемых форм 1С83, релиз 14, 64-х разрядный вариант
Рис. 11. Фоновое изображение во внешнем окне управляемых форм 1С83, релиз 14, 64-х разрядный вариант.

Рис. 12. Фоновое изображение во внешнем окне управляемых форм 1С83, релиз 15, 64-х разрядный вариант
Рис. 12. Фоновое изображение во внешнем окне управляемых форм 1С83, релиз 15, 64-х разрядный вариант.

Предварительные выводы


Данная компонента была реально использована на практике (рис. 1), главный бухгалтер осталась довольна, все прошло чудесно. Попутно выяснилось, что пользователям нравиться выбирать собственные фоновые картинки, в данном случае, для работы на «семерке». Для «восьмерки» наша компонента адаптирована с заделом на будущее, пока ее стоит рассматривать как демо-вариант.

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

Из технических новаций была использована блокировка по выгрузке нашей компоненты платформой 1С (поскольку она не соблюдает формат ВК). Кроме того, другой трюк позволил назначить дочернему окну локальное меню, так как операционная система Windows блокирует создание подобного меню у подчиненных окон. Поэтому вы нигде не увидите местных меню в том же MDI (Multi Document Interface). Заменой ему служат командные панели, тулбары и контекстное меню. Есть еще момент по обновлению окон. Иногда бывает, то ни UpdateWindow(), ни InvalidateRect() не срабатывают должным образом. А вот пара функций в этом случае оказывается успешной:

ShowWindow(hWnd, SW_HIDE);
ShowWindow(hWnd, SW_SHOW);

Еще нужно отметить, что наша компонента может конфликтовать с другими, например, с FormEx.dll для 1С77. В этом случае нужно, чтобы она загружалась последней.

Кстати, замечено, что если создавать конфигурацию в версии 1С-8.3.14 и выше, то компонента не загружается никаким штатным способом. Но если база создана в более ранней версии 1С, а открывается в последних версиях, то тогда проблем с загрузкой нашей ВК нет. Это еще раз намекает на необходимость создания внешнего загрузчика.

В данном проекте использована подсистема WinAPI GDI+. С ее помощью можно отображать картинки различных форматов: bmp, jpg, gif, png, tif и других. В этом же порядке компонента пытается загружать первый доступный файл Main.* из локального каталога Pics в текущей конфигурации. Если ни один из данных файлов не найден, то используется простой фоновый рисунок из ресурсов компоненты. На рис. 13 показан этот фоновый рисунок для обычных форм 64-х разрядной 1С83, релиз 15. Для разнообразия внешнее окно слега увеличено и на его фон добавлено еще одно изображение из файла Main1.png, уложенное «черепицей».

Рис. 13. Фоновый рисунок по умолчанию для обычных форм 64-х разрядной 1С83, релиз 15
Рис. 13. Фоновый рисунок по умолчанию для обычных форм 64-х разрядной 1С83, релиз 15. Дополнительно добавлено еще одно изображение из файла Main1.png, уложенное «черепицей».

Разницы работы компоненты в режимах разной разрядности не наблюдается.

Также можно отметить, то наша компонента осуществляет сабклассинг главного окна 1С и его MDI-клиента, если он имеется. Именно это, по-видимому, служит источником конфликта с FormEx.dll, когда он загружен последним (в 1С77).

Часть вторая — техническая


Непосредственно с самим проектом можно ознакомиться по следующим ссылкам:


Проект на С++ может быть легко адаптирован под 10-ю версию, если в файлах конфигурации заменить строку «v120» на «v100» и «ToolsVersion=«12.0»» на «ToolsVersion=«4.0»».

Код для 32-х разрядной и 64-х разрядной версий один и тот же и компилироваться могут одновременно.

Версия 1С77 определяется во внешней компоненте по ненулевому хэндлу фнукции GetMenu(), а версия 1С83, по отсутствию дочерних окон у главного окна, хэндл которого определяется функцией GetForegroundWindow().

О технологии создания внешних компонент для 1С


На дисках ИТС фирмы «1С», и в Интернете, можно без труда найти информацию о создании ВК и соответствующие шаблоны на разных языках программирования. Однако во времена 1С77 эти шаблоны удовлетворяли «не только лишь всех».

Если посмотреть на некоторые широко используемые компоненты, особенно для 1С77, то увидим, что их авторы нередко применяли особые методы программирования ради расширения возможностей своих разработок.

Возможно одной из первых такой внешней компонентой была «RAINBOW ADDIN 2000 для 1С: Предприятие 7.7». Пожалуй, самым важным здесь было более глубокое проникновение в недра «семерки», чем позволяла официальная технология ВК, хотя формат ВК она соблюдала. Это достигалось за счет полученных, вполне возможно нестандартными методами, хидеров (*.h-файлов) библиотечных файлов 1С77, используемых и в других широко известных проектах.

Действительно, если такие функции 1С как ЗагрузитьВнешнююКомпоненту() и ПодключитьВнешнююКомпоненту() позволяют внедрить в собственное адресное пространство внешние dll (прежде всего, удовлетворяющих формату технологии ВК), то почему бы пользовательским программам не поддаться искушению и не попытаться получить доступ к другим, скрытым от них, процедурам и прочим объектам целевой платформы? Вот этот подход как раз успешно продемонстрировал компонент Rainbow.dll.

Позже подобный механизм взяли на вооружение другие авторы компонент 1С версии 7.7. Особо следует выделить компоненту для «семерки» 1C++.dll и ее как бы частный случай FormEx.dll.

Но на этом нетривиальность подхода к проектированию внешних компонент для 1С77 не закончилась. По-видимому, кто-то должен был сказать: «Зачем нам кузнец? Кузнец нам не нужен!». Здесь под «кузнецом» мы понимаем технологию COM от MicroSoft, которой, в некотором смысле, следовала технология ВК для «семерки». Нет, ну, правда, зачем нам реестр, если мы загружаем нашу ВК непосредственно? Это может иметь смысл для веб-браузеров, работающих с Интернетом, но для локальной работы использование реестра явно избыточно. По крайней мере, это не должно быть обязательным условием. Тем более что для редактирования реестра нужны административные права.

Заметим, что фирма «1С» эту технологию очень любила (по крайней мере, до портирования 1С на Линукс). Мы же относимся к ней достаточно прохладно. COM удобна для использования ActiveX компонент и это естественно, поскольку последние разрабатывались изначально для Интернета.

Однако в последних версиях фирма «1С» добавила возможность применения технологии Native API, которая позволяет обходиться без использования реестра. В принципе, это то, что нам нужно, за исключением того, что эта технология не применима в «семерке», а она, для некоторых, все еще актуальна.

Но иногда возникают относительно простые задачи, когда не хочется использовать кучу шаблонного кода ВК и желательно работать с 1С со стороны внешней компоненты только. Как, скажем, в нашем случае демонстрации поздравительного изображения в клиентской области или, при необходимости, в отдельном окне, конфигурации 1С.

Другими словами, если мы не собираемся непосредственно обмениваться данными между 1С и ВК, то нас вполне устроит более простой и универсальный вариант внешней компоненты для 1С. Простота здесь будет достигаться за счет отсутствия шаблонного кода.

Альтернативные технология создания ВК для 1С


Поскольку ВК для 1С это частный случай COM-сервера (до появления технологии Native API), то, нашлись разработчики ВК, которые сказали: «COM’у – нет!». Особенно заметна деятельность в этом направлении Александра Орефкова. Его компоненты «1sqlite.dll», «TurboMD.dll» и возможно другие, не используют COM от слова «совсем». Также по этому пути развивается компонента ЙоксельSpreadSheet.dll»).

Но как же тогда загрузчик ВК от 1С77 загружает эти компоненты? Ведь они даже не пытаются имитировать какой-то там COM. И действительно, если мы попытаемся тупо подсунуть в функцию ЗагрузитьВнешнююКомпоненту() какую-нибудь стандартную dll, сгенерированную скажем мастером MS VC++, то нас ждет облом.

В «семерке» мы получим сообщение, типа:
Ошибка при создании объекта из компоненты <ПолныйПуть\ИмяКомпоненты>.dll (отсутствует CLSID)

В «толстом» 32-х разрядном клиенте «восьмерки» сообщение будет аналогичное. Та же dll-ка вызовет похожую ругань (рис. 15):
Ошибка при вызове метода контекста (ЗагрузитьВнешнююКомпоненту): Ошибка при загрузке внешней компоненты

Так все-таки, как упомянутые библиотеки решают эту проблему? Изучая тексты программ Орефкова и Йокселя мы, в конечном счете, приходим к выводу, что «виноваты» следующие «волшебные строчки» в файле ресурсов (*.rc либо *.rc2):

STRINGTABLE DISCARDABLE 
BEGIN
    100 "\0sd"  // 1sqlite.dll
    100 "\0tmd"  // TurboMD.dll
    100  "\0f"  // SpreadSheet.dll
END

Т.е. в обязательном порядке в ресурсах программы присутствует строка с идентификатором 100 и некоторым строковым значением, первый символ которого нулевой. Вы можете экспериментировать с вариантами подобных строк, а меня вполне устраивает строка "\0L". Таким образом, создаем файл ресурсов и пишем туда строчки вида:

STRINGTABLE DISCARDABLE 
BEGIN
    100 "\0L"  // Без подобной строки 1С не загрузит нормально внешнюю компоненту!
END

Подключаем этот файл к нашему простейшему dll-проекту, сгенерированному мастером MS C++, добавляем код:

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)  {
    switch(dwReason) {
        case DLL_PROCESS_ATTACH:
            MessageBox(NULL, "Привет, из DllMain()!", "Информация", MB_OK);
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
    }  // switch(dwReason)

    return TRUE;
}  // DllMain()

и наблюдаем (рис. 14).

Рис. 14. Использование простейшей «ВК» в 1С82
Рис. 14. Использование простейшей «ВК» в 1С82.

Без «волшебных строчек» в файле ресурсов, наша dll, после показа MessageBox'a, сразу же выгрузиться с руганью, со стороны 1С (рис. 15).

Рис. 15. Ошибка загрузки обычной dll в 1С82
Рис. 15. Ошибка загрузки обычной dll в 1С82.

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

Первым, похоже, «волшебные строки» описал в своей старой статье Алексей Фёдоров (АЛьФ), но ссылка на нее уже недоступна, а автор не видит смысла в ее переопубликации. Причем, наиболее интенсивно их использовал Александр Орефков, и судя по всему, с его подачи, автор Йокселя. Поэтому мы будем говорить о «волшебных» строках Фёдорова-Орефкова. Смысл их в том, чтобы заблокировать выгрузку нестандартных (с точки зрения 1С) dll-файлов функцией ЗагрузитьВнешнююКомпоненту(). Причем, как мы видим, этот прием работает не только в 1С77, но и в «толстых» формах 1С82.

Однако в управляемых формах 1С82 и во всех версиях 1С83 эта возможность уже полностью перерыта (также появился и другой загрузчик – ПодключитьВнешнююКомпоненту()).

Таким образом, в современных версиях 1С нужно искать уже другие простые альтернативы «волшебным» строкам Фёдорова-Орефкова.

И такую альтернативу легко предложить. Суть проста. Загрузчик 1С выгружает «неправильную» компоненту, если она вызывает исключение при попытке обратиться к ней по заданному протоколу, например, при запросе версии компоненты. У нас, естественно, ничего такого нет, что и служит основанием для выгрузки нестандартной dll. Но требование 1С к операционной системе выгрузить данную динамическую библиотеку может быть проигнорирована системой, если эта ВК все еще где-то используется. Вместо собственно удаления, система просто уменьшает счетчик использования искомого модуля. И удалит физически не ранее, чем этот счетчик обнулится. Поэтому наша задача искусственно увеличить этот счетчик.

Для этого можно в секции DLL_THREAD_ATTACH еще раз вызвать нашу dll функцией WinAPI LoadLibrary()

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)  {
    switch(dwReason) {
        case DLL_PROCESS_ATTACH: {
             WCHAR szDllName[_MAX_PATH] = {0};

             // Получаем полный путь нашей dll
             GetModuleFileName(hModule, szDllName, _MAX_PATH);
             //MessageBox(NULL, szDllName, L"Info", MB_OK);

             // Повторная загрузка текущей dll (чтобы блокировать ее выгрузку из 1С83), 
             // но без повторного выполнения секции DLL_PROCESS_ATTACH
             HMODULE hDll = LoadLibrary(szDllName);
  
            break;
        }  // case DLL_PROCESS_ATTACH
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            break;
    }  // switch(dwReason)

    return TRUE;
}  // DllMain()

Всё! Проблема решена. Повторный вызов той же самой динамической библиотеки увеличит счетчик ее использования на единицу, а выгрузка (с предварительным заходом в секцию DLL_THREAD_DETACH) уменьшит на единицу. Итого имеем 2 – 1 = 1 > 0, следовательно, операционная система выгружать нашу dll не будет. Причем, что важно, повторной инициализации секции DLL_PROCESS_ATTACH происходить не будет.

Отсюда, кстати, видно, как фирма «1С» может бороться с подобным трюком в своих последних версиях (и, по-видимому, уже делает это, в конфигурациях, созданных в 1С-8.3.14 и выше). Она может использовать функцию LoadLibraryEx() с параметром, блокирующим выполнение секции инициализации DLL_PROCESS_ATTACH, после чего, сразу вызовет нужные экспортируемые функции. И, действительно, если посмотреть код примера ВК для Native API, то видно, что вызывать код инициализации нет никакой необходимости, так как по формату ВК он должен быть пустым.

Относительно примеров использования технологии COM, очевидно, что исполнение секции инициализации DLL_PROCESS_ATTACH, там необходимо, поэтому в не слишком новых версиях 1С, точнее, в конфигурациях, которые сделаны в 1С-8.3.13 и ниже, нам подойдет загрузчик 1С:

ПодключитьВнешнююКомпоненту(АдресКомпоненты, НаимКомпоненты, ТипВнешнейКомпоненты.COM);

Здесь последний параметр можно убрать, поскольку он подразумевается по умолчанию. При этом открываться они могут нормально в любой более высокой версии. В версиях 1С83 прежний загрузчик ЗагрузитьВнешнююКомпоненту(АдресКомпоненты) нам уже не подходит (соответственно и «волшебные строки» Фёдорова-Орефкова там не работают).

В общем случае, как уже упоминалось, проблему можно решить путем использования внешнего загрузчика. Либо, что вполне естественно, соблюдать в той или иной мере технологию внешних компонент фирмы «1С».

Следует также отметить, что эксперименты мы проводили в файловых версиях 1С разной разрядности. Чтобы загрузить нашу компоненту может потребоваться установить в конфигурации свойство «Режим использования синхронных вызовов» в значение «Использовать».

Также следует понимать, что применение подобной техники вы осуществляете на собственный страх и риск, экспериментируйте предварительно на тестовых конфигурациях либо копиях рабочих, чтобы избежать потенциальных проблем в основных программах.

Обновление от 11.09.2019


Выяснилось, что я зря переживал, что: «в версиях 1С-8.3.14 и выше, секция инициализация во внешней компоненте не выполняется уже от слова «совсем»».

Оказывается выполняется, только возвратное сообщение в функции ПодключитьВнешнююКомпоненту() не нужно обрабатывать. Причем независимо от того, какой тип компоненты мы укажем: COM или Native API.

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

В связи с этим слегка изменен код в тестовых конфигурациях для 1С82 и 1С83, хотя различия между ними становятся уже не принципиальными.

При этом очевидно остается в силе наше замечание, что фирма «1С» может легко заблокировать выполнение кода инициализации в любой ВК, по крайней мере, для внешних компонент типа Native API, поскольку, судя по их шаблону, в этом нет никакой необходимости. Для ВК типа COM такая необходимость пока есть, но что мешает от нее избавиться? Заодно и посмотрим, примут ли «там» в расчет эту информацию?




К сожалению, не доступен сервер mySQL