Архив за месяц: Сентябрь 2011

Склонения объектов в 1с 8.1. конфигурации УПП

Мысли на тему: Склонения объектов в конфигурации Управление производственным предприятем 8.1. Использование запросов для получения склонений.


В базе должен быть РегистрСведений.СклонениеОбъектов

Функция ЗапросСклоненияОбъекта(Объект,Падеж) 

    // задается Объект и Падеж (Перечисления.Падежи)

        Запрос = Новый Запрос;

        Запрос.Текст = "ВЫБРАТЬ
        |    СклонениеОбъектов.Объект,
        |    СклонениеОбъектов.Падеж,
        |    СклонениеОбъектов.Значение
        |ИЗ
        |    РегистрСведений.СклонениеОбъектов КАК СклонениеОбъектов
        |ГДЕ
        |(СклонениеОбъектов.Падеж = &Падеж)
        |И (СклонениеОбъектов.Объект = &ТекОбъект)";

        Запрос.УстановитьПараметр("Падеж", Падеж);
        Запрос.УстановитьПараметр("ТекОбъект",Объект);
    Возврат Запрос.Выполнить();

КонецФункции



 Функция УстановитьПадеж(Сотрудник)
       Запрос = Новый Запрос;  //  Выбираются склонения должностей и физ. лиц

        Запрос.УстановитьПараметр("Падеж", Сотрудник.Падеж);
        Запрос.УстановитьПараметр("Должность", Сотрудник.Должность);
        Запрос.УстановитьПараметр("ФизическоеЛицо", Сотрудник.ФизЛицо);

        Запрос.Текст = "ВЫБРАТЬ
        |    СклонениеОбъектов.Объект,
        |    СклонениеОбъектов.Падеж,
        |    СклонениеОбъектов.Значение
        |ИЗ
        |    РегистрСведений.СклонениеОбъектов КАК СклонениеОбъектов
        |ГДЕ
        |СклонениеОбъектов.Падеж = &Падеж
        |И (СклонениеОбъектов.Объект = &Должность
        |ИЛИ СклонениеОбъектов.Объект = &ФизическоеЛицо)";

        Результат = Запрос.Выполнить().Выгрузить();


       Для каждого Строка Из Результат Цикл
           Если Строка.Объект = Сотрудник.Должность Тогда

                    Сотрудник.ДолжностьВПадеже   = Строка.Значение;

            КонецЕсли;

           Если Строка.Объект = Сотрудник.ФизЛицо Тогда
                   Сотрудник.ФИОВПадеже   = Строка.Значение;
            КонецЕсли;
        КонецЦикла;

     Возврат Неопределено;
 КонецФункции



В функцию УстановитьПадеж(Сотрудник) Передается Структура. Выглядить она таким образом:


Сотрудник = Новый Структура();


Для Каждого ТекСтрока Из РаботникиОрганизации Цикл

Сотрудник.Вставить("ФизЛицо",ТекСтрока.ФизЛицо);        
Сотрудник.Вставить("Падеж", Перечисления.Падежи.Дательный);
Сотрудник.Вставить("Должность", ТекСтрока.Должность);
Сотрудник.Вставить("ДолжностьВПадеже",ТекСтрока.Должность.Наименование);
Сотрудник.Вставить("ФИОВПадеже",ТекСтрока.ФизЛицо.Наименование);


КонецЦикла;

Склонения объектов в 1с 8.1. конфигурации УПП

Мысли на тему: Склонения объектов в конфигурации Управление производственным предприятем 8.1. Использование запросов для получения склонений.


В базе должен быть РегистрСведений.СклонениеОбъектов

Функция ЗапросСклоненияОбъекта(Объект,Падеж) 

    // задается Объект и Падеж (Перечисления.Падежи)

        Запрос = Новый Запрос;

        Запрос.Текст = "ВЫБРАТЬ
        |    СклонениеОбъектов.Объект,
        |    СклонениеОбъектов.Падеж,
        |    СклонениеОбъектов.Значение
        |ИЗ
        |    РегистрСведений.СклонениеОбъектов КАК СклонениеОбъектов
        |ГДЕ
        |(СклонениеОбъектов.Падеж = &Падеж)
        |И (СклонениеОбъектов.Объект = &ТекОбъект)";

        Запрос.УстановитьПараметр("Падеж", Падеж);
        Запрос.УстановитьПараметр("ТекОбъект",Объект);
    Возврат Запрос.Выполнить();

КонецФункции



 Функция УстановитьПадеж(Сотрудник)
       Запрос = Новый Запрос;  //  Выбираются склонения должностей и физ. лиц

        Запрос.УстановитьПараметр("Падеж", Сотрудник.Падеж);
        Запрос.УстановитьПараметр("Должность", Сотрудник.Должность);
        Запрос.УстановитьПараметр("ФизическоеЛицо", Сотрудник.ФизЛицо);

        Запрос.Текст = "ВЫБРАТЬ
        |    СклонениеОбъектов.Объект,
        |    СклонениеОбъектов.Падеж,
        |    СклонениеОбъектов.Значение
        |ИЗ
        |    РегистрСведений.СклонениеОбъектов КАК СклонениеОбъектов
        |ГДЕ
        |СклонениеОбъектов.Падеж = &Падеж
        |И (СклонениеОбъектов.Объект = &Должность
        |ИЛИ СклонениеОбъектов.Объект = &ФизическоеЛицо)";

        Результат = Запрос.Выполнить().Выгрузить();


       Для каждого Строка Из Результат Цикл
           Если Строка.Объект = Сотрудник.Должность Тогда

                    Сотрудник.ДолжностьВПадеже   = Строка.Значение;

            КонецЕсли;

           Если Строка.Объект = Сотрудник.ФизЛицо Тогда
                   Сотрудник.ФИОВПадеже   = Строка.Значение;
            КонецЕсли;
        КонецЦикла;

     Возврат Неопределено;
 КонецФункции



В функцию УстановитьПадеж(Сотрудник) Передается Структура. Выглядить она таким образом:


Сотрудник = Новый Структура();


Для Каждого ТекСтрока Из РаботникиОрганизации Цикл

Сотрудник.Вставить("ФизЛицо",ТекСтрока.ФизЛицо);        
Сотрудник.Вставить("Падеж", Перечисления.Падежи.Дательный);
Сотрудник.Вставить("Должность", ТекСтрока.Должность);
Сотрудник.Вставить("ДолжностьВПадеже",ТекСтрока.Должность.Наименование);
Сотрудник.Вставить("ФИОВПадеже",ТекСтрока.ФизЛицо.Наименование);


КонецЦикла;

Выбрать элемент справочника в v 7.7 и в 8.1


Поговорим об интерактивном выборе элемента из справочника в версиях 7.7 и 8.1



В Версии 7.7 выбрать элемент справочника можно функцией Выбрать:

Перем ВыбСпр;
Спр=СоздатьОбъект("Справочник.Товары");
Если Спр.Выбрать("Выберите товар из справочника", "ОсновнаяФорма")=Тогда
    ВыбСпр=Спр.ТекущийЭлемент();
КонецЕсли;
 



ОсновнаяФорма - можно выбрать


Для версии 8.1 выбор элемента может выглядеть так:

Спр=Справочники.Товары;
ФормаВыбора=Спр.ПолучитьФормуВыбора("ФормаВыбора");
ФормаВыбора.ЗакрыватьПриВыборе=Истина;
ФормаВыб.РежимВыбора = Истина;
 
ФормаВыбора.ОткрытьМодально();


Аналогичным образом можно выбирать и документы

Рабочая версия выглядит так:

ВыбСправочника = Справочник.Номенклатура.ПолучитьФормуВыбора("ФормаВыбора",ЭтаФорма);
Выб
Справочника.РежимВыбора= Истина;
Выб
Справочника.ЗакрыватьПриВыборе=Истина;
ВыбЭлемент= ВыбДокумента.ОткрытьМодально();
Если ВыбЭлемент <> Неопределено Тогда
//<...Действия с выбраным товаром>
КонецЕсли;


 
Второй параметр в функции  ПолучитьФормуВыбора("ФормаВыбора",ЭтаФорма) - задает форму владелеца (ЭтаФорма) (подробнее описание функции смотрите в справке )

Была ситуация в версии 8.1, когда при выборе элемента из списка - элемент не выбирался, а происходило открытие для редактирования.
Проблема оказалась в коде выбираемого документа. Там был код принудительного открытия формы документа при выборе из списка:

Процедура ДокументСписокВыбор(Элемент, ВыбраннаяСтрока, Колонка, СтандартнаяОбработка)
        Если ТекЗначениеУчета.Значение=Перечисления.ВидУчетаЛБ.Соптовары Тогда
            СтандартнаяОбработка = Ложь;
            ФормаСпецификации = ВыбраннаяСтрока.ПолучитьФорму("ФормаДокументаРозничная");
            ФормаСпецификации.Открыть();
        КонецЕсли;

КонецПроцедуры

пришлось добавить условие: если существует форма владелец, то производить выбор. В результате получилось так:

