Книга знаний

1С:Предприятие / Приемы программирования / Внешние компоненты

Написание внешних компонент для 1С на VB.NET и C# (статья)

Даны примеры внешних компонент на VB.NET и C#Автор статьи: Asmody | Редакторы: romix, Волшебник,
Последняя редакция №8 от 17.02.06 | История
URL: http://kb.mista.ru/article.php?id=56

Ключевые слова: внешняя, компонента, VB.NET, C#, COM, XML, XmlTextReader, XmlTextWriter, .NET


COM-интерфейс внешних компонент подробно описан в «коробочном» продукте фирмы 1С «Технология создания внешних компонент». По цене примерно 25 банок пива вам продадут небольшую желтую коробку с CD, желтой анкетой и тонкой, но умной, желтой книжкой (из которой, впрочем, неподготовленный читатель поймет очень немного). Из указанного комплекта вы можете сразу запускать приложенные примеры ВК, и исследовать их работу, добавляя отладочную печать. Если же вам жалко тратить такое количество пива на образцы компонент, которые (в случае .NET) даже не компилируются (или это только у меня так?), скачайте работающие образчики ВК в конце этой статьи. Образцы основаны на вышеуказанной разработке 1С и примерах (с) Igor Kissil. Перевод шаблона компоненты с VB.NET на язык C# - (c) Asmody.

Почему именно .NET?


Во-первых, среда разработки намного проще для новичков и ограниченных во времени корпоративных пользователей.

Пример: нажатие  Ctrl-F1 всегда дает «правильную» контекстную подсказку с учетом названия типа. Устаревшие среды разработки (включая, к сожалению, 1С 7.7/8.0) во многих случаях «выкатят» вам «на выбор» множество различных вариантов подсказки. Данный вариант ответа «вопросом на вопрос» означает, что среда сама не в состоянии определить тип своей же переменной, и грузит этим «лабиринтом отражений» начинающего программиста. В качестве простого примера наберите в 1С:Предприятие 8.0 (у меня версия 8.0.12.21) следующий код:

   сп=Новый СписокЗначений();
   сп.Добавить();

Контекстная подсказка на слове Добавить выдаст 57 различных вариантов подсказки по разным объектам: новичок, по идее, должен безошибочно выбрать из них нужный, просмотрев все эти «фальшивые зеркала» сверху вниз. Среда разработки .NET сама определяет тип переменной, что сильно облегчает жизнь новичку.

Во-вторых, .NET помогает писать качественный и устойчивый софт.

Например, библиотека классов .NET возвращает исключения при программных ошибках. Это позволяет сделать ваш код, который «честно» отрабатывает все ошибочные ситуации, более четким и ясным. Попробуйте сделать то же самое с кодами возврата (это другой способ возвращать ошибки, который вы встретите в устаревших средах разработки) – и вы просто запутаетесь в ветвлениях программы. Или сделаете проверки менее «честными», поскольку раздувать код в несколько раз, чтобы вести запутанный контроль ошибок, способны, пожалуй, только китайцы и индусы (восточная философия предполагает созерцательное отношение к жизни).  Исключения (exceptions) избавят вас от этой проблемы – код проверки ошибок с использованием исключений - намного более ясный и компактный.

Кроме того, среда исполнения .NET отличается тем, что вы не сможете определить строку «не того размера», не освободить память после уничтожения локальной переменной или обратиться по неинициализированному указателю. Это типичные ошибки C-программистов, возможность устроить которые просто не существует (или глубоко спрятана) в более качественных и продуктивных средах разработки, таких как .NET.

В-третьих, доступна обширная и понятная документация MSDN/SDK
Написанная не для экспертов в английской словесности, а для людей «со словарем». Я не особо силен в английском, и Шекспир c Толкиеном в подлиннике для меня – китайская грамота.  Но в текстах MSDN, обильно снабженных работающими примерами кода, все более или менее ясно.

Особенности среды исполнения


На клиенте необходимо установить библиотеку среды исполнения CLR (порядка 10 мегабайт); в новых версиях Windows она уже стоит). Система Windows 98 находится в списке поддерживаемых ОС для платформы .NET, и под нее тоже можно исполнять «дот-нетовский» код.

Система .NET исполняет по-особому скомпилированный двоичный код, но не является интерпретатором (перекомпилирует сборку на клиенте). Перевод кода сборки (EXE или DLL) в двоичный код конкретного микропроцессора производится на клиентской машине (один раз, при первом запуске сборки). Это позволяет учесть особенности оборудования (а не ориентироваться на устаревшие системы типа 80386) и анализировать безопасность поступающих извне сборок (обезвреживать типичные для C-кода атаки на систему).

