31.03.2009

Бинарнарный сериализатор. (BinarySerializer)

Данный сериализатор формирует из типа, байт последовательность. Формат последовательности следующий:

<class_element_count> - количество полей в классе (4 байта)
[<element>, <element>] - поля класса

где <element> - это простой тип данных:
<size> - размер тела (4 байта)
- простой тип переведенный в байты
или массив представление сериализованного массива аналогично классу (см. выше)

Данный сериализатор поддерживает следующие простые типы:
bool, short, int, long(Int64), float(Single), double, Guid.

Механизм содержит три основные части:
1. Атрибуты, которые указывают, что класс или поле необходимо сериализовать.
2. Временный контейнер, который используется для хранения данных при сериализации.
3. Собственно сам механизм сериализации.

Атрибуты:


// Атрибут для класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ClassToBinaryAttribute : Attribute
{
}

/// Атрибут для поля класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldToBinaryAttribute : Attribute
{
}


Временный контейнер:


/// <summary>
/// Контейнер для хранение сериализованных бинарных данных
/// </summary>
public abstract class BinaryContainer
{
protected List<BinaryContainer> _childs =
new List<BinaryContainer>();
/// <summary>
/// Дочерние элементы контейнера
/// </summary>
public List<BinaryContainer> Childs
{
get { return this._childs; }
}

protected byte[] _body;
/// <summary>
/// Сериализованные данные
/// </summary>
public byte[] Body
{
//get { return _body; }
set { _body = value; }
}

/// <summary>
/// Получить размер бинарной информации
/// </summary>
/// <returns></returns>
public abstract int GetBodySize();

/// <summary>
/// Получить бинарные данные
/// </summary>
/// <returns></returns>
public abstract byte[] GetBody();
}

/// <summary>
/// Контейнер для поля
/// </summary>
class BinaryField : BinaryContainer
{
public override int GetBodySize()
{
if (this._body != null)
return this._body.Length + 4;
else
return 0;
}

public override byte[] GetBody()
{
if (this._body != null)
{
// весь размер буфера: размер + данные
int fullSize = this._body.Length + 4;
byte[] newBody = new byte[fullSize];

Buffer.BlockCopy(
BitConverter.GetBytes(this._body.Length), 0, newBody, 0, 4);
Buffer.BlockCopy(
this._body, 0, newBody, 4, this._body.Length);

return newBody;
}
else
return null;
}
}

/// <summary>
/// Контейнер для класса или массива
/// </summary>
class BinaryArray : BinaryContainer
{
public int ArrayElementCount
{
get { return this._childs.Count; }
}

public override int GetBodySize()
{
int size = 4;

if (this._childs.Count > 0)
{
foreach (BinaryContainer child in this._childs)
size += child.GetBodySize();

return size;
}
else
return size;
}

public override byte[] GetBody()
{
if (this._childs.Count > 0)
{
int bodySize = this.GetBodySize();
this._body = new byte[bodySize];

Buffer.BlockCopy(
BitConverter.GetBytes(
this.ArrayElementCount), 0, this._body, 0, 4);

int offset = 4;
foreach (BinaryContainer child in this._childs)
{
int childSize = child.GetBodySize();
byte[] childBody = child.GetBody();
Buffer.BlockCopy(
childBody, 0, this._body, offset, childSize);
offset += childSize;
}

return this._body;
}
else
return new byte[4];
}

}


Контейнер предоставляет два типа:
-тип для временного хранения простых типов (BinaryArray);
-тип для временного хранения сложных типов, таких как классы и массивы (BinaryField).
Два метода GetBodySize и GetBody предоставляют возможность для получения размера тела и сам байтовый массив. Паттерн Dependency Injection позволяет прозрачно, для основного механизма сериализации, использовать типы контейнера.

Сериализатор:


/// <summary>
/// Бинарный сериализатор
/// Поддержка следующих типов данных:
/// классы, массивы,
/// bool, short, int32, long(int64), float(single), double, Guid
/// Сереализация generic типов не поддерживается
/// </summary>
public class BinarySerializer
{
/// <summary>
/// Сериализация объекта
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="obj">Объект</param>
/// <returns>Массив байт</returns>
public byte[] Serialize<TType>(TType obj)
{
BinaryContainer binaryContainer = this.ClassSerializer(typeof(TType), obj);
byte[] result = binaryContainer.GetBody();

return result;
}

#region Хелперы для сериализации
/// <summary>
/// Сериализация объекта
/// </summary>
/// <param name="type">Тип объекта</param>
/// <param name="obj">Объект</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer ClassSerializer(Type type, object obj)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

BinaryContainer classContainer = new BinaryArray();

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
ypeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1) // поле сложного типа
{
classContainer.Childs.Add(
this.ClassSerializer(fInfo.FieldType, fInfo.GetValue(obj)));
}
else // поле простого типа
{
if (fInfo.FieldType.IsArray) // поле массив
{
//сериализация массива
classContainer.Childs.Add(
this.ArraySerializer(fInfo, obj));
}
else // поле простого типа
classContainer.Childs.Add(
this.SimpleTypeSerializer(
fInfo.FieldType, fInfo.GetValue(obj)));
}
}
}