Процедура ДокументСписокВыбор(Элемент, ВыбраннаяСтрока, Колонка, СтандартнаяОбработка)

    Если ЭтаФорма.ВладелецФормы = Неопределено Тогда      //Добавлено условие - в случае, если Форма-Владелей не определена

        Если ТекЗначениеУчета.Значение=Перечисления.ВидУчетаЛБ.Соптовары Тогда

            СтандартнаяОбработка = Ложь;

            ФормаСпецификации = ВыбраннаяСтрока.ПолучитьФорму("ФормаДокументаРозничнаяЛБ");

            ФормаСпецификации.Открыть();

        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Ещё можно глянуть более подробный материал здесь 

Выбрать элемент справочника в v 7.7 и в 8.1


Поговорим об интерактивном выборе элемента из справочника в версиях 7.7 и 8.1



В Версии 7.7 выбрать элемент справочника можно функцией Выбрать:

Перем ВыбСпр;
Спр=СоздатьОбъект("Справочник.Товары");
Если Спр.Выбрать("Выберите товар из справочника", "ОсновнаяФорма")=Тогда
    ВыбСпр=Спр.ТекущийЭлемент();
КонецЕсли;
 



ОсновнаяФорма - можно выбрать


Для версии 8.1 выбор элемента может выглядеть так:

Спр=Справочники.Товары;
ФормаВыбора=Спр.ПолучитьФормуВыбора("ФормаВыбора");
ФормаВыбора.ЗакрыватьПриВыборе=Истина;
ФормаВыб.РежимВыбора = Истина;
 
ФормаВыбора.ОткрытьМодально();


Аналогичным образом можно выбирать и документы

Рабочая версия выглядит так:

ВыбСправочника = Справочник.Номенклатура.ПолучитьФормуВыбора("ФормаВыбора",ЭтаФорма);
Выб
Справочника.РежимВыбора= Истина;
Выб
Справочника.ЗакрыватьПриВыборе=Истина;
ВыбЭлемент= ВыбДокумента.ОткрытьМодально();
Если ВыбЭлемент <> Неопределено Тогда
//<...Действия с выбраным товаром>
КонецЕсли;


 
Второй параметр в функции  ПолучитьФормуВыбора("ФормаВыбора",ЭтаФорма) - задает форму владелеца (ЭтаФорма) (подробнее описание функции смотрите в справке )

Была ситуация в версии 8.1, когда при выборе элемента из списка - элемент не выбирался, а происходило открытие для редактирования.
Проблема оказалась в коде выбираемого документа. Там был код принудительного открытия формы документа при выборе из списка:

Процедура ДокументСписокВыбор(Элемент, ВыбраннаяСтрока, Колонка, СтандартнаяОбработка)
        Если ТекЗначениеУчета.Значение=Перечисления.ВидУчетаЛБ.Соптовары Тогда
            СтандартнаяОбработка = Ложь;
            ФормаСпецификации = ВыбраннаяСтрока.ПолучитьФорму("ФормаДокументаРозничная");
            ФормаСпецификации.Открыть();
        КонецЕсли;

КонецПроцедуры

пришлось добавить условие: если существует форма владелец, то производить выбор. В результате получилось так:

Процедура ДокументСписокВыбор(Элемент, ВыбраннаяСтрока, Колонка, СтандартнаяОбработка)

    Если ЭтаФорма.ВладелецФормы = Неопределено Тогда      //Добавлено условие - в случае, если Форма-Владелей не определена

        Если ТекЗначениеУчета.Значение=Перечисления.ВидУчетаЛБ.Соптовары Тогда

            СтандартнаяОбработка = Ложь;

            ФормаСпецификации = ВыбраннаяСтрока.ПолучитьФорму("ФормаДокументаРозничнаяЛБ");

            ФормаСпецификации.Открыть();

        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Ещё можно глянуть более подробный материал здесь 

Краткое введение в eXpress Application Framework

Часто тут и там мы слышим о концепции отделения бизнес-логики приложения от UI. Разные софтверные компании предлагают разные решения этой задачи. Microsoft, к примеру, продвигает технологию WPF, а для Qt разрабатывается QML. В этой статье я хочу познакомит Вас с решением от компании Developer Express -- eXpress Application Framework (или просто XAF). Фреймворк предоставляет невероятно много функционала, поэтому я расскажу только о вершине айсберга. Возможно, в следующих статьях, я расскажу больше.
Сразу же стоит отметить, что XAF -- это фреймворк для .NET. Поэтому, если Вы хотите написать кроссплатформенное приложение, то этот фреймворк не для Вас. Под Mono XAF тоже не работает.
Помимо минусов, у XAF есть и положительные качества. К примеру, XAF реализован для WinForms и ASP.NET приложений таким образом, что Вам не нужно задумываться, для какой платформы Вы пишите, конечный продукт будет работать на обеих платформах.
Для того, чтобы начать программировать с использованием XAF не нужно много знать о его устройстве, достатьчно прочесть tutorial на официальном сайте продукта. После установки фреймворка на компьютер, в Visual Studio появляются мастера создания проектов. Вы можете создать проект для WinForms, для ASP.NET или для обеих платформ сразу. Приложения XAF имеют модульную архитектуру.  Мастер сгенерирует несколько проектов, один из которых будет являться общим для всех приложений модулем. Также будут созданы модули для win и web приложений отдельно.
Все эти шаги подробно описаны в указанном tutorial. В этой же статье я приведу пример создания XAF приложения полностью вручную. Скачать демо версию фреймворка Вы можете с официально сайта.

База данных

Для работы приложения XAF Вам понадобится база данных на одной из поддерживаемых СУБД. В приведённом примере я буду использовать MySQL. Придумайте название для Вашего приложения и создайте базу данных с этим именем. Настоятельно рекомендую использовать кодировку UTF-8 для вновь созданной базы MySQL. Мой пример будет носить имя "XafDemo".

Создание базового приложения

Теперь, когда мы обзавелись базой данных, можно приступать к созданию приложения. Для этого создадим в студии каркас Windows Form Application и выкинем из него всё лишнее, оставив только файл Program.cs и ссылку на модуль System. Теперь добавим нужные нам ссылки:
  • System.configuration
  • DevExpress.Data.v11.1
  • DevExpress.ExpressApp.Images.v11.1
  • DevExpress.ExpressApp.v11.1
  • DevExpress.ExpressApp.Win.v11.1
  • DevExpress.Xpo.v11.1
  • DevExpress.Xpo.v11.1.Providers
  • MySql.Data
Последняя ссылка требует установленного MySQL .NET Connector'а и зависит от той СУБД, которую Вы используете.
Добавим к проекту файл конфигурации и пропишем в нём Connection String до базы данных. Мой конфигурационный файл будет выглядеть так:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ConnectionString" value="XpoProvider=MySql;server=192.168.1.2;user id=sa; password=123456; database=XafDemo;persist security info=true;CharSet=utf8;"/>
</appSettings>
</configuration>

Модуль бизнес-логики

Теперь нужно создать ещё один проект, в котором будет содержаться бизнес-логика нашего приложения. Этот проект должен быть библиотекой классов. Назовём его DemoModule. Так же, как и в первом случае, выкидываем из вновь созданного модуля всё лишнее. Вот те ссылки, которые должны быть в проекте:
  • System
  • DevExpress.Data.v11.1
  • DevExpress.ExpressApp.v11.1
  • DevExpress.ExpressApp.Win.v11.1
  • DevExpress.Xpo.v11.1
Чтобы XAF заметил наш модуль, мы должны реализовать публичный класс, унаследованный от DevExpress.ExpressApp.ModuleBase. В нашем случае, класс тривиален:
using DevExpress.ExpressApp;

namespace DemoModule
{
public class DemoModule :
ModuleBase
{
}
}

Реализация базового приложения

Теперь у нас есть модуль, который мы можем зарегистрировать в базовом приложении. Возвращаемся в проект XafDemo, добавляем в зависимости проект DemoModule и прописываем его в файле конфигурации
<add key="Modules" value="DemoModule"/>
Для запуска приложения требуется создать экземпляр класса DevExpress.ExpressApp.WinApplication. Обернём создание экземпляра этого класса в класс XafDemoApplication.
using DevExpress.ExpressApp.Win;
using System.Configuration;
using System;
using DevExpress.ExpressApp.Updating;

namespace XafDemo
{
public class XafDemoApplication : IDisposable
{
private WinApplication application;

public XafDemoApplication(string[] arguments)
{
Init();
}

private void Init()
{
application = new WinApplication()
{
Title = "XAF Demo Application"
};
application.Setup("XafDemo", LoadConnectionString(), LoadModules());
}

private string LoadConnectionString()
{
return ConfigurationManager.AppSettings["ConnectionString"];
}

private string[] LoadModules()
{
string modules = ConfigurationManager.AppSettings["Modules"];
if(String.IsNullOrEmpty(modules))
return new string[0];
return modules.Split(';');
}

public void Start()
{
application.Start();
}

public void HandleException(Exception exception)
{
application.HandleException(exception);
}

public void Dispose()
{
application.Dispose();
}
}
}
Метод Setup класса WinApplication, в этом примере, принимает три аргумента: имя приложения (соответствует имени базы данных), connection string к базе данных и загружаемые модули. Метод HandleException показывает удобный Message Box с подробным сообщением об ошибке.
Чтобы запустить приложение, достаточно простой реализации метода Main.
using System;

