При переносе программного кода из модулей форм во вновь созданные общие модули разработчики 1С часто сталкиваются со странным поведением платформы. Код, который ранее успешно работал в форме обработки, внезапно отказывается компилироваться в общем модуле, выдавая ошибку вида Процедура или функция с указанным именем не определена. Особенно критично это проявляется при разработке процедур для регламентных и фоновых заданий.
В этой статье мы подробно разберем, почему возникает данная проблема, проанализируем механизмы компиляции общего модуля под различными клиентами, исправим ошибки вызова функций внутри одного модуля, а также устраним скрытые критические уязвимости, которые могут полностью заблокировать работу фонового процесса на сервере.
Рассмотрим классический пример из практики разработки. В общем модуле с именем РегЗад размещены процедура ЗаписьДанных и функция ПолучитьСтрокиКарт. Процедура вызывается регламентным заданием и, в свою очередь, обращается к функции внутри того же модуля для получения текстовых данных из справочника.
При синтаксическом контроле или запуске конфигурации платформа выдает ошибку:
{ОбщийМодуль.РегЗад.Модуль(39,15)}: Процедура или функция с указанным именем не определена (ПолучитьСтрокиКарт) СтрокаКарты = >ПолучитьСтрокиКарт(СтрокаРазделителя); (Проверка: Тонкий клиент)
Посмотрим на исходный код этих методов:
Процедура ЗаписьДанных Экспорт
СтрокаРазделителя = "*";
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл("C:\Users\User\Desktop\Новаяпапка\Список_дисконт.xml");
ЗаписьXML.ЗаписатьОбъявлениеXML;
ЗаписьXML.ЗаписатьНачалоЭлемента("ContragentList");
ЗаписьXML.ЗаписатьАтрибут("ИмяСправочника", "Контрагенты");
ЗаписьXML.ЗаписатьКомментарий("Выгрузка элементов справочника");
// Вызов функции, вызывающий ошибку
СтрокаКарты = ПолучитьСтрокиКарт(СтрокаРазделителя);
СтрокиКарт = СтрРазделить(СтрокаКарты, Символы.ПС);
Для ТекущаяСтрока = 0 По СтрокиКарт.Количество() - 1 Цикл
Данные = СтрРазделить(СтрокиКарт[ТекущаяСтрока], СтрокаРазделителя);
ЗаписьXML.ЗаписатьНачалоЭлемента("ЭлементСправочника");
ЗаписьXML.ЗаписатьНачалоЭлемента("Код");
ЗаписьXML.ЗаписатьТекст(Данные[0]);
ЗаписьXML.ЗаписатьКонецЭлемента;
ЗаписьXML.ЗаписатьНачалоЭлемента("Наименование");
ЗаписьXML.ЗаписатьТекст(Данные[1]);
ЗаписьXML.ЗаписатьКонецЭлемента;
ЗаписьXML.ЗаписатьНачалоЭлемента("ДатаРождения");
ЗаписьXML.ЗаписатьТекст(Данные[2]);
ЗаписьXML.ЗаписатьКонецЭлемента;
ЗаписьXML.ЗаписатьКонецЭлемента;
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента;
ЗаписьXML.Закрыть();
КонецПроцедуры
Функция ПолучитьСтрокиКарт(Разделитель) Экспорт
СтрокаКарты = Новый Массив;
МассивСтрокКарт = Новый Массив;
Выборка = Справочники.ДисконтКарты.Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаКарты.Очистить();
СтрокаКарты.Добавить(Выборка.Наименование);
СтрокаКарты.Добавить(Выборка.ПолноеНаименование);
СтрокаКарты.Добавить(Выборка.УНП);
Данные = СтрСоединить(СтрокаКарты, Разделитель);
МассивСтрокКарт.Добавить(Данные);
КонецЦикла;
Возврат СтрСоединить(МассивСтрокКарт, Символы.ПС);
КонецФункции
Первоначально у общего модуля были установлены следующие свойства (флаги):
Давайте разберем по шагам, почему при таких настройках возникает ошибка компиляции.
Основная причина ошибки кроется в конфликте между флагом Клиент (управляемое приложение) и кодом, написанным внутри функции ПолучитьСтрокиКарт.
Когда у общего модуля взведен флаг Клиент, платформа при запуске или синтаксической проверке пытается скомпилировать весь код данного модуля для выполнения на стороне Тонкого клиента. Однако в коде функции используется обращение к базе данных напрямую: Справочники.ДисконтКарты.Выбрать().
В управляемом приложении 1С действует жесткое архитектурное ограничение: работа с объектной моделью базы данных (справочники, документы, регистры) на стороне тонкого клиента запрещена. Эти объекты доступны исключительно на сервере.
Поскольку компилятор тонкого клиента встречает в функции ПолучитьСтрокиКарт запрещенную конструкцию Справочники.ДисконтКарты, компиляция этой функции для клиента аварийно прерывается. Она попросту «выпадает» из клиентского контекста модуля. Вслед за этим процедура ЗаписьДанных, которая также компилируется для клиента, пытается вызвать эту функцию и закономерно сообщает, что Процедура или функция с указанным именем не определена (ПолучитьСтрокиКарт).
Для исправления этой ситуации выполним следующие настройки свойств общего модуля:
РегЗад в дереве метаданных.После отключения клиентской компиляции весь код общего модуля будет компилироваться только на сервере 1С. Поскольку сервер имеет полный доступ к СУБД и объектной модели, компиляция пройдет успешно, и ошибка исчезнет.
Иногда начинающие разработчики, пытаясь исправить ошибки компиляции, начинают изменять синтаксис вызова методов. Распространенной ошибкой является попытка вызвать функцию через имя ее же общего модуля внутри самого этого модуля:
СтрокаКарты = РегЗад.ПолучитьСтрокиКарт(СтрокаРазделителя); // Неверно!
Разберем, почему так делать не нужно и к каким последствиям это приводит:
ПолучитьСтрокиКарт(СтрокаРазделителя).РегЗад на ФоновыеПроцедуры), то вызовы внутри модуля, написанные жестким кодом с использованием старого имени, сломаются. При прямом вызове код останется работоспособным.Правило: Внутри одного и того же общего модуля всегда вызывайте функции напрямую по их имени, без указания имени модуля.
Даже если мы полностью устраним синтаксические ошибки, код автора темы не сможет нормально работать в реальном регламентном задании. Обратим внимание на следующую строку:
ЗаписьXML.ОткрытьФайл("C:\Users\User\Desktop\Новаяпапка\Список_дисконт.xml");
Регламентные задания в системе 1С всегда выполняются в фоновом режиме на стороне сервера 1С:Предприятия. Серверный процесс функционирует в контексте специальной операционной системы и запускается под служебной учетной записью (например, usr1cv8 на Windows или 1cv8 на Linux).
При попытке выполнить данный код на сервере возникнет критическая ошибка выполнения по нескольким причинам:
C:\Users\User\Desktop...), так как этот рабочий стол принадлежит конкретному интерактивному пользователю, а не службе сервера.Рассмотрим, как правильно организовать работу с файлами в фоновых заданиях. Проанализируем два основных сценария:
Если файл создается временно для отправки по электронной почте, выгрузки на FTP или передачи в веб-сервис, его необходимо создавать в специализированной временной директории сервера с помощью метода ПолучитьИмяВременногоФайла().
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xml");
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла);
// ... код заполнения XML ...
ЗаписьXML.Закрыть();
// Далее отправляем файл по назначению и обязательно удаляем его
УдалитьФайлы(ИмяВременногоФайла);
Если файл должен быть доступен пользователям сети, его следует сохранять в сетевой ресурс по UNC-пути. При этом у учетной записи, под которой запущена служба сервера 1С, должны быть предоставлены полные права (чтение и запись) на эту сетевую папку.
ПутьКФайлу = "\\ФайловыйСервер\ОбщаяПапка\Список_дисконт.xml";
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ПутьКФайлу);
// ... код заполнения XML ...
ЗаписьXML.Закрыть();
Поскольку регламентные задания выполняются без участия пользователя, любые возникающие в них ошибки выполнения (исключения) не выводятся на экран. Если в процессе выгрузки заблокируется таблица базы данных или пропадет доступ к сетевому диску, задание просто тихо завершится со статусом «Ошибка».
Для обеспечения надежности и возможности мониторинга обязательно оборачивайте выполнение критических операций в конструкцию Попытка ... Исключение и фиксируйте детальную информацию в Журнале регистрации.
Модифицируем нашу процедуру ЗаписьДанных с учетом всех стандартов разработки:
Процедура ЗаписьДанныхЭкспорт() Экспорт
// 1. Формируем безопасный путь для записи файла на сервере
ИмяФайла = "\\Server-Data\Exchange\Список_дисконт.xml";
СтрокаРазделителя = "*";
ЗаписьXML = Новый ЗаписьXML;
Попытка
ЗаписьXML.ОткрытьФайл(ИмяФайла);
Исключение
// Фиксируем ошибку открытия файла в Журнале регистрации
ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаДисконтныхКарт",
УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось открыть файл для записи по пути: " + ИмяФайла + Символы.ПС + ТекстОшибки);
Возврат;
КонецПопытки;
Попытка
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("ContragentList");
ЗаписьXML.ЗаписатьАтрибут("ИмяСправочника", "Контрагенты");
ЗаписьXML.ЗаписатьКомментарий("Выгрузка элементов справочника");
// Прямой вызов функции внутри этого же модуля
СтрокаКарты = ПолучитьСтрокиКарт(СтрокаРазделителя);
СтрокиКарт = СтрРазделить(СтрокаКарты, Символы.ПС);
Для ТекущаяСтрока = 0 По СтрокиКарт.Количество() - 1 Цикл
Данные = СтрРазделить(СтрокиКарт[ТекущаяСтрока], СтрокаРазделителя);
// Защита от пустых строк
Если Данные.Количество() < 3 Тогда
Продолжить;
КонецЕсли;
ЗаписьXML.ЗаписатьНачалоЭлемента("ЭлементСправочника");
ЗаписьXML.ЗаписатьНачалоЭлемента("Код");
ЗаписьXML.ЗаписатьТекст(Данные[0]);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Наименование");
ЗаписьXML.ЗаписатьТекст(Данные[1]);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("ДатаРождения");
ЗаписьXML.ЗаписатьТекст(Данные[2]);
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента(); // ЭлементСправочника
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // ContragentList
ЗаписьXML.Закрыть();
// Информационная запись об успешном выполнении
ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаДисконтныхКарт",
УровеньЖурналаРегистрации.Информация, , ,
"Выгрузка дисконтных карт успешно завершена. Файл сохранен: " + ИмяФайла);
Исключение
// Закрываем файл в случае ошибки, чтобы не блокировать его в ОС
ЗаписьXML.Закрыть();
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("РегламентныеЗадания.ВыгрузкаДисконтныхКарт",
УровеньЖурналаРегистрации.Ошибка, , ,
"Произошла критическая ошибка при выгрузке данных: " + Символы.ПС + ТекстОшибки);
КонецПопытки;
КонецПроцедуры
Использование предложенных подходов гарантирует стабильную компиляцию общего модуля, корректность выполнения кода регламентными заданиями на стороне сервера и своевременное логирование любых нештатных ситуаций в Журнале регистрации 1С.