Как исправить ошибку 0x80020008 (DISP_E_BADVARTYPE) при работе с COMConnector 1С из C++?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При интеграции внешних приложений на языке C++ с платформой 1С:Предприятие часто используется компонент COMConnector (например, V82.COMConnector или V83.COMConnector). Однако при низкоуровневой работе с интерфейсом IDispatch разработчики нередко сталкиваются с ошибкой 0x80020008 (DISP_E_BADVARTYPE — «Один из аргументов метода не является допустимым типом variant») при попытке прочитать свойство или вызвать метод объекта (например, получить Метаданные или Версия).

Разберем по шагам, почему возникает эта ошибка и как корректно вызывать свойства и методы объектов 1С через COM в Visual C++.

Причина возникновения ошибки DISP_E_BADVARTYPE

Проанализируем механику работы вызова метода IDispatch::Invoke. При получении свойства через флаг DISPATCH_PROPERTYGET или вызове метода через DISPATCH_METHOD вызывающая сторона передает указатель на структуру VARIANT, куда сервер автоматизации должен записать результат (параметр pVarResult).

Согласно стандартам COM/OLE Automation, реализация метода внутри COM-сервера 1С перед записью нового результата в выходной VARIANT вызывает стандартную системную функцию VariantClear. Это необходимо для того, чтобы освободить предыдущие ресурсы, если в переменной уже хранились динамические данные (например, интерфейсные указатели VT_DISPATCH или строки VT_BSTR).

Если локальная переменная VARIANT была объявлена на стеке без предварительного обнуления, поле vt содержит случайный «мусор» из оперативной памяти. Функция VariantClear обнаруживает некорректный идентификатор типа и возвращает ошибку DISP_E_BADVARTYPE (код 0x80020008), прерывая выполнение вызова Invoke.

Пошаговое решение: правильная инициализация структуры VARIANT

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

Рассмотрим последовательность действий:

  1. Обнуление типа данных: Самый простой способ — явно присвоить типу пустое значение: vRetMeta.vt = VT_EMPTY; (или vRetMeta.vt = 0;).
  2. Использование стандартной функции API: Рекомендуемым по стандарту COM способом является вызов системной функции VariantInit(&vRetMeta);.
  3. Очистка после использования: После того как полученные данные обработаны, необходимо вызвать VariantClear(&vRetMeta); для корректного освобождения памяти и уменьшения счетчика ссылок COM-объекта.

Посмотрим на пример исправленного блока кода обращения к свойству Метаданные:


VARIANT vRetMeta;
VariantInit(&vRetMeta); // Обязательная инициализация перед вызовом!

DISPPARAMS argsMeta = { NULL, NULL, 0, 0 }; // Для получения свойства без параметров
DISPID dispID;
BSTR MethodName = L"Метаданные";

hr = connector->GetIDsOfNames(IID_NULL, &MethodName, 1, 0, &dispID);
if (SUCCEEDED(hr))
{
    EXCEPINFO exs;
    hr = connector->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &argsMeta, &vRetMeta, &exs, NULL);
    
    if (SUCCEEDED(hr) && vRetMeta.vt == VT_DISPATCH)
    {
        IDispatch* pMetadata = vRetMeta.pdispVal;
        std::cout << "Интерфейс IDispatch метаданных успешно получен" << std::endl;
        
        // Дальнейшая работа с объектом метаданных...
        
        // Освобождаем полученный IDispatch
        pMetadata->Release();
    }
}
VariantClear(&vRetMeta);

Рекомендации по безопасной работе с COM в C++

Ручной вызов методов через IDispatch::Invoke, ручное заполнение DISPPARAMS и управление счетчиками ссылок через AddRef/Release трудоемки и подвержены утечкам памяти. Рассмотрим более надежные и современные подходы:

  1. Использование директивы #import: Visual C++ позволяет импортировать библиотеку типов 1С (comcntr.dll). Компилятор автоматически сгенерирует строго типизированные C++ классы-обертки (_com_ptr_t, _variant_t, _bstr_t), благодаря чему обращение к базе 1С станет лаконичным:
    
    #import "C:\Program Files\1cv8\8.3.XX.XXXX\bin\comcntr.dll" rename("EOF", "V8EOF")
    
    try {
        V83::IConnectorPtr pConnector(__uuidof(V83::COMConnector));
        IDispatchPtr pConnection = pConnector->Connect(L"File=\"D:\\База1С\";Usr=\"Admin\";");
        
        // Прямое чтение свойств через высокоуровневые обертки
        _variant_t vMetadata = pConnection->GetProperty(L"Метаданные");
        // Обработка данных...
    }
    catch (_com_error& e) {
        std::wcout << L"Ошибка COM: " << e.ErrorMessage() << std::endl;
    }
    
  2. Классы-обертки ATL: Если проект использует ATL, рекомендуется применять классы CComPtr, CComVariant и CComDispatchDriver. Метод CComDispatchDriver::GetPropertyByName берет на себя вызовы GetIDsOfNames, инициализацию структур и вызов Invoke.
  3. Учет разрядности приложения: Библиотека comcntr.dll является внутрипроцессным COM-сервером (in-process). 32-битное приложение C++ должно использовать 32-битный COM-коннектор 1С, а 64-битное приложение — 64-битный. Если разрядности различаются, создание объекта завершится ошибкой, либо потребуется настройка выделенного COM+-приложения в службе компонентов Windows.
  4. Альтернативные интерфейсы интеграции: При разработке новых модулей обмена данными рекомендуется вместо COM использовать встроенные HTTP-сервисы или протокол OData платформы 1С. Это избавляет проект на C++ от привязки к Windows COM, версионности DLL и разрядности процессов.
← На главную