namespace XafDemo
{
public class XafDemo
{
[STAThread]
public static void Main(string[] args)
{
using(XafDemoApplication application = new XafDemoApplication(args))
{
try
{
application.Start();
}
catch (Exception err)
{
application.HandleException(err);
}
}
}
}
}

Однако, при попытке запустить приложение, окажется, что структура базы данных не созана и приложение завершиться с ошибкой. Для того, чтобы база данных была в актуальном состоянии, её следует постоянно обновлять. Добавим метод для обновления базы в класс XafDemoApplication.
public void UpdateDatabase()
{
DatabaseUpdater updater = application.CreateDatabaseUpdater();
updater.Update();
}
Выполнив этот метод, мы поместим всю служебную информацию в базу. Кроме того, будут созданы таблицы для хранения всех бизнес-объектов, о которых чуть позже.

Database updater

Было бы неосмотрительно давать каждому пользователю возможность обновлять базу, поэтому вынесем функционал для обновления в отдельный проект. Для этого создадим Console Application и, по традиции, выкинем всё лишнее из сгенерированного проекта. Назовём новый проект DBUpdater. Понадобятся нам лишь те библиотеки, которые мы использовали в модуле и сам модуль:
  • System
  • DevExpress.Data.v11.1
  • DevExpress.ExpressApp.v11.1
  • DevExpress.ExpressApp.Win.v11.1
  • DevExpress.Xpo.v11.1 
  • DemoModule
Скопируем файл конфигурации из XafDemo в проект и реализуем метод Main.
using System;
using XafDemo;

namespace DBUpdater
{
public class DBUpdater
{
static void Main(string[] args)
{
using(XafDemoApplication application =
new XafDemoApplication(args))
{
try
{
Console.Write("Updating: ");
application.UpdateDatabase();
Console.WriteLine("success!");
}
catch(Exception err)
{
Console.WriteLine("fail!");
Console.WriteLine("Error details:");
Console.WriteLine("Type: {0}", err.GetType().FullName);
Console.WriteLine("Message: {0}", err.Message);
Console.WriteLine("Stack trace:");
Console.WriteLine(err.StackTrace);
Console.WriteLine();
}
Console.Write("Press any key...");
Console.ReadKey();
}
}
}
}
Запустив это приложение, мы создадим рабочую структуру базы. Но есть одна, не очень приятная, особенность. При выполнении метода Setup XAF показывает splash окно. А в консольном приложении оно выглядит глупо. Добавим в XafDemoApplication функционал для отключения splash. Сделать это можно установив null в свойство SplashScreen класса WinApplication. Добавим конструктор, принимающий флаг того, нужно ли отключать окно приветствия, а в методе Init выполним проверку этого флага и установим null в SplashScreen.
public XafDemoApplication(string[] arguments)
{
Init(false);
}

public XafDemoApplication(string[] arguments, bool suppressSplash)
{
Init(suppressSplash);
}

private void Init(bool suppressSplash)
{
application = new WinApplication()
{
Title = "XAF Demo Application"
};
if(suppressSplash)
application.SplashScreen = null;
application.Setup("XafDemo", LoadConnectionString(), LoadModules());
}
Изменим создание XafDemoApplication в DBUpdater, передав true вторым аргументом и противное окно больше не появится.

Первый запуск

Всё готово к первому запуску. Так как мы не реализовали ни одного бизнес-объекта и не прилинковали ни одного стандартного модуля, то окно приложения будет абсолютно пустым

Бизнес-объекты

Чтобы в нашем окне появилось что-нибудь интересное, нужно это создать. Все бизнес объекты, с которыми работает XAF являются объектами ORM eXpress Persistent Objects (XPO), разрабатываемой той же Developer Express. В качестве базового класса для всех бизнес-объектов удобно использовать класс DevExpress.Xpo.XPObject.
Создадим класс Employee, описывающий сотрудника некой фирмы.
using System;
using DevExpress.Xpo;

namespace DemoModule
{
public class Employee : XPObject
{
private string firstName;
private string secondName;

public Employee(Session session) :
base(session)
{
}

public string FirstName
{
get { return firstName; }
set { SetPropertyValue<string>("FirstName", ref firstName, value); }
}

public string SecondName
{
get { return secondName; }
set { SetPropertyValue<string>("SecondName", ref secondName, value); }
}
}
}
Как видно, нет ничего сложного. Все открытые свойства, по умолчанию, сохраняются в базе и отображаются на форме. Конструктор класса XPObject принимает объект Session, который создаётся при установлении соединения с базой данных. Чтобы поместить новое значение свойства в базу, следует использовать метод SetPropertyValue, объявленный в классе DevExpress.Xpo.PersistentBase.
Чтобы обновить схему в базе данных, запустите DBUpdater.
После запуска XafDemoApplication мы не увидим ни каких изменений. Это случилось потому, что мы не указали XAF, что хотим видеть.

Модель XAF

Для того, чтобы указать XAF, что мы хотим видеть на форме, мы должны создать файл настроек модели. Добавьте простой текстовый файл с именем Model.DesignedDiffs.xafml в проект DemoModule и установите его свойство "Build Action" в "Embedded Resource". Запишите в этот файл следующий текст
<?xml version="1.0" encoding="utf-8"?>
<Application/>
После пересборки проекта этот файл можно использовать для настройки UI. Делается это либо через встроенный в Visual Studio редактор, либо вручную, запустив программу DevExpress.ExpressApp.ModelEditor.v11.1.exe, которая лежит, у меня, по адресу c:Program FilesDevExpress 2011.1eXpressApp FrameworkToolsModel Editor. В качестве опций эта программа принимает путь до модуля (файла *.dll) и путь до каталога с файлом Model.DesignedDiffs.xafml.
Итак, запустив редактор, мы видим следующее окно

Переходим в раздел NavigationItems и добавляем в пункт Items новый NavigationItem, щёлкнув ПКМ. Это будет наш корневой раздел, назовём его "Organization" (впишите имя в поле Id). Добавим во вновь созданный NavigationItem новый NavigationItem и выберем из списка "View" пункт "Employee_ListView". В полях "Id" и "Caption" выставим имя "Employees". В поле ImageName можно выбрать картинку, которая будет отображаться в пункте навигации. Сохраняем настройки и выходим. После прекомпиляции, в нашем окне добавится новая информация.

Нажав на кнопку "new" мы можем создать новый объект класса Employee.
Можно заметить "лишнее" поле "Oid", которое XPO использует для идентификации объектов в базе данных. Естественным желанием будет удалить это поле. Для этого открываем редактор модели и переходим в пункт Views/DemoModule/Employee_DetailView/Layout/Main/SimpleEditors и удаляем пункт XPObject.

Наложение ограничений на свойства

Наша программа позволяет создать объект класса Employee без имени и фамилии. Таких людей в базе мы видеть не хотим. XAF совместно с XPO позволяют наложить ограничения на поля бизнес-классов простым добавлением атрибутов. Чтобы использовать систему валидации нужно добавить две зависимости: DevExpress.Persistent.Base.v11.1 и DevExpress.ExpressApp.Validation.v11.1. В последнем модуле находится XAF модуль DevExpress.ExpressApp.Validation.ValidationModule. Мы должны добавить зависимость от него в наш модуль DemoModule. Для этого в конструктор DemoModule пишем строку
RequiredModuleTypes.Add(typeof(ValidationModule));
Теперь мы можем наложить атрибут DevExpress.Persistent.Validation.RuleRequiredFieldAttribute на поля FirstName и SecondName. Полученный код будет выглядеть так:
[RuleRequiredField(
"RuleRequiredField for DemoModule.Employee.FirstName",
DefaultContexts.Save,
"First Name cannot be empty")]
public string FirstName
{
get { return firstName; }
set { SetPropertyValue<string>("FirstName", ref firstName, value); }
}

[RuleRequiredField(
"RuleRequiredField for DemoModule.Employee.SecondName",
DefaultContexts.Save,
"Second Name cannot be empty")]
public string SecondName
{
get { return secondName; }
set { SetPropertyValue<string>("SecondName", ref secondName, value); }
}
После перекомпиляции проекта и обновления базы при попытке сохранить объект без указания имени и фамилии сотрудника мы получим соответствующую ошибку.
Первым параметром атрибута мы указываем уникальный идентификатор валидатора, вторым аргументом указываем, когда нужно запускать проверку. В последнем параметре указывается сообщение, отображаемое в сообщении об ошибке.
Кроме автоматической валидации, модуль DevExpress.ExpressApp.Validation создаёт кнопку "Validate" в тулбаре окна (зелёная галочка), нажав на которую мы можем принудительно провести валидацию.

Отношение ассоциации "один ко многим"