return classContainer;
}

/// <summary>
/// Сериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="obj">Объект</param>
/// <returns>Бинарный контейнер с сереализованным массивом</returns>
private BinaryContainer ArraySerializer(FieldInfo fInfo, object obj)
{
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
Array array = (Array)fInfo.GetValue(obj);

bool isClass = false;
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1)
isClass = true;

// контейнер под хранения массива в бинарном виде
BinaryContainer binaryArray = new BinaryArray();

// испульзуем энумератор для передвижения по массиву
IEnumerator arrayEnumerator = array.GetEnumerator();
while (arrayEnumerator.MoveNext() &&
arrayEnumerator.Current != null)
{
BinaryContainer arrayElement = null;
if (isClass)
{
arrayElement =
this.ClassSerializer(
elementType, arrayEnumerator.Current);
}
else
{
// получение бинарного представления для элемента массива
arrayElement =
this.SimpleTypeSerializer(
elementType, arrayEnumerator.Current);
}

// добавление простого типа к бинарному контейнеру
binaryArray.Childs.Add(arrayElement);
}

return binaryArray;
}

/// <summary>
/// Сериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType">Тип поля</param>
/// <param name="value">Значение поля</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer SimpleTypeSerializer(Type fieldType, object value)
{
// буфер для хранения сериализовнных данных
byte[] buffer = null;

if (fieldType == typeof(Boolean))
{
buffer = BitConverter.GetBytes((Boolean)value);
}
else if (fieldType == typeof(Int16))
{
buffer = BitConverter.GetBytes((Int16)value);
}
else if (fieldType == typeof(Int32))
{
buffer = BitConverter.GetBytes((Int32)value);
}
else if (fieldType == typeof(Int64))
{
buffer = BitConverter.GetBytes((Int64)value);
}
else if (fieldType == typeof(Single))
{
buffer = BitConverter.GetBytes((Single)value);
}
else if (fieldType == typeof(Double))
{
buffer = BitConverter.GetBytes((Double)value);
}
else if (fieldType == typeof(Guid))
{
buffer = ((Guid)value).ToByteArray();
}

BinaryContainer tempC = null;
if (buffer != null && buffer.Length > 0)
{
tempC = new BinaryField();
tempC.Body = buffer;
}
else
throw new Exception("Ошибка в сериализациия поля типа " + fieldType.Name);

return tempC;
}
#endregion

/// <summary>
/// Десерилазиация
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="byteObj">Массив байт</param>
/// <returns>Объект заданного типа</returns>
public TType Deserialize<TType>(byte[] byteObj)
{
int offset = 0;
TType inst =
(TType)this.ClassDesirealize(
typeof(TType), byteObj, ref offset);

return inst;
}

#region Хелперы для десериализации

/// <summary>
/// Десериализация класса
/// </summary>
/// <param name="type">Тип класса</param>
/// <param name="data">Бинарные данные</param>
/// <returns>Экземпляр объекта</returns>
private object ClassDesirealize(
Type type, byte[] data, ref int offset)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

//создание объекта типа type
object obj = this.CreateInstance(type);

offset += 4; // первые 4 байта класса - количество полей

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
typeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

object fieldValue = null;

if (customTypeAttributes.Length == 1) // поле сложного типа
{
fieldValue =
this.ClassDesirealize(
fInfo.FieldType, data, ref offset);
}
else
{
if (fInfo.FieldType.IsArray) // поле массив
{
fieldValue =
this.ArrayDeserializer(fInfo, data, ref offset);
}
else // поле простого типа
{
// массив байт для хранения размера
byte[] sizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, sizeByte, 0, sizeByte.Length);
// получение размера
int size = BitConverter.ToInt32(sizeByte, 0);

// получение данных
byte[] dataBuffer = new byte[size];
Buffer.BlockCopy(
data, offset + sizeByte.Length,
dataBuffer, 0, dataBuffer.Length);

// десериализация поля
fieldValue =
this.SimpleTypeDeserializer(
fInfo.FieldType, dataBuffer);

// увелечени сдвига по буферу
offset += sizeByte.Length + size;
}
}

// помещение значение в объект
fInfo.SetValue(obj, fieldValue);
}
}

return obj;
}