Пример внешней компоненты


В качестве полезного примера мы напишем внешнюю компоненту, которая работает с текстовыми файлами, размеченными в формате XML. Пример достаточно прост (фактически, ничего не делает, а только предоставляет интерфейс), выявляет типичные трудности и приемы работы, которые вы можете встретить при создании интерфейсных внешних компонент и, кроме того, делает полезную вещь – предоставляет программистам 1С 7.7 упрощенный способ для работы с XML, похожий на XML-интерфейс 1С 8.0.

Например, компонента может читать и записывать текстовые файлы такого вида:

<Товар Наименование= «Пиво Клинское 0,33» Цена= «18»/>
<Товар Наименование= «Пиво Жигулевское 0,5» Цена= «16»/>

Подобный формат разметки очень удобен для реализации обмена между информационными базами (например, когда необходимо быстро перенести справочник сотрудников, справочник товаров или какие-либо документы – этот вопрос появляется на Интернет-форумах по 1С, пожалуй, чаще других).

Замечу, что средства компоненты v7plus, предназначенные для этой цели, не всегда подходят разработчику, поскольку код с их использованием сложнее для восприятия, и на больших выгрузках они сильно (раз в 10 больше размера исходного XML) грузят память, что порой приводит к ситуации «глубокого погружения» вашего компьютера в виртуальность. Зачем нужно грузить все в память, я не знаю, но догадываюсь, что придумать подобное могли только настоящие афро-американцы (наверное, чтобы увеличить продажи микросхем памяти).

Мы не будем писать код собственно парсинга XML – всю работу за нас сделают объекты XMLTextReader и XMLTextWriter. Они реализуют последовательное чтение или запись XML как текстового файла, без загрузки всего документа в память целиком. Наша внешняя компонента будет лишь программной «прослойкой» для этих объектов.

Пример чтения произвольного текстового файла XML на языке 1С 7.7  выглядит так:

net.ОткрытьФайл(ПутьКФайлу); 
Пока net.Прочитать()=1 Цикл 
 Рез = "ТипУзла=" + net.ТипУзла;  
 Рез = Рез + " Имя='" + net.Имя;  
 Рез = Рез + "' Значение'" + net.Значение;  
 Рез = Рез + "' " ;  
 Сообщить(Рез) ;  
КонецЦикла


Код отчасти похож на применяемый в 1С:Предприятие 8.0 (пример взят из книги Митичкина), и если вы программируете в версии 7.7, то сможете по достоинству оценить новый стиль работы с текстовыми файлами XML в этой среде. Однако, компонента не на 100% совместима с прототипом – вы можете самостоятельно устранить различия, либо, как и я, сделать свою «среду обитания» (в части кодировок, умолчаний и формата возвращаемых значений) наиболее комфортной именно для вас.

Регистрация внешней компоненты в системе Windows


Внешняя компонента фактически является дополнением для программных файлов 1С:Предприятие, и требует соответствующей установки.

Любые внешние компоненты для 1С:Предприятие являются COM-библиотеками, которые необходимо перед первым запуском регистрировать в системном реестре. Для этого необходимо обладать правами администратора или привилегированного пользователя на компьютере, где производится установка.

Пример регистрации компоненты в системном реестре, чтобы ее смогла найти 1С:

regasm.exe ИмяКомпоненты.dll /codebase

Чтобы выполнить это действие из дистрибутива, просто запустите на исполнение пакетный файл reg.bat, в который я вписал вышеуказанную команду.

Для конечных пользователей, очевидно, более грамотным решением будет программа-инсталлятор. Воспользуйтесь, например, программой Inno Setup, чтобы создать установочный дистрибутив, который будет проверять наличие необходимых прав, и устанавливать компоненту, регистрируя ее в системе Windows.

Как скомпилировать пример исходного кода


Удобнее всего сделать это, установив Visual Studio.NET. Найдите файл с расширением .sln (он имеет красивый цветной значок) и запустите его на выполнение.

Компиляция DLL производится нажатием F5. Просмотреть исходный код можно, открыв меню View – Solution Explorer.


Подключение внешней компоненты средствами встроенного языка 1С


Ниже приведен пример подключения внешней компоненты из глобального модуля 1С:Предприятие 7.7:

перем net Экспорт;

Процедура ПриНачалеРаботыСистемы()
    ИмяВК="AddIn.vk_XmlTextReaderWriter";
  
    ок=ПодключитьВнешнююКомпоненту(ИмяВК);
    Если ок=0 Тогда
        Сообщить("Не удалось подключить компоненту "+ИмяВК);
    КонецЕсли;
    net =СоздатьОбъект(ИмяВК);
 КонецПроцедуры


Замечу, что метод ЗагрузитьВнешнююКомпоненту() в данной ситуации (COM-объекты на .NET) не работает, и необходимо использовать метод ПодключитьВнешнююКомпоненту().

Как вставить отладочные сообщения


System.Windows.Forms.MessageBox.Show("Сообщение")
Вставка отладочных сообщений – хороший способ понять, как работает компонента (и,
вообще, любая программа).

Если вам нужна трассировка в окне сообщений 1С, то используйте следующий код (вынесите его в процедуру):
        Dim ei As ExcepInfo
        ei.wCode = 1001 'Вид пиктограммы
        ei.scode = S_OK 'Просто сообщение без генерации ошибки
        ei.bstrDescription = s 'Сообщение
        ei.bstrSource = c_AddinName

        V7Data.ErrorLog.AddError(c_AddinName, ei)


Как переименовать образец внешней компоненты


В ситуации, когда вы хотите создать свою внешнюю компоненту, использовав другую компоненту как шаблон, выполните переименование файлов и замену подстрок с названием ВК по всем текстам проекта.
Замените GUID и ProgID компоненты на уникальные значения.
Guid("02E65142-0F38-4a10-9053-049F8F2B4024"), ProgId("AddIn.vk_XmlTextReaderWriter")
Сгенерировать новое значение GUID можно из меню Tools - Create GUID.
Эти значения будут храниться в системном реестре Windows, и система будет находить по ним внешнюю компоненту.

Изменение списка свойств ВК



Список свойств хранится в перечислении Props:
    '/////////////////////////////////////////////////////////////////////////////////
    Enum Props
        'Числовые идентификаторы свойств внешней компоненты
        propNodeType = 0 'Тип узла XML
        propName = 1 'Имя узла XML
        propValue = 2 'Значение узла XML
        propIndent = 3 'Признак "форматировать XML с использованием отступов"
        LastProp = 4
    End Enum


Фактически, это список констант. Например, propIndent – это то же самое, что и число 3.

Попытайтесь переименовать одно из свойств и скомпилировать проект.
Среда исполнения выдаст множество ошибок, по которым вы можете найти строки исходного текста, где задействовано это свойство.

Русские и английские имена свойств задайте здесь:
    '/////////////////////////////////////////////////////////////////////////////////
    Sub FindProp(ByVal bstrPropName As String, ByRef plPropNum As Integer) Implements ILanguageExtender.FindProp
        'Здесь 1С ищет числовой идентификатор свойства по его текстовому имени

        Select Case bstrPropName
            Case "NodeType", "ТипУзла"
                plPropNum = Props.propNodeType
            Case "Name", "Имя"
                plPropNum = Props.propName
            Case "Value", "Значение"
                plPropNum = Props.propValue
            Case "Indent", "Отступ"
                plPropNum = Props.propIndent
            Case Else
                plPropNum = -1
        End Select
    End Sub


Сами значения свойств 1С получает в методе с говорящим названием GetPropVal

    '/////////////////////////////////////////////////////////////////////////////////
    Sub GetPropVal(ByVal lPropNum As Integer, ByRef pvarPropVal As Object) Implements ILanguageExtender.GetPropVal


А устанавливает их в методе

    '/////////////////////////////////////////////////////////////////////////////////
    Sub SetPropVal(ByVal lPropNum As Integer, ByRef varPropVal As Object) Implements ILanguageExtender.SetPropVal


В данном случае, на вход процедур поступает lPropNum – порядковый номер свойства.
Например, в случае propIndent  это будет значение 3. Вторым параметром идет значение неопределенного типа Object. Чтобы получить значение определенного типа (например, строку или число), используйте оператор

CType(varPropVal, Integer)

Изменение списка методов ВК