Очень часто возникает ситуация, когда один объект хранит коллекцию других объектов. Фреймворк XPO позволяет создать такое отношение в базе данных, а XAF обеспечит полноценное отображение и удобную работу с такими ассоциациями.
Давайте создадим класс Position и присвоим каждому сотруднику ссылку на объект этого класса. Каждый сотрудник может занимать только одну должность, в то время, как должность содержит список всех сотрудников, которые её занимают.
using System;
using DevExpress.Xpo;
using DevExpress.Persistent.Validation;

namespace DemoModule
{
public class Position :
XPObject
{
private string name;

public Position(Session session) :
base(session)
{
}

[RuleUniqueValue(
"RuleUniqueValue for DemoModule.Position.Name",
DefaultContexts.Save,
"Name must be unique")]
[RuleRequiredField(
"RuleRequiredField for DemoModule.Position.Name",
DefaultContexts.Save,
"Name cannot be empty")]
public string Name
{
get { return name; }
set { SetPropertyValue<string>("Name", ref name, value); }
}

[Association("Employee-Position")]
public XPCollection<Employee> Employees
{
get { return GetCollection<Employee>("Employees"); }
}
}
}
Обратите внимание, для гарантирования уникальности имени продукта, я применил атрибут RuleUniqueValue.
Для хранения коллекции объектов в базе данных используется тип DevExpress.Xpo.XPCollection. Чтобы реализовать ассоциацию "один ко многим" нужно объявить свойство типа XPCollection, имеющее только getter. Получить коллекцию ассоциированных объектов можно методом GetCollection. Чтобы XPO и XAF знали что с чем ассоциировано нужно добавить свойству атрибут Association с уникальным именем в параметре. Тот же атрибут нужно указать на другом конце ассоциации. Давайте создадим свойство Position в классе Employee.
private Position position;

[Association("Employee-Position")]
[RuleRequiredField(
"RuleRequiredField for DemoModule.Employee.Position",
DefaultContexts.Save,
"Position cannot be empty")]
public Position Position
{
get { return position; }
set { SetPropertyValue<Position>("Position", ref position, value); }
}
С этой стороны ассоциации свойство выглядит так, как и все остальные, за исключением атрибута Association. Откомпилировав приложение, обновив базу, отредактировав модель и запустив программу мы можем создать несколько должностей и сотрудников и сассоциировать их. Примерный результат работы можно видеть на следующем изображении.

Отношение ассоциации "многие ко многим"

Не всегда достаточно отношения "один ко многим". Часто бывает необходимо, чтобы один объект хранил коллекцию других объектов, а другие объекты хранили коллекции первых объектов. XPO и XAF поддерживают и эту концепцию. Для её демонстрации, предположим, что наша организация занимается производством нескольких видов продуктов. Каждый сотрудник может принимать участие в производстве нескольких продуктов, равно как и один продукт производится несколькими сотрудниками. Создадим простой класс Product.
using System;
using DevExpress.Xpo;
using DevExpress.Persistent.Validation;

namespace DemoModule
{
public class Product :
XPObject
{
private string name;

public Product(Session session) :
base(session)
{
}

[RuleUniqueValue(
"RuleUniqueValue for DemoModule.Product.Name",
DefaultContexts.Save,
"Name must be unique")]
[RuleRequiredField(
"RuleRequiredField for DemoModule.Product.Name",
DefaultContexts.Save,
"Namecannot be empty")]
public string Name
{
get { return name; }
set { SetPropertyValue<string>("Name", ref name, value); }
}

[Association("Employee-Product")]
public XPCollection<Employee> Employees
{
get { return GetCollection<Employee>("Employees"); }
}
}
}
Для описания ассоциации "многие ко многим" используется тот же принцип, что и при использовании ассоциации "один ко многим", только на обоих концах ассоциации находятся свойства, возвращающие коллекции.
[Association("Employee-Product")]
public XPCollection<Product> Products
{
get { return GetCollection<Product>("Products"); }
}
По традиции, компилируем проект, обновляем базу данных, редактируем модель, запускаем. После некоторых манипуляций с данными мы можем получить такой результат.

Пользовательские элементы управления

XAF поддерживает создание пользовательских элементов управления. Для этого нужно реализовать контроллер для одного или нескольких отображений и в нём создать объект одного из наследников класса DevExpress.ExpressApp.Actions.ActionBase. Приведу пример лишь самого простого из них -- SimpleAction, который представляет собой кнопку на тулбаре. Наш action будет отображаться в детальном просмотре объектов класса Employee и, при активации, очищать список продуктов сотрудника.

using System;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.Persistent.Base;

namespace DemoModule
{
public class EmployeeController :
ViewController<DetailView>
{
private SimpleAction cleanProductsAction;

public EmployeeController()
{
TargetObjectType = typeof(Employee);
cleanProductsAction = new SimpleAction(this,
"CleanProductsFromEmployee", PredefinedCategory.RecordEdit)
{
Caption = "Clean all products"
};
}

protected override void OnActivated()
{
base.OnActivated();
cleanProductsAction.Execute += OnCleanProductsActionExecute;
}

protected override void OnDeactivated()
{
base.OnDeactivated();
cleanProductsAction.Execute -= OnCleanProductsActionExecute;
}

private void OnCleanProductsActionExecute(object sender,
SimpleActionExecuteEventArgs e)
{
Employee employee = View.CurrentObject as Employee;
if(employee == null)
return;
int count = employee.Products.Count;
for(int i = count - 1; i >= 0; --i)
{
employee.Products.Remove(employee.Products[i]);
}
if(count > 0)
View.ObjectSpace.SetModified(employee);
}
}
}
Для создания контроллера требуется указать тип объекта, на представлении которого он будет активироваться. Первым параметром конструктора класса SimpleAction передаём ссылку на родительский контроллер. Затем передаётся идентификатор action'а и раздел, куда этот action будет помещён.

При активации контроллера вызывается метод OnActivated, в котором мы подписываемся на событие "Execute".
Контроллер содержит в себе ссылку на вид, на котором был активирован в свойстве View. Через этот объект мы можем получить текущий объект, обратившись к свойству CurrentObject. На всякий случай проверим его тип и, перебрав все продукты, удалим их. Изменения в списке XAF не заметит и кнопка "Save" не будет активирована. Для устранения этого эффекта вызываем метод SetModified из пространства объектов текущего вида.

Заключение

В этой статье я рассказал далеко не обо всех возможностях eXpress Application Framework. Чтобы узнать больше, нужно почитать официальную документацию. Возможно, в следующих статьях я расскажу что-то ещё. Скачать полный проект приведённого примера можно отсюда.

Как программно добавить кнопку в Панель формы 1с 8.1

Иногда бывает, что нужно вставить кнопку в форму программно. Как это можно реализовать в 1с 8.х

Мы хотим на Панель ОсновныеДействияФормы поместить новую кнопку

Процедура ПередОткрытием(Отказ, СтандартнаяОбработка)

Если ЭлементыФормы.ОсновныеДействияФормы.Кнопки.Найти("ИмяКнопки")=Неопределено Тогда // проверяем: есть ли уже такая кнопка?

ТипКнопки = ТипКнопкиКоманднойПанели.Действие;
ДействиеКнопки = Новый Действие("ОсновныеДействияФормыНазваниеИмяКнопки");

КнопкиФормы =ЭлементыФормы.ОсновныеДействияФормы.Кнопки;
НоваяКнопка =КнопкиФормы.Добавить("ИмяКнопки",ТипКнопки,"Имя Кнопки",ДействиеКнопки);
НоваяКнопка.Картинка = БиблиотекаКартинок.ПиктограммаВашейКнопки;
НоваяКнопка.Отображение = ОтображениеКнопкиКоманднойПанели.Картинка; // можно выбрать другие варианты "Авто", "Надпись" или "Надпись и Картинка"
КнопкиФормы.Сдвинуть(НоваяКнопка,(-1)*КнопкиФормы.Количество()-1); // сдвигаем кнопку в крайнее правое положение
КонецЕсли;

КонецПроцедуры

КонецПроцедуры




Программно добавлена кнопка с изображением значка Word


Как программно добавить кнопку в Панель формы 1с 8.1

Иногда бывает, что нужно вставить кнопку в форму программно. Как это можно реализовать в 1с 8.х

Мы хотим на Панель ОсновныеДействияФормы поместить новую кнопку

Процедура ПередОткрытием(Отказ, СтандартнаяОбработка)

Если ЭлементыФормы.ОсновныеДействияФормы.Кнопки.Найти("ИмяКнопки")=Неопределено Тогда // проверяем: есть ли уже такая кнопка?

ТипКнопки = ТипКнопкиКоманднойПанели.Действие;
ДействиеКнопки = Новый Действие("ОсновныеДействияФормыНазваниеИмяКнопки");

КнопкиФормы =ЭлементыФормы.ОсновныеДействияФормы.Кнопки;
НоваяКнопка =КнопкиФормы.Добавить("ИмяКнопки",ТипКнопки,"Имя Кнопки",ДействиеКнопки);
НоваяКнопка.Картинка = БиблиотекаКартинок.ПиктограммаВашейКнопки;
НоваяКнопка.Отображение = ОтображениеКнопкиКоманднойПанели.Картинка; // можно выбрать другие варианты "Авто", "Надпись" или "Надпись и Картинка"
КнопкиФормы.Сдвинуть(НоваяКнопка,(-1)*КнопкиФормы.Количество()-1); // сдвигаем кнопку в крайнее правое положение
КонецЕсли;