/// <summary>
/// Десериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="data">Массив байт</param>
/// <param name="offset">Смещение в массиве байт</param>
/// <returns>Массив</returns>
private object ArrayDeserializer(
FieldInfo fInfo, byte[] data, ref int offset)
{
// получение количества элементов в массиве
byte[] elCountByte = new byte[4];
Buffer.BlockCopy(data, offset, elCountByte, 0, elCountByte.Length);
int elementsCount = BitConverter.ToInt32(elCountByte, 0);

// увелечение счетчика движения по буферу
offset += elCountByte.Length;

bool isClass = false;
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1)
isClass = true;

// создание экземпляра массива
Array array = Array.CreateInstance(elementType, elementsCount);

//получение элементов массива
for (int i = 0; i < elementsCount; i++)
{
object value = null;

if (isClass)
{
value =
this.ClassDesirealize(
elementType, data, ref offset);
}
else
{
if (elementType.IsArray)
{
throw new NotImplementedException(
"Ошибка при десериализации в массива ");
}
else
{
// получение размера элемента массива
byte[] elSizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, elSizeByte, 0, elSizeByte.Length);
int elementSize = BitConverter.ToInt32(elSizeByte, 0);

offset += elSizeByte.Length;

//получение значения элемента массива
byte[] valueByte = new byte[elementSize];
Buffer.BlockCopy(
data, offset, valueByte, 0, valueByte.Length);
value = this.SimpleTypeDeserializer(elementType, valueByte);

offset += valueByte.Length;
}
}

// заполнение массива
array.SetValue(value, i);
}

return array;
}

/// <summary>
/// Создание экземпляра класса по его типу
/// </summary>
/// <param name="type">Тип</param>
/// <returns>Объект</returns>
private object CreateInstance(Type type)
{
ConstructorInfo constructor =
type.GetConstructor(new Type[] { });

object inst = null;

if (constructor != null)
{
inst = constructor.Invoke(new object[] { });
}
else
throw new Exception(
"Не возможно создать объект типа " +
type.Name +
". Объект должен иметь пустой конструктор");

return inst;
}

/// <summary>
/// Десериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType"></param>
/// <param name="data"></param>
/// <returns></returns>
public object SimpleTypeDeserializer(Type fieldType, byte[] data)
{
object value = null;

if (fieldType == typeof(Boolean))
{
value = BitConverter.ToBoolean(data, 0);
}
else if (fieldType == typeof(Int16))
{
value = BitConverter.ToInt16(data, 0);
}
else if (fieldType == typeof(Int32))
{
value = BitConverter.ToInt32(data, 0);
}
else if (fieldType == typeof(Int64))
{
value = BitConverter.ToInt64(data, 0);
}
else if (fieldType == typeof(Single))
{
value = BitConverter.ToSingle(data, 0);
}
else if (fieldType == typeof(Double))
{
value = BitConverter.ToDouble(data, 0);
}
else if (fieldType == typeof(Guid))
{
value = new Guid(data);
}

if (value == null)
throw new Exception(
"Ошибка в десериализациия поля типа " +
fieldType.Name);

return value;
}

#endregion

}

30.03.2009

Каждому экшену - свое место

Итак, определившись с типом экшена, который мы хотим включить в инсталляцию, необходимо определиться с тем местом инсталляции, в котором его необходимо выполнить. В msi существует две таблицы в которые можно размещать экшены - InstallUISequence и InstallExecuteSequence. Действия, определенные в таблице InstallUISequence выполняются в самом начале установки и как правило именно в этой таблице описывается последовательность пользовательских диалогов, которая будет выводиться для сбора сведений об инсталляции от пользователя. При размещении своих экшенов в этой таблице надо помнить об одной важной вещи - существует "тихий" режим инсталляции, в котором таблица InstallUISequence не обрабатывается. Поэтому если экшен должен выполниться в любом случае, то логично разместить его в таблице InstallExecuteSequence. Действия в таблице InstallExecuteSequence по сути отвечают за сам процесс установки, именно они копируют файлы, ведут записи в реестре и т.д. И как правило именно при их выполнении пользователь видит перед собой диалоговое окно с прогресс-баром. Но при размещении в этой таблице своих экшенов надо помнить об опциях выполнения, которые в WIX определяются атрибутом "Execute" тега "CustomAction". Важнейшими из них являются deferred и immediate. Дело в том, что установка продукта не происходит "в один проход". На определенном этапе, а если точнее, то между действиями InstallInitialize и InstallFinalize происходит запись скрипта инсталляции и те экшены, которые помечены как deferred не выполняются сразу, а записываются в скрипт и исполняются после InstallFinalize, в то время как immediate-экшены выполняются всегда в тот момент, когда приходит их очередь по таблице. Отсюда следуют несколько интересных выводов: во-первых, deferred-экшен не может быть поставлен никуда кроме как между InstallInitialize и InstallFinalize. Во-вторых, если поставить immediate-экшен и deferred-экшен в одно место после копирования файлов, то на момент выполнения immediate-действия файлы еще скопированы не будут, потому как копируются во время исполнения скрипта, а не во время его записи, соттветственно deferred-экшен сможет использовать эти файлы. То же самое относится и к другим изменениям, выполняющимся в deferred-фазу.


