Friday, March 09, 2007

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

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


Ключевые слова: пример внешней компоненты, внешняя компонента, VB.NET, C#, COM, XML, XmlTextReader, XmlTextWriter, .NET, зарегистрировать внешнюю компоненту, regasm, не работает regsvr32, не работает ЗагрузитьВнешнююКомпоненту, вызвать исключение, CLSID


Скачайте работающие образчики ВК в конце этой статьи. Образцы основаны на "технологии создания внешних компонент" фирмы 1С и примерах (с) Igor Kissil. Перевод шаблона компоненты с VB.NET на язык C# - (c) Asmody.
Комментарий от Asmody: автором собственно статьи является romix, я лишь создал "заготовку" для нее. Спишем сей грех на особенности движка КЗ.

В чем преимущества .NET для новичков?

При нажатии Ctrl-F1 вы увидете подсказку строго по нужному объекту.
Библиотека классов .NET возвращает ошибки при помощи исключений (Exceptions). Алгоритмически обработка ошибок получается намного "легче" и понятнее для восприятия, а программа - устойчивее к сбоям.

Среда разработки и исполнения защищена от типичных ошибок C-программиста. Visual Studio 2005 в этом отношении предпочтительнее версии 2003. Это значит, что процесс разработки не "упрется" в трудноуловимую ошибку, а заказчик на удаленном объекте не будет жаловаться на нестабильную работу кода.
Доступна обширная и понятная документация MSDN/SDK. "Технический английский" Microsoft отличается короткими ясными фразами. Эти тексты очень легко понять (особенно, имея под рукой электронный словарь наподобие Lingvo). Текст описаний, как правило, снабжен работающими примерами кода.

Почему код, скомпилированный для .NET, может выполняться быстрее?

EXE- и DLL-сборки для .NET очень компактные, и их размер измеряется килобайтами.
При первом запуске заметна задержка в доли секунды. В этот момент среда исполнения .NET компилирует код MSIL (http://ru.wikipedia.org/wiki/MSIL) в "родные" инструкции микропроцессора (предположительно, что это именно так и есть, и оптимальность такой компиляции не уступает качеству компиляции из C-кода). Это позволяет производить проверки кода (например, вы не сможете передать по сети и исполнить частично поврежденный код) и учесть особенности микропроцессора и другого оборудования на клиенте.

На клиентах Windows 98/NT/2000 необходимо установить CLR

На старых версиях операционных систем необходимо установить библиотеку среды исполнения CLR (порядка 10 мегабайт); в новых версиях и сервис-паках Windows она уже стоит.

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

В качестве полезного примера мы напишем внешнюю компоненту, которая работает с текстовыми файлами, размеченными в формате 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-библиотеками, которые необходимо перед первым запуском регистрировать в системном реестре. Для этого необходимо обладать правами администратора или привилегированного пользователя на компьютере, где производится установка.

Регистрация при помощи regsvr32 в данном случае, однако, не работает.

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

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


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

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

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

Удобнее всего сделать это, установив Visual Studio.NET (например, версии 2005). Найдите файл с расширением .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 = New 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 можно утилитой guidgen.exe, которая находится в комплекте Visual Studio.
Эти значения будут храниться в системном реестре 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 '// Записывает стартовый элемент, например
methWriteAttribute = 7 '//Записывает атрибут тега
methWriteText = 8 '// Записывает текст
methWriteEndElement = 9 '//Записывает конечный элемент, например,


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 = New 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 <> {
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.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 (пример проверен в Visual Studio 2005).

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


Шаблоны (примеры простейших) внешних компонент на C# и VB.NET (актуальны для Visual Studio 2003):

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

Замечание: пример для VB не компилируется для VS 2005 - в файле проекта с ошибкой указана настройка BaseAddress (которую необходимо стереть). Кроме того, VS 2005 показывает несколько ошибок, которые не смогла отловить среда VS 2003. Исправленные примеры кода - в архиве vb_XmlTextReaderWriter.rar.


2 comments:

Unknown said...

Я скачал ваши примеры. Пример на VB.NET работает прекрасно чего не скажеш о C# пример на С выдает ошику при выходе из метода Init что то о InvalidVARIANT. Не подскажете в чем дело?

Elisy said...

Появился новый внешний компонент для 1С - .Net Bridge. Он позволяет работать с компонентами .Net, не вдаваясь в подробности технологии написания внешних компонентов для 1С. Например без труда можно подключить диаграммы иностранных разработчиков: http://www.richmedia.us/post/2009/11/21/1c-dot-net-components-integration.aspx