КонецПроцедуры

КонецПроцедуры




Программно добавлена кнопка с изображением значка Word


Нюхаем сеть через Linux

В этой статье речь пойдёт не столько о написании сниффера для Linux, сколько о структуре пакетов стека протоколов TCP/IP. Идея статьи взята из статьи на сайте xakep.ru.
Мы будем рассматривать пакеты, которые попадают к нам на сетевую карту. Эти пакеты относятся к протоколу Ethernet. Примерная структура этого пакета приведена в таблице ниже.
Ethernet заголовок
Ethernet данные (фрейм сетевого протокола)
IP заголовок
IP данные (фрейм транспортного протокола)
TCP или UDP заголовок
Данные приклодного протокола (HTTP, FTP, SMB итд).

Содержание статьи

Sniffer

Было бы не справедливо, если бы я приводил примеры анализа пакетов, не показав, как их получить. Для этой цели я написал сниффер, аналогичный тому, что описан в упомянутой статье.
#ifndef __LINUX_SNIFFER_SHIFFER_H__
#define __LINUX_SNIFFER_SHIFFER_H__


#include <set>
#include <string.h>
#include <stdexcept>
#include "Analyzer.h"


namespace LinuxSniffer {

class SnifferError :
public std::runtime_error
{
public:
SnifferError(const std::string & message) throw() :
std::runtime_error(message)
{
}

virtual ~SnifferError() throw()
{
}
}; // class SnifferError


class Sniffer
{
public:
explicit Sniffer(const std::string & device_name, bool sniff_all = false)
throw(SnifferError);
virtual ~Sniffer();
bool addAnalyzer(Analyzer * analyzer);
bool removeAnalyzer(Analyzer * analyzer);
void start() throw(SnifferError);
void stop();

private:
void deinit();
void makeSocket() throw(SnifferError);
void bindSocketToDevice() throw(SnifferError);
void setPromiscuousMode() throw(SnifferError);
void unsetPromiscuousMode();
void runAnalyzers(const unsigned char * frame, size_t frame_size);

private:
Sniffer(const Sniffer &);
Sniffer & operator = (const Sniffer &);

private:
const std::string m_device;
const bool m_sniff_all;
std::set<Analyzer *> m_analyzers;
int m_socket;
bool m_is_promiscuouse_mode_set;
bool m_is_stopping;
unsigned char * mp_frame_buffer;
static const size_t m_frame_buffer_size = 65536;
}; // class Sniffer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_SHIFFER_H__
Когда сниффер получает пакет, он запускает всех зарегистрированных анализаторов. Для их регистрации и служат методы addAnalyzer и removeAnalyzer.
Каждый анализатор должен реализовывать интерфейс Analyzer
class Analyzer
{
public:
virtual ~Analyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size) = 0;
}; // class Analyzer
Именно этим механизмом мы и будем пользоваться при анализе структуры пакетов стека TCP/IP.
Приведу полную реализацию класса Sniffer
#include <errno.h>
#include <netpacket/packet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/sockios.h>
#include "Sniffer.h"


using namespace LinuxSniffer;


Sniffer::Sniffer(const std::string & device_name, bool sniff_all)
throw(SnifferError) :
m_device(device_name),
m_sniff_all(sniff_all),
m_is_promiscuouse_mode_set(false),
m_is_stopping(false),
mp_frame_buffer(0)
{
makeSocket();
mp_frame_buffer = new unsigned char[m_frame_buffer_size];
}

Sniffer::~Sniffer()
{
deinit();
}

void Sniffer::deinit()
{
::close(m_socket);
unsetPromiscuousMode();
delete [] mp_frame_buffer;
}

bool Sniffer::addAnalyzer(Analyzer * analyzer)
{
return m_analyzers.insert(analyzer).second;
}

bool Sniffer::removeAnalyzer(Analyzer * analyzer)
{
return m_analyzers.erase(analyzer) > 0;
}

void Sniffer::makeSocket()
throw(SnifferError)
{
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
if(-1 == m_socket)
throw SnifferError(::strerror(errno));
try
{
bindSocketToDevice();
if(m_sniff_all)
setPromiscuousMode();
}
catch(...)
{
deinit();
throw;
}
}

void Sniffer::bindSocketToDevice()
throw(SnifferError)
{
const size_t device_name_len = m_device.length() + 1;
char * device = new char[device_name_len];
::strcpy(device, m_device.c_str());
device[m_device.length()] = '';
int setopt_result = ::setsockopt(m_socket, SOL_SOCKET,
SO_BINDTODEVICE, device, device_name_len);
delete [] device;
if(-1 == setopt_result)
throw SnifferError(::strerror(errno));
}

void Sniffer::setPromiscuousMode() throw(SnifferError)
{
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
iface.ifr_flags |= IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) < 0)
throw SnifferError(::strerror(errno));
m_is_promiscuouse_mode_set = true;
}

void Sniffer::unsetPromiscuousMode()
{
if(!m_is_promiscuouse_mode_set)
return;
ifreq iface;
::strcpy(iface.ifr_name, m_device.c_str());
if(::ioctl(m_socket, SIOCGIFFLAGS, &iface) >= 0)
{
iface.ifr_flags &= ~IFF_PROMISC;
if(::ioctl(m_socket, SIOCSIFFLAGS, &iface) >= 0)
m_is_promiscuouse_mode_set = false;
}
}

void Sniffer::start() throw(SnifferError)
{
while(!m_is_stopping)
{
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer,
m_frame_buffer_size, 0, 0, 0);
if(-1 == length)
throw SnifferError(::strerror(errno));
runAnalyzers(mp_frame_buffer, length);
}
}

void Sniffer::runAnalyzers(const unsigned char * frame, size_t frame_size)
{
for(std::set<Analyzer *>::iterator it = m_analyzers.begin();
m_analyzers.end() != it; ++it)
{
Analyzer * analyzer = *it;
if(0 != analyzer)
analyzer->analyze(frame, frame_size);
}
}

void Sniffer::stop()
{
m_is_stopping = true;
}
Конструктор принимает два параметра: имя устройства для прослушивания, например eth0 и флаг, включающий неразборчивое прослушивание. Для тех сетей, которые работают через хабы, этот режим будет прослушивать все пакеты, даже те, которые не адресованы Вашей машине.
Конструктор вызывает метод makeSocket, который создаёт сокет для прослушивания устройства. Делается это вызовом
m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL));
Я отошёл от того вызова, что был показан в статье на xakep.ru по причине того, что в man 2 socket написано
SOCK_PACKET
Устарело и не должно использоваться в новых программах; см. packet(7).
А страница man 7 packet говорит, что нужно использовать SOCK_RAW.
Далее метод bindSocketToDevice привязывает созданный сокет к устройству вызовом
::setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE, device, device_name_len); 
Последним шагом в подготовке сокета является необязательная установка опции неразборчивого прослушивания методом setPromiscuousMode. Для её установки мы должны получить структуру ifreq из сокета вызовом
::ioctl(m_socket, SIOCGIFFLAGS, &iface)
добавить флаг IFF_PROMISC в поле ifr_flags
iface.ifr_flags |= IFF_PROMISC;
и записать сруктуру обратно
::ioctl(m_socket, SIOCSIFFLAGS, &iface)
Всё готово к запуску сниффера. Метод start вызывает в цикле функцию recvfrom и передаёт полученный буфер анализаторам.
ssize_t length = ::recvfrom(m_socket, mp_frame_buffer, m_frame_buffer_size, 0, 0, 0);

Стек протоколов TCP/IP

Прежде чем перейти к описанию анализатора, давайте рассмотрим фреймы протоколов стека TCP/IP.

Ethernet

Пакеты этого протокола являются низшими из тех, что мы можем получить. Существует несколько версий пакетов ethernet, мы рассмотрим самую популярную - вторую версию. В таблице ниже показана структура фрейма Ethernet 2
Смещение Размер Описание
0 байт 6 байт MAC адрес назначения
6 байт 6 байт MAC адрес источника
12 байт 2 байта Тип Etherner
14 байт 46 - 1500 байт Данные
Последние 4 байта 4 байта CRC контрольная сумма
Для разбора фреймов всех протоколов я написал базовый класс
#ifndef __LINUX_SNIFFER_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_PROTOCOL_FRAME_H__

#include <string>
#include <sys/types.h>
#include <stdint.h>