Также важным моментом является использование rollback-экшенов. Эти экшены создаются с использованием значения "rollback" для атрибута "Execute". Такие экшены выполняются в том случае, если во время инсталляции произошла ошибка и производится откат. Использовать их следует в паре с написанными кастом-экшенами для отката изменений, ибо сам инсталлер не откатывает то, что делают кастом-экшены.
Порядок выполнения экшенов в таблицах задается целыми числами. В WIX эти числа можно определять как напрямую через атрибут "Sequence" тега "Custom", либо через атрибуты "Before" и "After", задавая перед или после какого экшена выполняться текущему действию.

23.03.2009

Custom Actions, какие они?

Экшены - это действия, котоорые выполняются в процессе инсталляции. По своей сути они представляют собой функции, которые и руководят процессом установки от копирования файлов и записей в реестре до сложных пользовательских функций. Существуют как встроенные экшены инсталлятора, например LunchCondition, который осуществляет проверку условий установки, так и CustomActions - пользовательские функции, расширяющие возможности инсталлятора. Существует несколько типов custom-экшенов, подходящих для разных нужд разработчика. Ниже я постарался перечислить все типы экшенов, присутствующих в Wix 3. Данная информация может быть использована как памятка при выборе типа экшена для определенной задачи.

Type 1 – вызов функции из dll, dll находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id библиотеки в таблице Binary
DllEntry="" – имя вызываемой функции
/>

Type 2 – запуск исполняемого exe-файла, exe находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла в таблице Binary
ExeCommand="" – передаваемые параметры командной строки
/>

Type 5 – запуск скрипта Java, файл JavaScript находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла скрипта в таблице Binary
JScriptCall="" – имя вызываемой функции
/>

Type 6 – запуск скрипта VisualBasic, файл VBScript находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла скрипта в таблице Binary
VBScriptCall="" – имя вызываемой функции
/>

Type 17 – вызов функции из dll, dll является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id библиотеки в таблице файлов
DllEntry="" – имя вызываемой функции
/>

Type 18 – запуск исполняемого exe-файла, exe является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла в таблице файлов
ExeCommand="" – передаваемые параметры командной строки
/>

Type 19 – отображение сообщения об ошибке, завершение инсталляции.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Error="" – текст сообщения об ошибке
/>

Type 21 – запуск скрипта Java, файл JavaScript является устанавливаемым файлом
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла скрипта в таблице файлов
JScriptCall="" – имя вызываемой функции
/>

Type 22 – запуск скрипта VisualBasic, файл VBScript является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла скрипта в таблице Binary
VBScriptCall="" – имя вызываемой функции
/>

Type 34 – запуск исполняемого exe-файла, определяемого директорией, в которой расположен файл и командной строкой для запуска.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Directory="" – id директории в таблице Directory, в которой располагается exe
ExeCommand="" – командная строка для запуска приложения
/>

Type 35 – установка физического пути для элемента Directory инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Directory="" - id директории в таблице Directory
Value="" – новое значение пути (форматируемая строка)
/>

Type 37 – запуск скрипта Java, скрипт написан непосредственно в CustomAction
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Script="jscript">
< ![CDATA[< !—здесь находится текст скрипта -- >\]]>
< /customaction>

Type 38 – запуск скрипта Visual Basic, скрипт написан непосредственно в CustomAction
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Script=" vbscript">
< ![CDATA[< !—здесь находится текст скрипта -- >\]]>
< /customaction>

Type 50 – запуск исполняемого exe-файла, exe уже существует на целевой системе. Exe задается через Property.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Property="" – Property, определяющее имя exe-файла
ExeCommand="" – параметры командной строки, передаваемые приложению
/>

Type 51 – установка значения Property.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Property="" – id изменяемого Property
Value="" – новое значение (не должно содержать других Property)
/>

Type 53 – запуск скрипта Java, скрипт хранится в Property
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
JScriptCall="" – имя вызываемой функции
Property="" – id Property, хранящей текст скрипта
/>

Type 54 – запуск скрипта Visual Basic, скрипт хранится в Property
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
VBScriptCall="" – имя вызываемой функции
Property="" – id Property, хранящей текст скрипта
/>