Аналогично для методов:

    '/////////////////////////////////////////////////////////////////////////////////
    Enum Methods
        'Числовые идентификаторы методов (процедур или функций) внешней компоненты
        methOpenFile = 0 'Открытие файла XML
        methClose = 1 'Закрытие файла XML
        methRead = 2 'Чтение узла XML
        methGetAttribute = 3 'Получает атрибут XML по его имени или номеру
        methAttributeCount = 4 'Количество атрибутов узла XML 

        methCreateFile = 5 ' Создание файла XML
        methWriteStartElement = 6 ' Записывает стартовый элемент, например <tag>
        methWriteAttribute = 7 'Записывает атрибут тега
        methWriteText = 8 ' Записывает текст
        methWriteEndElement = 9 'Записывает конечный элемент, например, </tag>

        methRaiseException = 10 'Вызывает исключение (например, при ошибке)

        LastMethod = 11
    End Enum


    '/////////////////////////////////////////////////////////////////////////////////
    Sub FindMethod(ByVal bstrMethodName As String, ByRef plMethodNum As Integer) Implements ILanguageExtender.FindMethod
        'Здесь 1С получает числовой идентификатор метода (процедуры или функции) по имени (названию) процедуры или функции

        plMethodNum = -1
        Select Case bstrMethodName
            Case "OpenFile", "ОткрытьФайл"
                plMethodNum = Methods.methOpenFile

            Case "Close", "Закрыть"
                plMethodNum = Methods.methClose


и т.д.

Здесь необходимо задать количество параметров каждого метода:
    '/////////////////////////////////////////////////////////////////////////////////
    Sub GetNParams(ByVal lMethodNum As Integer, ByRef plParams As Integer) Implements ILanguageExtender.GetNParams
        'Здесь 1С получает количество параметров у метода (процедуры или функции)

        Select Case lMethodNum
            Case Methods.methOpenFile
                plParams = 1
            Case Methods.methClose
                plParams = 0


и т.д.


Метод с говорящим названием CallAsFunc позволяет 1С выполнять полезные действия средствами ВК:

    '/////////////////////////////////////////////////////////////////////////////////
    Sub CallAsFunc(ByVal lMethodNum As Integer, ByRef pvarRetValue As Object, _
    ByRef paParams As System.Array) _
    Implements ILanguageExtender.CallAsFunc

        'Здесь внешняя компонента выполняет код функций.

        Try
            pvarRetValue = 0 'Возвращаемое значение метода для 1С
            Select Case lMethodNum 'Порядковый номер метода
                '//////////////////////////////////////////////////////////
            Case Methods.methOpenFile  'Реализуем метод для открытия XML-файла

                    Dim s1 As String
                    s1 = CType(paParams.GetValue(0), String)
                    If g_reader Is Nothing Then
                    Else
                        g_reader.Close()
                        g_reader = Nothing
                    End If
                    g_reader = New XmlTextReader(s1)
                    g_reader.WhitespaceHandling = WhitespaceHandling.None

                    '//////////////////////////////////////////////////////////
                Case Methods.methClose  'Реализуем метод для закрытия XML-файла
                    If g_reader Is Nothing Then
                    Else
                        g_reader.Close()
                        g_reader = Nothing
                    End If
                    If g_writer Is Nothing Then
                    Else
                        g_writer.Close()
                        g_writer = Nothing
                    End If
…
                    '//////////////////////////////////////////////////////////
                    'Реализуем метод для генерации исключения
                Case Methods.methRaiseException
                    Dim s1 As String
                    s1 = CType(paParams.GetValue(0), String)
                    Throw New Exception(s1)

            End Select

        Catch ex As Exception 'Обрабатываем исключение (ошибку)
            Raise1CException(ex.Message)
        End Try


    End Sub


Для простоты я не использую CallAsProc, поскольку функции в 1С:Предприятие можно вызывать как процедуры.

Код этой процедуры обрамлен конструкцией обработки ошибок
        Try

…

        Catch ex As Exception 
            Raise1CException(ex.Message)
        End Try


Внутри нее расположена большая конструкция – переключатель Select Case:

            Select Case lMethodNum 'Порядковый номер метода
            Case Methods.methOpenFile  'Реализуем метод для открытия XML-файла
…


В каждом из «кейсов» мы выполняем определенное действие (открываем файл, закрываем файл и т.д.).

Обработка ошибок