namespace LinuxSniffer {


class ProtocolFrame
{
public:
ProtocolFrame(const std::string & protocol_name) :
m_protocol_name(protocol_name),
m_data_offset(0)
{
}

virtual ~ProtocolFrame()
{
}

const std::string & getName() const
{
return m_protocol_name;
}

virtual bool init(const uint8_t * buffer, size_t buffer_size) = 0;

size_t getDataOffset() const
{
return m_data_offset;
}

protected:
void setDataOffset(size_t offset)
{
m_data_offset = offset;
}

private:
const std::string m_protocol_name;
size_t m_data_offset;
}; // class ProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_PROTOCOL_FRAME_H__
Этот класс хранит и возвращает имя протокола описываемого фрейма и смещение данных от начала фрейма. Также класс передоставляет абстрактный метод инициализации из буфера фрейма и его размера.
Класс EthernetFrameV2 наследуется от ProtocolFrame и добавляет специфическую информацию.
#ifndef __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
#define __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__

#include <cstring>
#include "ProtocolFrame.h"
#include "MacAddress.h"

namespace LinuxSniffer {

class EthernetFrameV2 :
public ProtocolFrame
{
public:
EthernetFrameV2() :
ProtocolFrame("Ethernet Version 2")
{
}

virtual ~EthernetFrameV2()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const MacAddress & getSourceMacAddress() const
{
return m_src_mac_addr;
}

const MacAddress & getDestinationMacAddress() const
{
return m_dest_mac_addr;
}

static const uint8_t (& getEthernetType())[2]
{
static bool is_init = false;
static uint8_t type[2];
if(!is_init)
{
::memcpy(type, "x08x00", 2);
is_init = true;
}
return type;
}

private:
MacAddress m_src_mac_addr;
MacAddress m_dest_mac_addr;
}; // class EthernetFrameV2

} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__
Дополнительные поля являются MAC адресами отправителя и получателя. Эти поля имеют тип MacAddress, который описан простым классом
#ifndef __LINUX_SNIFFER_MAC_ADDRESS_H__
#define __LINUX_SNIFFER_MAC_ADDRESS_H__

#include <stdint.h>
#include <string>
#include <cstdio>

namespace LinuxSniffer {


class MacAddress
{
public:
MacAddress() :
b0(0),
b1(0),
b2(0),
b3(0),
b4(0),
b5(0)
{
}

explicit MacAddress(const uint8_t address[6]) :
b0(address[0]),
b1(address[1]),
b2(address[2]),
b3(address[3]),
b4(address[4]),
b5(address[5])
{
}

std::string toString() const
{
char str[16];
::sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", b0, b1, b2, b3, b4, b5);
return str;
}

public:
uint8_t b0;
uint8_t b1;
uint8_t b2;
uint8_t b3;
uint8_t b4;
uint8_t b5;
}; // class MacAddress


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_MAC_ADDRESS_H__
Реализацтя метода init класса EthernetFrameV2 сводится к проверке версии протокола Ethernet и получению MAC адресов отправителя и получателя. Для простоты, получение контрольной суммы опустим. Для того, чтобы получить все необходимые поля, достаточно привести буфер фрейма к типу ether_header, объявленному в netinet/ether.h.
#include <cstring>
#include <netinet/ether.h>
#include "EthernetFrameV2.h"

using namespace LinuxSniffer;


bool EthernetFrameV2::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(ether_header))
return false;
const ether_header * hdr = reinterpret_cast<const ether_header *>(buffer);
if(::memcmp(&hdr->ether_type, getEthernetType(), sizeof(getEthernetType())))
return false;
setDataOffset(sizeof(ether_header));
m_src_mac_addr = MacAddress(hdr->ether_shost);
m_dest_mac_addr = MacAddress(hdr->ether_shost);
return true;
}

Internet Protocol

В секции данных фрейма протокола ethernet хранится фрейм протокола IP. Этот протокол добавляет к пакету информацию об IP адресах отправителя и получателя и множество служебных данных. В следующей таблице показана структура фрейма IP версии 4.
Байты Смещение в битах Размер в битах Описание
0 0 4 Версия
4 4 Размер заголовка
1 8 6 Точка кода дифференцированных услуг (Differentiated services code point)
14 2 Явное уведомление о перегруженности (Explicit congestion notification)
2-3 16 16 Размер пакета
4-5 32 16 Идентификатор
6-7 48 3 Флаги
51 13 Смещение фрагмента
8 64 8 Время жизни
9 72 8 Протокол
10 80 16 Контрольная сумма заголовка
11-15 96 32 IP адрес источника
16-20 128 32 IP адрес назначения
20-24 160 32 Опции (если размер заголовка > 5)
20+ или 24+ 160 или 192 - Данные
В ранних версиях спецификации протокола "Точка кода дифференцированных услуг" и "Явное уведомление о перегруженности" были объединены в одно поле "Тип сервиса". Для того, чтобы использовать новое деление участники обмена должны договориться об этом. Я не буду разделять эти поля и буду использовать тип сервиса.
В поле "Флаги" биты от старшего к младшему означают:
0: Зарезервирован, должен быть равен 0;
1: Не фрагментировать;
2: У пакета еще есть фрагменты.
"Идентификатор" используется для предоставления информации о фрагментации пакета. Поле "Протокол" сообщает идентификатор протокола, фрейм которого находится в данных.
Думаю, остальные поля не нуждаются в комментариях.
Класс, описывающий IP фрейм приведён ниже
#ifndef __LINUX_SNIFFER_IP_FRAME_V4_H__
#define __LINUX_SNIFFER_IP_FRAME_V4_H__

#include <string>
#include "ProtocolFrame.h"

namespace LinuxSniffer {


class IpFrameV4 :
public ProtocolFrame
{
public:
IpFrameV4() :
ProtocolFrame("Internet Protocol Version 4"),
m_header_length(0),
m_tos(0),
m_package_size(0),
m_id(0),
m_flags(0),
m_frag_offset(0),
m_time_to_life(0),
m_protocol(0),
m_checksum(0)
{
}

virtual ~IpFrameV4()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

const std::string & getSourceAddress() const
{
return m_src_ip_addr;
}

const std::string & getDestinationAddress() const
{
return m_dest_ip_addr;
}

uint8_t getHeaderLength() const
{
return m_header_length;
}

uint8_t getTypeOfService() const
{
return m_tos;
}

uint16_t getPackageSize() const
{
return m_package_size;
}

uint16_t getId() const
{
return m_id;
}

uint8_t getFlags() const
{
return m_flags;
}

uint16_t getFragmintationOffset() const
{
return m_frag_offset;
}

uint8_t getTimeToLife() const
{
return m_time_to_life;
}

uint8_t getProtocolId() const
{
return m_protocol;
}

uint16_t getHeaderCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_ipv4_version = 4;

private:
void splitFragmentOffsetAndFlags();

private:
std::string m_src_ip_addr;
std::string m_dest_ip_addr;
uint8_t m_header_length;
uint8_t m_tos;
uint16_t m_package_size;
uint16_t m_id;
uint8_t m_flags;
uint16_t m_frag_offset;
uint8_t m_time_to_life;
uint8_t m_protocol;
uint16_t m_checksum;
}; // class IpFrameV4


} // namespace LinuxSniffer



#endif // __LINUX_SNIFFER_IP_FRAME_V4_H__
Для того, чтобы получить все поля из буфера фрейма достаточно привести указатель на него к указателю на структуру iphdr, объявленной в файле netinet/ip.h. В реализации метода init класса IpFrameV4 именно так и сделано
#include <netinet/ip.h>
#include <arpa/inet.h>
#include "IpFrameV4.h"



using namespace LinuxSniffer;

bool IpFrameV4::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(iphdr) || 0 == buffer)
return false;
const iphdr * ip_header = reinterpret_cast<const iphdr *>(buffer);
if(m_ipv4_version != ip_header->version)
return false;
in_addr addr;
addr.s_addr = ip_header->saddr;
m_src_ip_addr = ::inet_ntoa(addr);
addr.s_addr = ip_header->daddr;
m_dest_ip_addr = ::inet_ntoa(addr);
m_header_length = ip_header->ihl;
m_tos = ip_header->tos;
m_package_size = ip_header->tot_len;
m_id = ip_header->id;
m_frag_offset = ip_header->frag_off;
m_time_to_life = ip_header->ttl;
m_protocol = ip_header->protocol;
m_checksum = ip_header->check;
setDataOffset(m_header_length);
splitFragmentOffsetAndFlags();
return true;
}

void IpFrameV4::splitFragmentOffsetAndFlags()
{
union
{
struct
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint16_t flags : 3;
uint16_t frag_offset : 13;
#elif __BYTE_ORDER == __BIG_ENDIAN
uint16_t frag_offset : 13;
uint16_t flags : 3;
#endif
} spl;
uint16_t num;
} splitter;

splitter.num = m_frag_offset;
m_flags = splitter.spl.flags;
m_frag_offset = splitter.spl.frag_offset;
}
Смещение фрагмента и флаги в структуре iphdr объединены. Для их разделения я написал метод splitFragmentOffsetAndFlags.

Transmission Control Protocol

Протоколы траспортного уровня добавляют сведения о портах источника и назначения. Для описания общих свойств всех фреймов транспортных протоколов я ввёл класс TransportProtocolFrame, унаследованный от ProtocolFrame.
#ifndef __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
#define __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__

#include "ProtocolFrame.h"