С внедрением в Wix инструмента DTF стал доступен запуск экшенов из .NET сборки. Как создать соответствующую сборку можно поглядеть в примерах в интернете, а объявление такого экшена в Wix имеет вид аналогичный экшенам для dll-библиотек (например type 1).

P.S. пробелов после открывающихся скобок быть не должно - поставлены, чтобы не воспринимались как html-разметка.

05.03.2009

Динамический ServiceKnownType

При использовании DataContractSerializer иногда возникает необходимость задавать динамически ServiceKnownTypes. Сделать это можно с использование класса-хелпера.



public static class ServiceHelper
{
private static List<Type> _knownTypes = new List<Type>();
public static List<Type> KnownTypes
{
get { return ServiceHelper._knownTypes; }
}

public static IEnumerable<Type> GetServiceKnownTypes(
ICustomAttributeProvider provider)
{
return _knownTypes;
}
}

[ServiceKnownType("GetServiceKnownTypes", typeof(ServiceHelper))]
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
public interface IServiceContract
{
// методы сервиса
}


Добавление типов в ServiceHelper на клиенте можно делать до создания объекта реализующего контракт, а в сервисе в global.asax в методе Application_Start.

Создание экземпляра объекта по его типу в C#

Используя рефлексию (reflection) можно создать следующий метод:


private static List<TEntityType> CreateObjects<TEntityType>(
Type realType)
{
ConstructorInfo constructor;
TEntityType entity;

constructor = realType.GetConstructor(Type.EmptyTypes);
if (constructor != null)
{
entity = (TEntityType)constructor.Invoke(new object[0]);
}
else // если конструктор содержит параметры
{
constructor = realType.GetConstructor(
new Type[] { <тип>, <тип>, ... });
entity = (TEntityType)constructor.Invoke(
new object[] { <аргумент>, <аргумент>, ... });
}
return entity;
}

Получение массива байтов из Int32 в С#

Для того чтобы получить массив байтов из числа типа Int32 можно воспользоваться следующим способом:



[StructLayout(LayoutKind.Explicit)]
public struct IntToByte
{
[FieldOffset(0)]
public Int32 _intValue;

[FieldOffset(0)]
public byte _b1;

[FieldOffset(1)]
public byte _b2;

[FieldOffset(2)]
public byte _b3;

[FieldOffset(3)]
public byte _b4;

}


Пример использования выше указанной структуры в создании методов по преобразованию:


...
/// Преобразование массива байт в Int32
public Int32 ByteArrayToInt(byte[] byteInt)
{
if (byteInt.Length != 4)
throw new Exception("Массив байт содержит не 4 байта.");

IntToByte intToByte;
intToByte._intValue = 0;
intToByte._b1 = byteInt[0];
intToByte._b2 = byteInt[1];
intToByte._b3 = byteInt[2];
intToByte._b4 = byteInt[3];

return intToByte._intValue;
}

/// Преобразование числа типа Int32 в массив байт
private byte[] IntToByteArray(Int32 intR)
{
IntToByte intToByte;
intToByte._b1 = 0;
intToByte._b2 = 0;
intToByte._b3 = 0;
intToByte._b4 = 0;

intToByte._intValue = intR;

return new byte[] {
intToByte._b1, intToByte._b2, intToByte._b3, intToByte._b4 };
}
...



Аналогично можно поступить и для других типов (float, double и т.д.)

04.03.2009

About Wix

Wix(Windows Installer XML) - набо инструментов для создания инсталляционных пакетов Windows. Изначально в состав Wix входили несколько консольных утилит:

  • candle - препроцессор/компилятор
  • light - компоновщик
  • lit - управление библиотеками
  • dark - декомпилятор
  • tallow - утилита для автогенерации кода - с 3-й версии заменена на heat.
