При разработке внешних компонент для платформы 1С 7.7 с использованием современных инструментов, таких как .NET Framework и язык C#, разработчики часто сталкиваются с неприятной особенностью: после завершения работы с объектом и его удаления в коде 1С, занимаемая процессом оперативная память не уменьшается. Давайте детально проанализируем ситуацию, выясним причины такого поведения системы и рассмотрим возможные пути решения этой проблемы.
Рассмотрим ситуацию, когда мы создаем объект СоздатьОбъект("МойКомОбъект"). В этот момент операционная система загружает вашу библиотеку, но так как она написана на управляемом коде (C#), внутри процесса 1cv7s.exe инициализируется среда выполнения .NET. Проанализируем, почему это приводит к резкому скачку потребления памяти:
mscorlib).Таким образом, увеличение потребления памяти с 7 МБ до 26 МБ и более — это "входной билет" за использование возможностей .NET в старой среде 1С 7.7.
Выясним, почему стандартная операция Объект = 0 в языке 1С не приводит к очистке памяти. В .NET существует понятие AppDomain (домен приложения). Сборки (DLL) загружаются в конкретный домен. По умолчанию COM-объекты создаются в Default AppDomain.
Важный нюанс: В .NET Framework невозможно выгрузить отдельную сборку (DLL) из домена. Можно только выгрузить весь домен целиком. Поскольку 1С использует стандартный механизм COM-Interop, она работает с дефолтным доменом, который живет столько же, сколько сама 1С. Поэтому, даже если счетчик ссылок COM-объекта обнуляется, сама сборка остается "висеть" в памяти процесса.
Проанализируем поведение системы при использовании компонентов на VB6 или Delphi. Автор темы заметил, что даже нативные компоненты не всегда выгружаются мгновенно. Это связано с тем, как Windows управляет COM-библиотеками.
Когда вы выполняете Объект = 0, счетчик ссылок внутри DLL становится равным нулю. Однако Windows не удаляет DLL из памяти немедленно. Она ждет, пока хост-приложение (в нашем случае 1С) вызовет системную функцию CoFreeUnusedLibraries. Эта функция опрашивает все загруженные библиотеки через экспорт DllCanUnloadNow. Если библиотека отвечает "Да", она выгружается.
Проблема 1С 7.7: Платформа 7.7 крайне редко или вообще не вызывает очистку неиспользуемых библиотек в процессе активной работы пользователя. Обычно это происходит только при закрытии программы.
При создании полноценной Внешней Компоненты (ВК), а не простого 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С может "зависнуть" в памяти даже после закрытия окна программы.
Если задача требует обязательной очистки памяти без перезагрузки 1С, рассмотрим продвинутый метод с использованием Wrapper-архитектуры:
AppDomain.iTextSharp для PDF) загружаются именно в этот временный домен.AppDomain.Unload() для временного домена.При таком подходе память, занятая библиотеками обработки PDF, будет возвращена операционной системе, хотя базовая память, занятая самим Wrapper-ом и CLR, всё равно останется привязанной к процессу 1С.
Разберем попытку автора вручную управлять ссылками через 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) всей системы.
Подводя итог нашему анализу, можно сделать следующие выводы:
Done интерфейса IInitDone обнуляйте переменную pConnection, чтобы не блокировать завершение процесса 1С.Таким образом, мы выяснили, что "невыгрузка" компоненты — это не ошибка программиста, а фундаментальное свойство взаимодействия двух разных технологических миров: нативного COM 1С 7.7 и управляемого кода .NET.