namespace LinuxSniffer {


class TransportProtocolFrame :
public ProtocolFrame
{
public:
TransportProtocolFrame(const std::string & protocol_name) :
ProtocolFrame(protocol_name),
m_src_port(0),
m_dest_port(0)
{
}

virtual ~TransportProtocolFrame()
{
}

uint16_t getSourcePort() const
{
return m_src_port;
}

uint16_t getDestinationPort() const
{
return m_dest_port;
}

protected:
void setSourcePort(uint16_t port)
{
m_src_port = port;
}

void setDestinationPort(uint16_t port)
{
m_dest_port = port;
}

private:
uint16_t m_src_port;
uint16_t m_dest_port;
}; // class TransportProtocolFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__
Структура фрейма протокола TCP, являющегося одним из протоколов транспортного уровня, показана в следующей таблице.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-8 32 32 Номер последовательности
8-12 64 32 Номер подтверждения последовательности
13-14 96 4 Смещение данных
100 6 Зарезервировано
106 6 Флаги
15-16 112 16 Размер окна
17-18 128 16 Контрольная сумма
19-20 144 16 Указатель важности
21+ 160 - Необязательные опции
За опциями или 21+ За опциями или 160 - Данные
Протокол TCP контролирует последовательность пакетов. Именно для этого нужны поля "Номер последовательности" и "Номер подтверждения последовательности".
"Флаги":
URG - Поле "Указатель важности" задействовано;
ACK - Поле "Номер подтверждения" задействовано;
PSH - Инструктирует получателя протолкнуть данные, накопившиеся в приемном буфере, в приложение пользователя;
RST - Оборвать соединения;
SYN - Синхронизация номеров последовательности;
FIN - флаг указывает на завершение соединения.
"Размер окна" - это число байтов, которые получатель готов принять.
"Указатель важности" указывает на номер октета, которым заканчиваются важные данные. Это поле игнорируется, если флаг URG не установлен.
Для чтения фрейма протокола TCP я расширил класс TransportProtocolFrame классом TcpFrame.
#ifndef __LINUX_SNIFFER_TCP_FRAME_H__
#define __LINUX_SNIFFER_TCP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class TcpFrame :
public TransportProtocolFrame
{
public:
TcpFrame() :
TransportProtocolFrame("Transmission Control Protocol"),
m_sequence_number(0),
m_ascknowledgment_number(0),
m_flag_fin(false),
m_flag_syn(false),
m_flag_rst(false),
m_flag_psh(false),
m_flag_ack(false),
m_flag_urg(false),
m_window_size(0),
m_checksum(0),
m_urgent_ptr(0)
{
}

virtual ~TcpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint32_t getSequenceNumber() const
{
return m_sequence_number;
}

uint32_t getAscknowledgmentNumber() const
{
return m_ascknowledgment_number;
}

bool isFinFlagSet() const
{
return m_flag_fin;
}

bool isSynFlagSet() const
{
return m_flag_syn;
}

bool isRstFlagSet() const
{
return m_flag_rst;
}

bool isPshFlagSet() const
{
return m_flag_psh;
}

bool isAckFlagSet() const
{
return m_flag_ack;
}

bool isUrgFlagSet() const
{
return m_flag_urg;
}

uint16_t getWindowSize() const
{
return m_window_size;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

uint16_t getUrgentPtr() const
{
return m_urgent_ptr;
}

public:
static const uint8_t m_protocol_id = 6;

private:
uint32_t m_sequence_number;
uint32_t m_ascknowledgment_number;
bool m_flag_fin;
bool m_flag_syn;
bool m_flag_rst;
bool m_flag_psh;
bool m_flag_ack;
bool m_flag_urg;
uint16_t m_window_size;
uint16_t m_checksum;
uint16_t m_urgent_ptr;
}; // class TcpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_TCP_FRAME_H__
Как и в случае с IP, для фрейма TCP в заголовочных файлах linux припасена структура tcphdr. Находится она в файле netinet/tcp.h. Реализуем метод init с использованием этой структуры.
#include <netinet/tcp.h>
#include "TcpFrame.h"

using namespace LinuxSniffer;


bool TcpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(tcphdr))
return false;
const tcphdr * tcp_header = reinterpret_cast<const tcphdr *>(buffer);
setSourcePort(tcp_header->source);
setDestinationPort(tcp_header->dest);
setDataOffset(tcp_header->doff);
m_sequence_number = tcp_header->seq;
m_ascknowledgment_number = tcp_header->ack_seq;
m_flag_fin = tcp_header->fin != 0;
m_flag_syn = tcp_header->syn != 0;
m_flag_rst = tcp_header->rst != 0;
m_flag_psh = tcp_header->psh != 0;
m_flag_ack = tcp_header->ack != 0;
m_flag_urg = tcp_header->urg != 0;
m_window_size = tcp_header->window;
m_checksum = tcp_header->check;
m_urgent_ptr = tcp_header->urg_ptr;
return true;
}

User Datagram Protocol

Последний протокол, который мы рассмотрим в этой статье - UDP. Это простой протокол транспортного уровня, который ни как не контролирует доставку пакетов и не устанавливает соединения. По этим причинам, фрейм этого протокола весьма прост.
Байты Смещение в битах Размер в битах Описание
0-1 0 16 Порт источника
2-3 16 16 Порт назначения
4-5 32 16 Длина дейтаграммы
6-7 48 16 Контрольная сумма
8+ 64 - Данные
В этой таблице всё понятно без коментариев.
Второй класс, унаследованный от TransportProtocolFrame - UdpFrame.
#ifndef __LINUX_SNIFFER_UDP_FRAME_H__
#define __LINUX_SNIFFER_UDP_FRAME_H__

#include "TransportProtocolFrame.h"