На данный момент утилит стало больше, но эти являются основными. Исходным файлом для wix является xml-файл, написанный в соответствии с определенными правилами. Wix является разработкой Microsoft, и, по некоторым слухам, является инструментом их внутреннего использования. но пока это вызывает сомнения. Тем не менее, при установке Wix успешно интегрируется в Visual Studio, получает свою порцию типов проектов и другие студийные плюшки, включая Intellisense.Чего там катастрофически не хватает, так это редактора диалоговых форм. Но Microsoft уже обещали интеграцию Wix в студию в одном из следующих релизов, так что есть надежда, что и дизайнер форм тоже рано или поздно появится.
Так как wix предназначен для создания инсталляционных msi-пакетов, то и структура xml-файла для wix во многом определена этим. В инсталляционном пакете, и в wix соответственно, устанавливаемый продукт представляет собой иерархическую структуру.
На самом верхнем уровне располагается непосредственно сам продукт(Product), его теги обрамляют практически весь файл кода и в них можно указать основные сведения о нем - наименование, выпускающую фирму, версию, язык.
На более низком уровне детализации лежат "фичи"(Features). Features представляют собой составляющие части устанавливаемого продукта с точки зрения пользователя. Например, при установке Visual Studio можно выбирать различные компоненты для установки - инструменты разработчика, тестировщика, SQL-сервер и многое другое. Вот это и есть features. В диалоговом окне они представляются в виде дерева, соответствено сами могут организовываться в иерархические структуры.
На следующем уровне детализации находятся компоненты(Components). Компоненты невидимы для пользователя и представляют из себя атомарную единицу инсталляции. С точки зрения инсталляции компонент неделим. То есть, он конечно состоит из различных элементов, как то: файлы, ярлыки, записи реестра, SQL-запросы и др., но компонент нельзя установить частично, он либо устанавливается весь, либо не устанавливается вовсе. Если появляется необходимость устанавливать какие либо файлы в зависимости от некоторых условий, то такие файлы необходимо вынести в отдельный компонент. Также компонент должен иметь обязательно родительскую feature, ибо только features определяют какие составляющие продукта пользователь будет устанавливать, а какие нет. В свою очередь компонент содержит более мелкие элементы, описывать которые все нет смысла, основные упомянуты выше. Также обязательным элементов компонента является наличие у него родительской директории, в которую соответственно и будут установлены файлы компонента. Компонент не может быть разбит на части между несколькими директориями, он всегда ставится целиком в одну директорию.
Идентификатор продукта в масштабах Windows-вселенной обязан быть уникальным, имеет общепринятый вид GUID и может быть сгенерирован студией. Также обстоит дело и с компонентами - каждый компонент должен быть уникален. Features же GUID'a не имеют.
Такова примерная структура устанавливаемого продукта в msi-пакетах.

C# EditableObject: Commit and Rollback для объекта.

Иногда возникает необходимость такой работы над объектом, когда нужно иметь возможность отменить все изменения в нем (rollback). Например, пользователь редактирует свойства объекта, а потом отказывается сохранять изменения. Можно загрузить объект заново с сервера, но проще отменить изменения на стороне клиента. Для этого можно использовать шаблонный класс EditableObject<>. Он создает для объекта второй экземпляр, с которым и производятся все действия. В случае отмены действий экземпляр просто пересоздается заново копированием оригинала. А в случае применения действий данные из копии переносятся в оригинал, используя рефлексию (reflection).


public class EditableObject<TObjectType>
where TObjectType : ICloneable, new()
{
/// Архивная версия объекта</summary>
private TObjectType _original;

/// Текущий объект</summary>
private TObjectType _editable;

/// Текущий объект</summary>
public TObjectType Current
{
get { return _editable; }
}

public EditableObject(TObjectType objectInst)
{
if (objectInst != null)
{
_original = objectInst;

BeginEdit();
}
else
{
throw new ArgumentNullException(
"Объект objectInst должен быть не null", (Exception)null);
}
}

/// Начать редактировать объект
private void BeginEdit()
{
_editable = (TObjectType)_original.Clone();
}

/// Отменить все сделанные изменения
public void CancelEdit()
{
BeginEdit();
}

/// Завершить редактирование объекта
public void EndEdit()
{
Copy(_editable, _original);
}

/// Копирование объектов
private static void Copy(
TObjectType fromObject, TObjectType toObject)
{
foreach (FieldInfo fInfo in typeof(TObjectType).GetFields(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance))
{
fInfo.SetValue(toObject, fInfo.GetValue(fromObject));
}
}
}


Пример использования:


...
EditableObject<TestClass> editableTestInst =
new EditableObject<TestClass>(testInst);
...
try
{
...
// применить изменения
editableTestInst.EndEdit();
}
catch(Exception)
{
// отменить все изменения
editableTestInst.CancelEdit();
}


Свойство Current возвращает объект который можно редактировать.

Многопоточный сервер на C#

Сервер может быть организован по следующему принципу: отдельный поток на прослушивание порта и каждый поток на клиентское подключение.



