Почему внешняя компонента COM на C# не выгружается из памяти 1С 7.7 и как с этим бороться?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При разработке внешних компонент для платформы 1С 7.7 с использованием современных инструментов, таких как .NET Framework и язык C#, разработчики часто сталкиваются с неприятной особенностью: после завершения работы с объектом и его удаления в коде 1С, занимаемая процессом оперативная память не уменьшается. Давайте детально проанализируем ситуацию, выясним причины такого поведения системы и рассмотрим возможные пути решения этой проблемы.

Причина 1: Особенности среды выполнения CLR (Common Language Runtime)

Рассмотрим ситуацию, когда мы создаем объект СоздатьОбъект("МойКомОбъект"). В этот момент операционная система загружает вашу библиотеку, но так как она написана на управляемом коде (C#), внутри процесса 1cv7s.exe инициализируется среда выполнения .NET. Проанализируем, почему это приводит к резкому скачку потребления памяти:

Таким образом, увеличение потребления памяти с 7 МБ до 26 МБ и более — это "входной билет" за использование возможностей .NET в старой среде 1С 7.7.

Причина 2: Архитектура доменов приложений (AppDomain)

Выясним, почему стандартная операция Объект = 0 в языке 1С не приводит к очистке памяти. В .NET существует понятие AppDomain (домен приложения). Сборки (DLL) загружаются в конкретный домен. По умолчанию COM-объекты создаются в Default AppDomain.

Важный нюанс: В .NET Framework невозможно выгрузить отдельную сборку (DLL) из домена. Можно только выгрузить весь домен целиком. Поскольку 1С использует стандартный механизм COM-Interop, она работает с дефолтным доменом, который живет столько же, сколько сама 1С. Поэтому, даже если счетчик ссылок COM-объекта обнуляется, сама сборка остается "висеть" в памяти процесса.

Причина 3: Механизм CoFreeUnusedLibraries в COM

Проанализируем поведение системы при использовании компонентов на VB6 или Delphi. Автор темы заметил, что даже нативные компоненты не всегда выгружаются мгновенно. Это связано с тем, как Windows управляет COM-библиотеками.

Когда вы выполняете Объект = 0, счетчик ссылок внутри DLL становится равным нулю. Однако Windows не удаляет DLL из памяти немедленно. Она ждет, пока хост-приложение (в нашем случае 1С) вызовет системную функцию CoFreeUnusedLibraries. Эта функция опрашивает все загруженные библиотеки через экспорт DllCanUnloadNow. Если библиотека отвечает "Да", она выгружается.

Проблема 1С 7.7: Платформа 7.7 крайне редко или вообще не вызывает очистку неиспользуемых библиотек в процессе активной работы пользователя. Обычно это происходит только при закрытии программы.

Реализация интерфейса IInitDone и работа с GUID

При создании полноценной Внешней Компоненты (ВК), а не простого COM-объекта, мы обязаны реализовать интерфейс IInitDone. Разберем вопрос автора о том, почему GUID должен быть строго определенным.

Для того чтобы 1С могла "узнать" в вашем объекте именно внешнюю компоненту, она запрашивает у объекта конкретный интерфейс по его идентификатору. Если вы не укажете Guid явно в коде C#, среда .NET сгенерирует его автоматически, и 1С просто не сможет с ним взаимодействовать. Рассмотрим пример правильного объявления интерфейса:


[Guid("AB634001-F13D-11d0-A459-004095E1DAEA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInitDone
{
    void Init([MarshalAs(UnmanagedType.IDispatch)] object pConnection);
    void Done();
    void GetInfo([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] ref object[] pInfo);
}

В методе Done() мы должны обязательно освобождать все ссылки на объекты 1С, которые мы сохранили во время работы. Если этого не сделать, процесс 1С может "зависнуть" в памяти даже после закрытия окна программы.

Как можно добиться выгрузки кода .NET?

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

  1. Создаем небольшую "прослойку" (Wrapper) на C#, которая регистрируется как COM-объект.
  2. Эта прослойка при инициализации программно создает новый AppDomain.
  3. Основная логика и тяжелые библиотеки (например, iTextSharp для PDF) загружаются именно в этот временный домен.
  4. Когда в 1С вызывается метод завершения работы, прослойка вызывает AppDomain.Unload() для временного домена.

При таком подходе память, занятая библиотеками обработки PDF, будет возвращена операционной системе, хотя базовая память, занятая самим Wrapper-ом и CLR, всё равно останется привязанной к процессу 1С.

Работа со счетчиками ссылок (Marshal.Release)

Разберем попытку автора вручную управлять ссылками через Marshal.Release. Посмотрим на пример кода, который часто пытаются использовать:


// Получение указателя на самого себя
IntPtr pointer = Marshal.GetIUnknownForObject(this);
// Попытка принудительного уменьшения счетчика
int count = Marshal.Release(pointer);

Важно понимать: Ручное манипулирование счетчиками ссылок в управляемом коде крайне опасно. .NET сама управляет временем жизни объектов через Runtime Callable Wrapper (RCW) и COM Callable Wrapper (CCW). Принудительный вызов Release может привести к тому, что 1С попытается обратиться к объекту, который уже помечен как удаленный в среде .NET, что вызовет неминуемый крах (Access Violation) всей системы.

Итоги и рекомендации

Подводя итог нашему анализу, можно сделать следующие выводы:

Таким образом, мы выяснили, что "невыгрузка" компоненты — это не ошибка программиста, а фундаментальное свойство взаимодействия двух разных технологических миров: нативного COM 1С 7.7 и управляемого кода .NET.

← На главную