Разработчики систем на платформе 1С:Предприятие часто сталкиваются со сбоями при использовании внешних компонент (ВК), написанных на сторонних языках программирования, в частности на Delphi. Проблема обычно проявляется внезапно: при запуске метода компоненты, создании объекта через конструктор или даже при закрытии программы 1С аварийно завершает работу без объяснения причин. В системных логах Windows при этом фиксируется ошибка типа BEX с кодом исключения c0000005 (Access Violation).
В этой статье мы подробно разберем, почему возникает данная проблема, проанализируем внутренние механизмы операционной системы и платформы 1С, а также рассмотрим несколько практических способов ее устранения — от быстрого исправления текущего исходного кода COM-компоненты до ее полной модернизации с использованием современной технологии Native API.
Для начала проанализируем сигнатуру системного сбоя, который получает разработчик в операционной системе Windows:
Основная причина такого поведения кроется в конфликте компоненты с аппаратным и программным механизмом защиты операционной системы — DEP (Data Execution Prevention / Предотвращение выполнения данных).
Старые версии компиляторов Delphi при создании динамических библиотек (DLL) для COM-объектов генерировали в оперативной памяти специальные промежуточные структуры кода — так называемые «переходники» (thunking/thunks). Эти структуры создавались непосредственно в куче (heap) или стеке (stack) процесса для связывания системных вызовов с объектами Delphi.
Начиная с Windows Vista и Windows 7, операционная система по умолчанию жестко разделяет страницы памяти процесса на исполняемые и неисполняемые. Платформа 1С:Предприятие, начиная с версии 8.2, скомпилирована разработчиками с флагом совместимости NX (No-Execute), что обязывает ОС блокировать любые попытки выполнения машинного кода из неисполняемых сегментов. Как только внешняя компонента пытается передать управление на сгенерированный в куче thunk-переходник, служба безопасности Windows немедленно прерывает выполнение потока процесса 1cv8.exe, генерируя исключение c0000005.
Это также объясняет, почему та же самая компонента могла годами абсолютно стабильно работать на платформе 1С:Предприятие 7.7. Устаревшая платформа 1С 7.7 запускалась в операционных системах старого поколения без жесткого контроля DEP, где выполнение инструкций из сегментов данных свободно допускалось архитектурой.
Если ваша внешняя компонента написана с использованием классических шаблонов COM-компонент для 1С (например, на основе широко известного шаблона от Александра / Chieftain), то корень проблемы часто кроется в некорректном управлении счетчиком ссылок COM-объектов платформы.
Рассмотрим структуру инициализации глобального контекста 1С в файле библиотеки AddInLib.pas. В классическом варианте шаблона присутствует следующий фрагмент кода:
if GlobalContext then begin
obj := OleVariant(p1cv7).AppDispatch; // Пометка (!)
// IDispatch(obj)._AddRef;
end;
Проанализируем, что здесь происходит. Метод AppDispatch возвращает COM-интерфейс к глобальному контексту 1С. При вызове этого свойства платформа 1С увеличивает внутренний счетчик ссылок COM-объекта. Если в коде Delphi-компоненты принудительно вызывается метод _AddRef, а в деструкторе или при выгрузке библиотеки этот счетчик корректно не уменьшается с помощью парного метода _Release, то нарушается баланс ссылок.
В результате этого при попытке уничтожить объект компоненты на стороне 1С (или при закрытии приложения):
c0000005.Решение проблемы на уровне COM-шаблона: если вам необходимо остаться на технологии COM, найдите указанную выше строку в файлах исходного кода вашей компоненты. Закомментируйте или удалите строку вызова _AddRef, а также тщательно проверьте методы Init и Done вашей компоненты на предмет освобождения всех используемых интерфейсов IDispatch и IUnknown. Управление временем жизни объектов должно быть полностью передано автоматическому механизму самой платформы 1С.
Использование технологии COM для разработки внешних компонент в современных реалиях считается устаревшим подходом. Наиболее надежным, безопасным и кроссплатформенным решением является перевод вашей компоненты на технологию Native API.
Рассмотрим подробнее преимущества Native API перед архитектурой COM:
IUnknown.regsvr32, что часто блокируется современными политиками безопасности Windows 10/11 и механизмами UAC (User Account Control). Компоненты Native API не требуют регистрации в ОС и загружаются непосредственно из макета конфигурации 1С.Давайте рассмотрим, как должна выглядеть базовая структура экспортируемых функций внешней компоненты Native API на языке Delphi.
В основном файле проекта библиотеки (файле .dpr) должны быть экспортированы четыре стандартные функции:
library MyNativeExtension;
uses
SysUtils,
Classes,
MyExtensionClass in 'MyExtensionClass.pas';
exports
GetClassObject,
DestroyObject,
RegisterExtensionAs,
GetInterfaceVersion;
begin
end.
Рассмотрим назначение этих интерфейсных функций:
GetInterfaceVersion — сообщает платформе 1С версию поддерживаемого компонентой интерфейса Native API (на текущий момент возвращается значение 1).RegisterExtensionAs — регистрирует имя компоненты, под которым она будет доступна во встроенном языке 1С.GetClassObject — фабрика классов, которая вызывается платформой 1С для создания экземпляра вашего класса расширения.DestroyObject — уничтожает экземпляр класса и освобождает всю выделенную им память.Класс компоненты на Delphi должен наследоваться от интерфейсов IInitDoneBase и ILanguageExtenderBase. Пример описания методов инициализации класса:
type
TMyExtension = class(TInterfacedObject, IInitDoneBase, ILanguageExtenderBase)
public
{ IInitDoneBase }
function Init(pConnection: IDispatch): Boolean; stdcall;
function Done: Boolean; stdcall;
function GetInfo(var pInfo: PAnsiChar): Boolean; stdcall;
{ ILanguageExtenderBase }
function RegisterExtensionAs(var pExtensionName: PWideChar): Boolean; stdcall;
// ... Реализация методов работы со свойствами и вызовами методов компоненты
end;
Использование строго типизированных интерфейсов Native API гарантирует, что 1С и DLL-библиотека будут использовать единый менеджер памяти, что полностью исключает падение платформы с ошибками обращения к памяти.
Начиная с версии платформы 1С:Предприятие 8.3.18, разработчики добавили мощный инструмент для защиты стабильности работы системы — изолированное подключение внешних компонент.
Даже если внешняя компонента написана сторонним разработчиком и содержит критические ошибки переполнения буфера или утечки памяти, вы можете предотвратить падение основного процесса 1С (клиентского приложения или рабочего процесса сервера rphost). Для этого используется специальный параметр при подключении компоненты.
Рассмотрим пример кода на встроенном языке 1С для безопасного подключения ВК:
// Определим параметры подключения
ИмяФайлаВК = "MyNativeExtension.dll";
ИмяПути = ПолучитьИмяВременногоФайла();
// Копируем ВК из макета во временную папку
МакетВК = Справочники.ВнешниеКомпоненты.НайтиПоРеквизиту("Имя", "МояКомпонента").ХранилищеКомпоненты.Получить();
МакетВК.Записать(ИмяПути);
// Подключаем компоненту в изолированном режиме
РезультатПодключения = ПодключитьВнешнююКомпоненту(ИмяПути, "MyExtension", ТипПодключенияВнешнейКомпоненты.Изолированно);
Если РезультатПодключения Тогда
Попытка
ОбъектВК = Новый("AddIn.MyExtension.MyClass");
ОбъектВК.ВыполнитьДействие();
Исключение
ЗаписьЖурналаРегистрации("ВнешняяКомпонента", УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ПоказатьПредупреждение(, "Произошел критический сбой при работе с компонентой!");
КонецПопытки;
Иначе
Сообщить("Не удалось подключить внешнюю компоненту.");
КонецЕсли;
Разберем, как работает изолированный режим:
ТипПодключенияВнешнейКомпоненты.Изолированно платформа 1С запускает отдельный служебный хост-процесс операционной системы.c0000005), аварийно завершается только этот изолированный хост-процесс.Попытка... Исключение, сохранив сеанс пользователя и целостность данных в информационной базе.Таким образом, сочетание исправления исходного кода компоненты, перехода на современный стандарт Native API и использования механизма изолированного выполнения позволяет полностью решить проблему падений 1С при работе с внешними компонентами и гарантировать высокую стабильность работы вашей системы.