При расширении возможностей 1С разработчики нередко прибегают к созданию собственных внешних библиотек на языке C#. Это позволяет задействовать функции операционной системы Windows, работать с низкоуровневыми вызовами WinAPI или реализовывать нестандартную логику интерфейса. Однако на этапе разработки и подключения COM-библиотеки часто возникают типовые проблемы: ошибка 0x80004002 (Интерфейс не поддерживается), сложности с поиском сборки в системе и особенности поиска дочерних окон внутри платформы 1С. Разберем по шагам, как правильно реализовать, зарегистрировать и использовать такую библиотеку.
Ошибка -2147467262(0x80004002): Интерфейс не поддерживается при вызове конструктора Новый COMОбъект(...) возникает в тех случаях, когда платформа 1С не может получить доступ к интерфейсу позднего связывания IDispatch. Платформа 1С обращается к методам и свойствам COM-объектов динамически, поэтому класс C# должен явно экспортировать этот интерфейс.
Для корректного взаимодействия класса с платформой 1С необходимо соблюдать следующие требования:
public) и иметь конструктор по умолчанию без параметров.[ComVisible(true)].[Guid("...")] и программный идентификатор [ProgId("...")].[ClassInterface(ClassInterfaceType.AutoDispatch)] или [ClassInterface(ClassInterfaceType.AutoDual)], чтобы среда .NET автоматически сформировала таблицу диспетчеризации для COM.Рассмотрим пример создания класса-обертки WndHlp, который экспортирует в 1С основные функции WinAPI для поиска окон, изменения их координат, стилей и взаимодействия через системные сообщения:
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace _1CAddInNET
{
[ComVisible(true)]
[ProgId("1CAddInNET.WndHlp")]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[Guid("A497AB77-85C3-4F32-839D-7EDA0F5F89C8")]
public class WndHlp
{
#region Private members
private IntPtr m_hWnd = IntPtr.Zero;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool PostMessage(IntPtr hWnd, int nMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr wParam, IntPtr wParamChildAfter, string cClass, string cTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool ShowWindow(IntPtr hWnd, int nMode);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWnd, IntPtr nWhdParent);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern uint SetWindowLong(IntPtr hWnd, int nStyle, int nParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr nOrder, int nX, int nY, int nW, int nH, uint nMode);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder cClass, int nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowRect(IntPtr hWnd, ref RECT rc);
#endregion
public int hWnd
{
get { return m_hWnd.ToInt32(); }
set { m_hWnd = new IntPtr(value); }
}
public bool PostMsg(int nMsg, int wParam, int lParam)
{
return PostMessage(m_hWnd, nMsg, wParam, lParam);
}
public bool FindWnd(int wParam, int wParamChildAfter, string cClass, string cTitle)
{
m_hWnd = FindWindowEx(new IntPtr(wParam), new IntPtr(wParamChildAfter), cClass, cTitle);
return m_hWnd != IntPtr.Zero;
}
public bool ShowWnd(int nMode)
{
return ShowWindow(m_hWnd, nMode);
}
public bool SetParentWnd(int nWhdParent)
{
SetParent(m_hWnd, new IntPtr(nWhdParent));
return true;
}
public int GetParentWnd()
{
return GetParent(m_hWnd).ToInt32();
}
public bool IsVisibleWnd()
{
return IsWindowVisible(m_hWnd);
}
public bool SetWndLong(int nStyle, int nParam)
{
SetWindowLong(m_hWnd, nStyle, nParam);
return true;
}
public bool SetWndPos(int nOrder, int nX, int nY, int nW, int nH, uint nMode)
{
SetWindowPos(m_hWnd, new IntPtr(nOrder), nX, nY, nW, nH, nMode);
return true;
}
public string GetClassNameWnd()
{
StringBuilder cClass = new StringBuilder(255);
GetClassName(m_hWnd, cClass, cClass.Capacity);
return cClass.ToString();
}
public bool BringWndToTop()
{
return BringWindowToTop(m_hWnd);
}
public bool SetForegroundWnd()
{
return SetForegroundWindow(m_hWnd);
}
}
}
Для того чтобы 1С могла загрузить сборку без обязательного копирования файла в каталог платформы bin, библиотеку нужно зарегистрировать в реестре Windows с указанием пути к файлу. Разберем особенности регистрации:
RegAsm.exe. Обязательно используйте флаг /codebase, который прописывает полный путь к сборке в реестре. Без этого параметра Windows пытается найти файл DLL только в рабочей папке вызывающего процесса (в каталоге bin платформы 1С).Если используется 32-разрядный клиент 1С, сборка должна регистрироваться через 32-битную версию RegAsm.exe:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe "C:\MyProjects\1CAddInNET.dll" /codebase /tlb
Для 64-разрядного клиента регистрацию выполняют через Framework64:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm.exe "C:\MyProjects\1CAddInNET.dll" /codebase /tlb
После успешной регистрации библиотеки обращение к ней из встроенного языка 1С происходит стандартным образом:
Попытка
ОберткаОкон = Новый COMОбъект("1CAddInNET.WndHlp");
Исключение
Сообщить("Не удалось инициализировать COM-объект: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
При поиске внутренних окон 1С разработчики часто сталкиваются с тем, что функция FindWindowEx не находит окно конкретной формы, если в качестве родительского окна передается главное окно программы. Проанализируем причину такого поведения:
V8TopLevelFrame. Они вложены в специальное системное окно с классом MDIClient.MDIClient, и уже внутри MDIClient выполняют поиск окна по его заголовку или классу.SetWindowPos новые параметры могут сброситься при перерисовке формы, изменении размеров главного окна или переключении страниц.