Обработка ошибок внешней компоненты реализована конструкцией Try-Catch.
Чтобы передать ошибку в 1С, я использую следующий код:

    '/////////////////////////////////////////////////////////////////////////////////
    'Функция генерирует исключение в 1С
    Sub Raise1CException(ByVal s As String)
        Try
            g_reader.Close() 'Закрываем файл XML
        Catch
        End Try
        Try
            g_reader = Nothing '"Убиваем" ссылку на объект
        Catch
        End Try

        Dim ei As ExcepInfo
        ei.wCode = 1004 'Вид пиктограммы

        '1000 - нет значка
        '1001 - обычный значок
        '1002 - красный значок !
        '1003 - красный значок !!
        '1004 - красный значок !!!
        '1005 - зеленый значок i
        '1006 - красный значок err
        '1007 - Окно предупреждения "Внимание"
        '1008 - Окно предупреждения "Информация"
        '1009 - Окно предупреждения "Ошибка"

        ei.scode = 1 'Генерируем ошибку времени исполнения
        ei.bstrDescription = s 'Сообщение
        ei.bstrSource = c_AddinName

        V7Data.ErrorLog.AddError(c_AddinName, ei)

    End Sub


Значение scode управляет ситуацией, когда код 1С прекращает свое выполнение при ошибке (как в нашем случае), или же выполнение продолжается, но просто выводится ошибка (когда scode = S_OK).

Встроенный язык 1С:Предприятие 8.0 позволяет вызывать исключения при ошибке.
Встроенный язык 1С:Предприятие 7.7 делать этого не позволяет (в глубокую старину люди еще не знали, что генерировать исключения при ошибке – это хорошо), поэтому я вставил во внешнюю компоненту соответствующий метод ВызватьИсключение().
Он может пригодиться, например, при анализе правильности входящего XML, и во многих других случаях, когда надо прекратить выполнение и произвести выход из множества вложенных процедур, функций и циклов, не сильно усложняя при этом код.

Исследование работы библиотечных классов


Поскольку наша внешняя компонента – это «обертка» для классов работы с XML, чтобы с чего-то начать, я взял из MSDN и скомпилировал небольшое «проверочное» приложение, чтобы понять, как работает класс. Пример на языке C#, но он отличается от VB.NET лишь небольшими нюансами (например, фигурными скобками), привычными для С-программистов. Он компилируется как консольное приложение, и позволяет разобраться, а что, собственно, нужно делать, чтобы прочитать текстовый XML-файл при помощи XmlTextReader.

using System;
using System.Xml;


class MyApp
{
  static void Main (string[] args)
  {
    if (args.Length < 1) 
    {
      Console.WriteLine ("Syntax: XmlTextReader xmldoc");
      return;
    }

    XmlTextReader reader = new XmlTextReader (args[0]);
    try 
    {
      reader.WhitespaceHandling = WhitespaceHandling.None;

      while (reader.Read ())
      {
        Console.WriteLine ("Type={0}\t Name={1}\t Value={2}",
           reader.NodeType, reader.Name, reader.Value);
        if (reader.HasAttributes){
          for (int i=0; i<reader.AttributeCount; i++)
          {
            reader.MoveToAttribute(i);
            Console.WriteLine(" \t{0}={1}", reader.Name, reader.Value);
          }
        }

      }
    }
    catch (Exception ex) 
    {
      Console.WriteLine (ex.Message);
    }
    finally 
    {
      if (reader != null)
        reader.Close ();
    }
  }
}


Пример запускается из командной строки с параметром – текстовым файлом в формате XML, и выводит в консоль названия прочитанных тегов, их тип (открывающий, закрывающий и т.п.) и атрибуты (параметры) тегов.

Заключение


Программирование в среде .NET, работа с XML и работа с исключениями иногда «до чертиков» пугают новичков (и не только новичков, но порой даже опытных специалистов с устаревшим стилем мышления). Однако, в современных средах разработки многие вещи становятся на удивление простыми и понятными. Может возникнуть даже такое чувство, что возвращаются старые добрые времена, когда дискеты были большими.

Файлы для загрузки



Пример внешней компоненты с исходным текстом на VB.NET для удобной и быстрой работы с XML (в стиле 1С 8.0).

http://x-romix.narod.ru/vb_XmlTextReaderWriter.rar (этот файл следует скачивать левой кнопкой мыши, 180К).

Шаблоны (примеры простейших) внешних компонент на C# и VB.NET :

http://www.kb.mista.ru/files/NetV7ExtTemplate.rar
http://www.kb.mista.ru/files/V7ExtTemplate-1.C.rar

Продолжение: Книга знаний: Внешние компоненты 1С на .NET: работа с типами 1С:Предприятия;

Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2025 | Mista.ru

Яндекс.Метрика