namespace LinuxSniffer {


class UdpFrame :
public TransportProtocolFrame
{
public:
UdpFrame() :
TransportProtocolFrame("User Datagram Protocol")
{
}

virtual ~UdpFrame()
{
}

virtual bool init(const uint8_t * buffer, size_t buffer_size);

uint16_t getDatagramLength() const
{
return m_datagram_length;
}

uint16_t getCheckSum() const
{
return m_checksum;
}

public:
static const uint8_t m_protocol_id = 17;

private:
uint16_t m_datagram_length;
uint16_t m_checksum;
}; // class UdpFrame


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_UDP_FRAME_H__
Аналогично фреймам протоколов IP и TCP, реализуем метод init с использованием структуры udphdr, объявленной в файле netinet/udp.h.
#include <netinet/udp.h>
#include "UdpFrame.h"

using namespace LinuxSniffer;


bool UdpFrame::init(const uint8_t * buffer, size_t buffer_size)
{
if(buffer_size < sizeof(udphdr))
return false;
const udphdr * udp_header = reinterpret_cast<const udphdr *>(buffer);
setSourcePort(udp_header->source);
setDestinationPort(udp_header->dest);
setDataOffset(sizeof(udphdr));
m_datagram_length = udp_header->len;
m_checksum = udp_header->check;
return true;
}

Анализатор

Теперь, когда мы знаем структуру основных протоколов, ничего не стоит проанализировать данные, которые мы получаем от сниффера. Для этого реализуем интерфейс Analyzer.
#ifndef __LINUX_SNIFFER_IP_ANALYZER_H__
#define __LINUX_SNIFFER_IP_ANALYZER_H__

#include <utility>
#include "Analyzer.h"


namespace LinuxSniffer {

class TransportProtocolFrame;

class IpAnalyzer :
public Analyzer
{
public:
virtual ~IpAnalyzer() { }
virtual void analyze(const uint8_t * frame, size_t frame_size);

private:
size_t tryEthV2Analyze(const uint8_t * frame, size_t frame_size);
std::pair<size_t, uint8_t> tryIpV4Analyze(const uint8_t * frame, ize_t frame_size);
size_t tryTcpAnalyze(const uint8_t * frame, size_t frame_size);
size_t tryUdpAnalyze(const uint8_t * frame, size_t frame_size);
void printTransportProtocolFrame(const TransportProtocolFrame & frame) const;
}; // class IpAnalyzer


} // namespace LinuxSniffer


#endif // __LINUX_SNIFFER_IP_ANALYZER_H__
#include <iostream>
#include "IpAnalyzer.h"
#include "EthernetFrameV2.h"
#include "IpFrameV4.h"
#include "TcpFrame.h"
#include "UdpFrame.h"

using namespace LinuxSniffer;

void IpAnalyzer::analyze(const uint8_t * frame, size_t frame_size)
{
const size_t ip_offset = tryEthV2Analyze(frame, frame_size);
if(0 == ip_offset)
return;

const uint8_t * ip_frame = &frame[ip_offset];
const size_t ip_frame_size = frame_size - ip_offset;
std::pair<size_t, uint8_t> ip_analyze_result =
tryIpV4Analyze(ip_frame, ip_frame_size);
if(0 == ip_analyze_result.first)
return;


const uint8_t * transport_frame = ip_frame + ip_analyze_result.first;
const size_t transport_frame_size = ip_frame_size - ip_analyze_result.first;
size_t application_protocol_data_offset;
switch(ip_analyze_result.second)
{
case TcpFrame::m_protocol_id:
application_protocol_data_offset = tryTcpAnalyze(
transport_frame, transport_frame_size);
break;
case UdpFrame::m_protocol_id:
application_protocol_data_offset = tryUdpAnalyze(
transport_frame, transport_frame_size);
break;
default:
std::cout << "======= Unsupported transport protocol ======n";
}

std::cout << std::endl;
}

size_t IpAnalyzer::tryEthV2Analyze(const uint8_t * frame, size_t frame_size)
{
EthernetFrameV2 eth_frame;
if(!eth_frame.init(frame, frame_size))
return 0;

std::cout << "====== " << eth_frame.getName() << " ======n" <<
"Source MAC Address: " << eth_frame.getSourceMacAddress().toString() <<
std::endl << "Destination MAC Address: " <<
eth_frame.getDestinationMacAddress().toString() << std::endl;

return eth_frame.getDataOffset();
}

std::pair<size_t, uint8_t> IpAnalyzer::tryIpV4Analyze(const uint8_t * frame,
size_t frame_size)
{
IpFrameV4 ip_frame;
if(!ip_frame.init(frame, frame_size))
return std::make_pair(0, 0);

std::cout << "====== " << ip_frame.getName() << " ======n" <<
"Source IP Address: " << ip_frame.getSourceAddress() << std::endl <<
"Destination IP Address: " << ip_frame.getDestinationAddress() <<
std::endl <<
"Header Length: " << std::dec <<
static_cast<uint32_t>(ip_frame.getHeaderLength()) << std::endl <<
"Type Of Service: " <<
static_cast<uint32_t>(ip_frame.getTypeOfService())<< std::endl <<
"Package Size: "<< ip_frame.getPackageSize() << std::endl <<
"Identification: " << ip_frame.getId() << std::endl <<
"Flags: " << static_cast<uint32_t>(ip_frame.getFlags())<< std::endl <<
"Fragmentation Offset: " << ip_frame.getFragmintationOffset() <<
std::endl <<
"Time To Live: " << static_cast<uint32_t>(ip_frame.getTimeToLife()) <<
std::endl <<
"Transport Protocol ID: " <<
static_cast<uint32_t>(ip_frame.getProtocolId()) << std::endl <<
"CRC-16 Header CheckSum:" << std::hex << ip_frame.getHeaderCheckSum() <<
std::endl;
return std::make_pair(ip_frame.getDataOffset(), ip_frame.getProtocolId());
}

size_t IpAnalyzer::tryTcpAnalyze(const uint8_t * frame, size_t frame_size)
{
TcpFrame tcp_frame;
if(!tcp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(tcp_frame);

std::cout << std::dec <<
"Sequence Number: " << tcp_frame.getSequenceNumber() << std::endl <<
"Ascknowledgment Number: " << tcp_frame.getAscknowledgmentNumber() <<
std::endl <<
"Window Size: " << tcp_frame.getWindowSize() << std::endl <<
"Urgent Pointer: " << tcp_frame.getUrgentPtr() << std::endl <<
"Flags:n" <<
" FIN: " << std::boolalpha << tcp_frame.isFinFlagSet() << std::endl <<
" SYN: " << tcp_frame.isSynFlagSet() << std::endl <<
" RST: " << tcp_frame.isRstFlagSet() << std::endl <<
" PSH: " << tcp_frame.isPshFlagSet() << std::endl <<
" ACK: " << tcp_frame.isAckFlagSet() << std::endl <<
" URG: " << tcp_frame.isUrgFlagSet() << std::endl <<
"CheckSum: " << std::hex << tcp_frame.getCheckSum() << std::endl;

return tcp_frame.getDataOffset();
}

size_t IpAnalyzer::tryUdpAnalyze(const uint8_t * frame, size_t frame_size)
{
UdpFrame udp_frame;
if(!udp_frame.init(frame, frame_size))
return 0;

printTransportProtocolFrame(udp_frame);
std::cout << std::dec <<
"Datagram Length: " << std::dec << udp_frame.getDatagramLength() <<
std::endl <<
"Datagram CheckSum: " << std::hex << udp_frame.getCheckSum() <<
std::endl;
return udp_frame.getDataOffset();
}

void IpAnalyzer::printTransportProtocolFrame(
const TransportProtocolFrame & frame) const
{
std::cout << "====== " << frame.getName() << " ======n" <<
std::dec << "Source Port: " << frame.getSourcePort() << std::endl <<
"Destination Port: " << frame.getDestinationPort() <<
std::endl;
}
Всё очень просто. Пытаемся преобразовать полученный фрейм в фрейм протокола Ethernet версии 2. Затем, используя полученное смещение данных, получаем фрейм IP. Проанализировав номер протокола, пытаемся получить данные либо о TCP, либо о UDP фрейме. Попутно выводим всю полученную информацию на консоль. В конце анализа у нас остаётся переменная, хранящая смещение данных прикладного протокола, которую Вы можете применить для дальнейшего разворачивания стека.

Заключение

Полученную программу нельзя запустить от пользователя, чей UID не равен нулю. Это и естественно, ведь мы слишком много себе позволили.
Исходные тексты программы можно скачать здесь.

Как выгрузить и загрузить архив с базой в 1с 7.7

Эти операции производятся в конфигураторе.

Выгрузка базы в архив

Запускаем 1с в режиме конфигуратора (естественно у вас должны быть права доступа на работу в конфигураторе). Выбираем Администрирование->Выгрузить данные...



Появляется окно Выгрузка данных.

Выбираем каталог, куда сохранится файл, и вводим имя файла



После выгрузки появляется сообщение: Выгрузка успешно завершена

База будет выгружена в архив. В архиве будет содержаться 3 файла: 
  • 1Cv77.dat - Файл самой базы. Этот имеет текстовый формат. Может быть огромного размера.
  • 1Cv7.MD - Файл конфигурации
  • users.usr - Файл с данными о пользователях (необязательно, может и отсутствавать, если флажок "Выгружать сведения о пользователях " при выгрузки базы не будет включен)
Если ваша база очень большая, то может случится, что выгрузка завершится с ошибкой. Есть проблема: когда файл 1Cv77.dat превышает 2 Гб, то система не может сжать такой файл из-за ограничения архиватора zip. Для этих случаев лучше воспользоваться патчем Romix. Найти его можно здесь

Так же желательно сохранять все важные файлы из каталога информационной базы. Это можно заархивировать в ручном режиме.


Загрузка базы в архив

Загрузка базы происходит аналогичным образом. Через команду меню Загрузить данные... 


В окне Загрузка данных выбирается архив базы
при загрузке могут появляться различные окна с предупреждениями о потере и замене данных. На всем окнах жмите ("Да" или "Принять" или "Ок").

Важно помнить, что в архиве должны находиться вышеуказанные файлы 1Cv77.dat,1Cv7.MD и users.usr, иначе может произойти ошибка или база вообще не загрузится.

Вот несколько советов по выгрузке и загрузке баз данных в 1с 7.7 .

В этом видео также описывается Создание резервной копии информационной базы:


1c 8.1: Как выгрузить конфигурацию в файл 

Как выгрузить и загрузить архив с базой в 1с 7.7

Эти операции производятся в конфигураторе.

Выгрузка базы в архив

Запускаем 1с в режиме конфигуратора (естественно у вас должны быть права доступа на работу в конфигураторе). Выбираем Администрирование->Выгрузить данные...



Появляется окно Выгрузка данных.

Выбираем каталог, куда сохранится файл, и вводим имя файла



После выгрузки появляется сообщение: Выгрузка успешно завершена

База будет выгружена в архив. В архиве будет содержаться 3 файла: 
  • 1Cv77.dat - Файл самой базы. Этот имеет текстовый формат. Может быть огромного размера.
  • 1Cv7.MD - Файл конфигурации
  • users.usr - Файл с данными о пользователях (необязательно, может и отсутствавать, если флажок "Выгружать сведения о пользователях " при выгрузки базы не будет включен)
Если ваша база очень большая, то может случится, что выгрузка завершится с ошибкой. Есть проблема: когда файл 1Cv77.dat превышает 2 Гб, то система не может сжать такой файл из-за ограничения архиватора zip. Для этих случаев лучше воспользоваться патчем Romix. Найти его можно здесь

Так же желательно сохранять все важные файлы из каталога информационной базы. Это можно заархивировать в ручном режиме.


Загрузка базы в архив

Загрузка базы происходит аналогичным образом. Через команду меню Загрузить данные... 


В окне Загрузка данных выбирается архив базы
при загрузке могут появляться различные окна с предупреждениями о потере и замене данных. На всем окнах жмите ("Да" или "Принять" или "Ок").

Важно помнить, что в архиве должны находиться вышеуказанные файлы 1Cv77.dat,1Cv7.MD и users.usr, иначе может произойти ошибка или база вообще не загрузится.

Вот несколько советов по выгрузке и загрузке баз данных в 1с 7.7 .

В этом видео также описывается Создание резервной копии информационной базы:


1c 8.1: Как выгрузить конфигурацию в файл