/// Ассинхронный сервер
public class AsyncServer
{
/// Сокет сервера
private Socket _serverSocket;

/// Элемент для синхронизации ожидания подключений
private static ManualResetEvent _connectionMutex =
new ManualResetEvent(false);

/// Менеджер для обработки клиентов
private ClientManager _clientManager;

public AsyncServer(string ipAddrees, int port)
{
try
{
// создание сокета для сервера
this._serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

// прикрепление сервера к ip адресу и порту
this._serverSocket.Bind(
new IPEndPoint(IPAddress.Parse(ipAddrees), port));

}
catch (Exception ex)
{
throw new Exception("Ошибка инициализации сервера.", ex);
}
}

private BackgroundWorker _listenThread = new BackgroundWorker();

/// Начать работу сервера
public void Start()
{
this._clientManager = new ClientManager(this._clientConnections);

this._listenThread.WorkerReportsProgress = true;
this._listenThread.WorkerSupportsCancellation = true;
this._listenThread.DoWork +=
new DoWorkEventHandler(ListenThread_DoWork);

this._listenThread.RunWorkerAsync(this._serverSocket);
}

/// Поток для прослушки порта
private void ListenThread_DoWork(object sender, DoWorkEventArgs e)
{
Socket serverSocket = (Socket)e.Argument;

// прослушивание сокета
serverSocket.Listen(100);

while (true)
{
// сбросить мьютекс
_connectionMutex.Reset();

serverSocket.BeginAccept(
new AsyncCallback(this.AcceptCallback), this._serverSocket);

// ожидание следующего подключения
_connectionMutex.WaitOne();
}
}

/// Клиентские подключения
private List _clientConnections = new List();

/// Количество клиентских подключений
public int ConnectionsCount
{
get { return this._clientConnections.Count; }
}

/// Callback метод для обработки входящих соединений
private void AcceptCallback(IAsyncResult asyncResult)
{
// уведомить о том, что подключение произошло
_connectionMutex.Set();

Socket serverSocket = (Socket)asyncResult.AsyncState;
Socket clientSocket = (Socket)serverSocket.EndAccept(asyncResult);
this._clientConnections.Add(clientSocket);

// передача управления клиентом менеджеру клиентов
this._clientManager.HandleClient(clientSocket);
}

}


Метод AcceptCallback(IAsyncResult asyncResult) срабатывает при новом входящем соединение и передает сокет клиента специальному менеджеру (ClientManager), который создаст для него отдельный поток на обслуживание. Ниже приведен код менеджера клиентов.



/// Менеджер для работы с клиентским подключениями
public class ClientManager
{
/// Коллекция процессоров клиентов
private List<BackgroundWorker> _clientProcessors = new List<BackgroundWorker>();

/// Коллекция соединений
private List<Socket> _connections;

/// <param name="connections">Список соединений</param>
public ClientManager(List<Socket> connections)
{
this._connections = connections;
}

/// Обработка клиентского подключения
public void HandleClient(Socket clientSocket)
{
BackgroundWorker clientProcessor = new BackgroundWorker();
clientProcessor.DoWork += new DoWorkEventHandler(ClientProcessing);

// добавление процессора в коллекцию процессоров
this._clientProcessors.Add(clientProcessor);

List<object> args = new List<object>();
// добавление аргументов для потока
// args.Add(...);

// запустить процессор для обработки клиента
clientProcessor.RunWorkerAsync(args);
}

/// Метод для работы с клиентом в другом потоке
private void ClientProcessing(object sender, DoWorkEventArgs e)
{
// чтение аргументов
List<object> args = (List<object>)e.Argument;

ProtocolSerializer serializer = new ProtocolSerializer();

try
{
while (socket.Connected)
{
// получение или отправка данных

}
}
catch (SocketException)
{
// обработка исключений
}
catch (Exception)
{
// обработка исключений
}
}
}

Быстрый поиск в C# сложных объектов

Поиск по списку, каждый элемент которого сложный объект можно организовать следующим образом:


/// Управление буфером контейнеров пользователя
public class SmartBuffer
{
/// Буфер для хранения контейнеров для пользователя
private List _cargoList = new List();

/// Получить контейнер для записи
public Cargo GetWritebleBuffer()
{
Cargo cargo = new Cargo();
this._cargoList.Add(cargo);

return cargo;
}

/// Удалить контейнер
public void RemoveCargo(Cargo cargo)
{
if (this._cargoList.Contains(cargo))
this._cargoList.Remove(cargo);
}

/// Выбрать контейнеры, которые находятся в пределах окна
public List Select(Cargo myCargo, int xDelta, int yDelta)
{
// сортировать буфер
this.SortBuffer();

// выполнить поиск по заданному критерию
List cargos = this._cargoList.FindAll(
delegate(Cargo c)
{
if (c != null && c.ClientAsk != null &&
c.ClientAsk.X >= myCargo.ClientAsk.X - xDelta &&
c.ClientAsk.X <= myCargo.ClientAsk.X + xDelta &&
c.ClientAsk.Y >= myCargo.ClientAsk.Y - yDelta &&
c.ClientAsk.Y <= myCargo.ClientAsk.Y + yDelta &&
myCargo.ClientAsk.UserID != c.ClientAsk.UserID)
return true;

return false;
});

return cargos;
}

/// Сортировка буфера
public void SortBuffer()
{
this._cargoList.Sort(new SortCargoByXY());
}
}

/// Сортировщик для быстрой сортировки.
/// Построение двойного индекса сначало по X потом Y.
class SortCargoByXY : IComparer
{
public int Compare(Cargo x, Cargo y)
{
if (x == y) return 0;
if (x == null || x.ClientAsk == null ||
y == null || y.ClientAsk == null)
return -1;
else if (x.ClientAsk.X == y.ClientAsk.X)
{
if (x.ClientAsk.Y > y.ClientAsk.Y) return 1;
else return -1;
}
else if (x.ClientAsk.X > y.ClientAsk.X) return 1;
else return -1;
}
}



Результаты тестирования:
100 000 элементов списка
значения X и Y [0; 100 000]
Время сортировки 39мс
Время поиска 15мс
Машина Core 2 Duo E8200 2666HGz 4Гб ОЗУ

03.03.2009

Нечеткое сравнение

Встала задача для сопоставления предложений. Решалась следующим образом: использовался алгоритм для определения дистанции редактирования, разработанный Владимиром Левенштейном.
Реализация данного алгоритма на C#:



public class StringDistance
{
private int _absoluteMeasure;
/// Абсолютное значение функции похожести
public int AbsoluteMeasure
{
get { return _absoluteMeasure; }
}

private int _procentMeasure;
/// Относительное значение функции похожести
public int ProcentMeasure
{
get { return _procentMeasure; }
}

private string _sentenceOne;
private string _sentenceTwo;

public StringDistance(string sentenceOne, string sentenceTwo)
{
this._sentenceOne = sentenceOne;
this._sentenceTwo = sentenceTwo;

this.SentenceSeemsMeasure();
}

/// Вычисление меры схожести двух предложений
private void SentenceSeemsMeasure()
{
/// получение слов в предложениях
string[] sOneWords = _sentenceOne.Split(
new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
string[] sTwoWords = _sentenceTwo.Split(
new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

/// заполнение матрицы отношений между словами предложения
int[,] wordRelations = new int[sOneWords.Length, sTwoWords.Length];
// значения максимальных значений в строках матрицы
int[] maxRowElements = new int[sOneWords.Length];

try
{

for (int i = 0; i < sOneWords.Length; i++)
{
int max = 0;
for (int j = 0; j < sTwoWords.Length; j++)
{
// получение дистанции для пары слов в предлажении
wordRelations[i, j] =
StringDistance.GetWordDistance(
sOneWords[i], sTwoWords[j]);
// сравение с максимальным значением
if (max < wordRelations[i, j])
max = wordRelations[i, j];
}
maxRowElements[i] = max;
}

/// получения меры схожести между предложениями

int measure = 0;
for (int i = 0; i < maxRowElements.Length; i++)
{
int max = maxRowElements[i];
for (int j = 0; j < sTwoWords.Length; j++)
if (max < wordRelations[i, j])
max = wordRelations[i, j];

measure += max;
}

this._absoluteMeasure = measure;

// получения относительного значения схожести двух предложений
this._procentMeasure =
(int)Math.Round(
measure / (double)Math.Max(sOneWords.Length, sTwoWords.Length), 0);
}
catch (Exception ex)
{
throw new Exception("Ошибка при сравнении предложений", ex);
}
}


/// Получение дистанции для двух слов
private static int GetWordDistance(String s, String t)
{
try
{
/// шаг 1

if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(t))
throw new Exception("Одно из слов не задано.");

s = s.ToLower();
t = t.ToLower();

int n = s.Length; // длина строки s
int m = t.Length; // длина строки t

int[,] d =
new int[n + 1, m + 1]; // матрица для хранения вычислений

/// шаг 2

for (int i = 0; i <= n; i++)
d[i, 0] = i;

for (int j = 0; j <= m; j++)
d[0, j] = j;

/// шаг 3

for (int i = 1; i <= n; i++)
{
int s_i = s[i - 1];

for (int j = 1; j <= m; j++)
{
int t_j = t[j - 1];
int cost = 0;

// определение оценки
if (s_i != t_j)
cost = 1;

// получить минимум для ячейки матрцы
d[i, j] = Minimum(
d[i - 1, j] + 1,
d[i, j - 1] + 1,
d[i - 1, j - 1] + cost);
}
}

int dist = d[n, m]; // дистанция редактирования

/// получение относительного числа похожести
int procent = (int)Math.Round(
(1 - dist / (double)System.Math.Max(s.Length, t.Length)) * 100, 0);

return procent;
}
catch (Exception ex)
{
throw new Exception("Ошибка при сравнении слов", ex);
}
}


/// Минимум из трех значений
private static int Minimum(int a, int b, int c)
{
int min = a;

if (b < min)
min = b;

if (c < min)
min = c;

return min;
}

}