Thursday, November 5, 2009

StringStream

Понадобилось парсить строки. И вспомнил я про класс strstream еще из универа, который был в библиотеке strstrea.h (Да, это был C++), поискав в недрах .net что-то подобное и нашел только StringReader. Мягко сказано, его функционал меня не устроил. Особенно не хватало управления символами разделителями хоть на каком-то уровне.

Хочешь что-то сделать - сделай это сам. Сказано - сделано. В итоге появился класс StringStream, который стал "в пригодi" уже не один раз. Класс полностью продокументирован комментариями и думаю вопросов возникнуть не должно. Ведь все гениальное просто!

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

/**
*StringStream - утилитный класс для
работы со строками,
*как с потоком данных.
*Является усовершенствованным
вариантом StringReader.
*
*Автор: Шихов А.А. aka S.a.T.
*
*Класс является частью проекта
Extension-Project
* http://extension-project.blogspot.com
*но может использоваться и как
самостоятельная единица.
*
*Разрешается использование в
любых проектах,
*в том числе и коммерческих.
*При использовании ссылка на
сайт первоисточник и указание
автора обязательно.
*
*Класс поставляется "КАК ЕСТЬ".
Автор не несет никакой
ответственности
*за возможный ущерб, который
может быть нанесен при
использовании данного класса.
*
*Версия 1.0.0
*Дата: 05.10.2009
*
*/
using System;
using System.Text;
using System.Collections.Generic;

namespace Extension.Utils
{
/// <summary>
/// Класс для работы со строкой
как с потоком данных.
/// Является альтернативой
StringReader с рядом полезных
методов.
/// </summary>
class IStringStream
{
/// <summary>
/// Получение исходной строки
/// </summary>
public string Source
{
get
{
return source;
}
}

/// <summary>
/// Получение или установка
текущей позиции символа.
/// Неправильные значения
игноррируются.
/// </summary>
public int Position
{
get;
set;
}

/// <summary>
/// Установка разделитей по
умолчанию:
/// пробел, знак таубляции,
знак перевода каретки
/// </summary>
public void SetSeparators();

/// <summary>
/// Установка
пользовательских
разделитей.
/// Текущие разделители будут
удалены
/// </summary>
/// <param name="newSeparators"></param>
public void SetSeparators(string newSeparators);

/// <summary>
/// Добавление разделителей к
текущим.
/// </summary>
/// <param name="separators"></param>
public void AddSeparators(string newSeparators);

/// <summary>
/// Получение разделитей в
формате строка.
/// </summary>
/// <returns></returns>
public string GetSeparators();

/// <summary>
/// Флаг, показывающий
прочитана ли вся строка
/// Для сброса воспользуйтесь
свойством Position,
/// установив его в 0
/// </summary>
public bool EndOfStream
{
get;
}

/// <summary>
/// Единственный конструктор.
Параметр со строкой
источником обязятелен.
/// </summary>
/// <param name="text"> не нулевая и не
пустая строка</param>
public StringStream(string text);

/// <summary>
/// Получение следующего
символа,
/// без сдвига указателя
текущего символа
/// </summary>
/// <returns>следующий символ, или
Char.MaxValue если EndOfStream = true</returns>
public char Peek();

/// <summary>
/// Перемещение указателя
текущего символа на первый
символ не-разделитель
/// Если разделитель не
находится, то текущая
позиция устанавливается за
последний символ
/// </summary>
public void SkipSeparators();

/// <summary>
/// Проверка на наличие
символа в списке
разделителей
/// </summary>
/// <param name="ch"></param>
/// <returns></returns>
private bool IsCharInSeparators(char ch);

/// <summary>
/// Чтение до конца строки,
исключая все разделители
/// </summary>
/// <returns></returns>
public string ReadAllExceptSeparators();

/// <summary>
/// Получение следующего
символа не-разделителя
/// </summary>
/// <param name="ch">следующий символ
не-разделитель, или
Char.MaxValue если EndOfStream<
/param>
/// <returns>true - если удалось
прочитать, false если
достигнут конец строки</returns>
public bool ReadChar(out char ch);

/// <summary>
/// Получение следующего
символа.
/// </summary>
/// <param name="skipSeparators">флаг,
который при
значении true
отменяет пропуск
символов
разделителей</param>
/// <returns>следующий символ или
Char.MaxValue если достигнут конец
строки</returns>
public char ReadChar(bool skipSeparators);

/// <summary>
/// Получение следующего
символа с пропуском
разделителей
/// </summary>
/// <returns></returns>
public char ReadChar();

/// <summary>
/// Чтение до указанного
символа или конца строки
игноррируя заданные
разделители
/// </summary>
/// <param name="ch">символ, до
которого читать из
строки</param>
/// <param name="str">строка
результат, не
включающая символ,
переданный первым
параметром метода<
/param>
/// <returns>прочитанную строку
или null, если EndOfStream = true</returns>
public bool ReadToChar(char ch, out string str);

/// <summary>
/// Читать с первого символа
не-разделителя до
следующего разделителя, не
включая его
/// </summary>
/// <returns>строка до следующего
разделителя, или null если
EndOfStream = true</returns>
public string Read();
}
}


Исходник можно скачать здесь: StringStream.cs

Monday, October 12, 2009

Хитрый ListView.

Выбрал для отображения структуры каталогов - компонент ListView, который в ближайшем будущем должен удовлетворять всем требованиям для отображения. Привлек компонент именно тем, что предоставляет возможность отображать содержимое в 4х режимах, из который наиболее важны View.LargeIcons и View.Details.

В общем 3 часа на выходных убил на то, чтобы найти баг, из-за которого в режиме List все отображается нормально, а в режиме Details - только серая полоска и пустой компонент. 10 раз перепроверил создание колонок при помощи ColumnHeaders и добавление элементов в список. А все оказалось гениально просто - при обновлении содержимого я вызывал ListView.Clear() по аналогии с компонентом TextBox, хотя, как оказалось, нужно было вызывать ListView.Items.Clear(), так как ListView.Clear() вызывает ListView.Items.Clear() + совсем не очевидный ход -> ListView.Columns.Clear(). А в связи с тем, что я создавал набор колонок один раз, то они при обновлении просто стирались. А так как по логике ListView - если нет заданных колонок, то ничего рисовать не надо - и получал пустой компонент, но при переключении в вид View.List - я видел корректное отображение, что меня вводило в ступор.

Вывод: перед использованием каких-либо API, меньше опираться на логику, а больше на документацию, которая в данном случае(MSDN) доступна чуть более, чем полностью :)

Friday, October 9, 2009

Дружба народов или использование native code библиотек с c#

Так как изобретать велосипед не хотелось, решил организовать поддержку в моем проекте плагинов для Total Commander. Плагины представляют собой динамические библиотеки двоичного кода со списком поддерживаемых функций, определяющих интерфейс плагина. Насчет статического связывания native библиотек с .net кодом написано много (программирование с помощью аттрибутов DllImport), но вот вопрос как загрузить библиотеку во время выполнения и выполнить функцию, которая экспортируется этой библиотекой остается мало освещен. Исправлю этот пробел.

Итак, постановка задачи:
Есть бибилотека someDll.dll, которая является не .net сборкой, а так называемым двоичным кодом(или "родным" (native)). Обычно такие библиотеки написаны на С++ или Delphi. Эта библиотека экспортирует функцию

void libraryFunction(char* text);


Необходимо вызвать эту функцию в проекте .net на этапе выполнения. В данном примере - в проекте на языке C#

Порядок действий.

1. Необходимо описать список win32 api функций, необходимых для работы с библиотеками. Использование этих функций легко организовать при помощи статического связывания при помощи InteropServices(Не забывайте добавлять директиву "using System.Runtime.InteropServices;" вначале файла) и использования аттрибута DllImport. Рекоммендую ресурс PInvoke.net для поиска готовых описаний системных функций, чтобы не ошибится при использовании функций со сложными входными параметрами и возвращаемыми значениями. Если вы используете SharpDevelop, то в контекстном меню редактора есть пункт "Insert PInvoke Signature" и выбрав его и введя в строке поиска необходимую функцию, вы сразу можете вставить готовое описание.


2. Для загрузки библиотеки необходима функция LoadLibrary. Она возвращает дескриптор библиотеки, который необходим для использования ее функций. В качестве параметра используется char[] который вы можете получить, используя метод ToCharArray() у класса string.


3. Необходимо получить адрес функции, которую вы хотите использовать. Используем GetProcAddress, первым параметром указываем дескриптор загруженной библиотеки, который мы получили вызвав LoadLibrary, а вторым параметром указываем название функции, которую хотим вызвать.

Примечание: Название функции содержит только имя функции без скобок, параметров и возвращаемого значения. Иначе функция вернет вам адресс IntPtr.Zero

4. Так как .net считается безопасным кодом, то преобразовать адрес функции в функцию невозможно. Но среда предоставляет возможность безопасно преобразовать адрес в делегат. Нам необходима функция "void libraryFunction(char* text);". Для этого объявим делегат

internal delegate void functionDelegate(char[] text);


Преобразовываем адрес функции в делегат(аналог безопасного варианта указателя на функцию в C++ для среды .net) при помощи метода Marshal.GetDelegateForFunctionPointer

functionDelegate libFunction = (functionDelegate)Marshal.
GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));


5. И теперь мы свободно можем пользоваться нашей функцией при помощи делегата.



6. Не забваем выгрузить библиотеку при помощи FreeLibrary после того, как библиотека стала не нужной.

using System;
using System.Runtime.InteropServices;

class Imports
{
#region external imports

[DllImport("kernel32", SetLastError = true)]
public static extern IntPtr LoadLibrary(string
lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi,
ExactSpelling = true, SetLastError = true)]
public internal static extern IntPtr GetProcAddress(
IntPtr hModule, string procName);

[DllImport("kernel32.dll", SetLastError=true)]
static extern bool FreeLibrary(IntPtr hModule);

#endregion
}

class DLL_Loader
{
internal delegate void functionDelegate(char[] text);

public void useFunctionFromDLL()
{
IntPtr libID = Imports.LoadLibrary("someDll.dll");
IntPtr procID = Imports.GetProcAddress(libID,
"libraryFunction");

functionDelegate libFunction = (functionDelegate)
Marshal.GetDelegateForFunctionPointer(procID, typeof(
functionDelegate));

libFunction("my string".ToCharArray());

Imports.FreeLibrary(libID);
}
}

Tuesday, September 8, 2009

Исходники и блог.

С этого момента весь текст относящийся к программному коду будет выделен в отдельный блок и будет подсветка синтаксиса. Так как встроенной такой функции нет, то буду пользоваться возможностью редактирования html кода.

Немного поискав конвертор из rtf в html формат, в основном это платные утилиты (вот только не понимаю за что такие астрономические цены за такую простую задачу) нашел бесплатный аналог по имени Highlight - freeware open source project (можно найти по адресу http://andre-simon.de) и выставив опции на закладке Output specific: 1. Embed style 2. Inline CSS после выбора необходимого файла с кодом нажимаем clipboard получаем что-то типа

/*
* Created by SharpDevelop.
* User: Admin
* Date: 16.09.2008
* Time: 20:52
*
* To change this template use Tools | Options | Coding |
Edit Standard Headers.
*/
using System;
using System.IO;

namespace FSLib.Generic
{
public delegate void StorageContentChangeHandler();
public delegate void StorageCapacityChangeHandler();

public enum StorageAddOptions
{
Copy,
Move,
HardLink
}

/// <summary>
/// Description of IFSStorage.
/// </summary>
public interface IFSStorage
{
long Capacity{get;set;}

long Occupied{get;}

long Free{get;}

long AddFile(FileInfo file, StorageAddOptions options,
string relativePath);

long AddFolder(DirectoryInfo directory, StorageAddOptions
options, bool copySubfolders, string relativePath);

bool DeleteFile(long file_id);

bool DeleteFolder(string relativePath, bool delContent);

string Name{get;}

string RootPath{get;}

DirectoryInfo PrepareRelativePath(string relativePath);

event StorageContentChangeHandler ContentChanged;
event StorageCapacityChangeHandler CapacityChanged;
}
}


только необходимо удалить теги html и body, так как программа генерирует полностью html страницу, а не html участок. Но с этим можно ммриться, ведь программа бесплатна, и на этом спасибо, все остальное мы напильником отточим :)

P.S.
Замечено, что этот блог некорректно отрабатывает длинный текст, поэтому буду пользоваться фичей программы - line wrapping.

Monday, September 7, 2009

Ах эти логи!

log4net подключил без проблем, и даже написал свой Appender для вывода логов в текстовое поле, этакая симуляция консоли в GUI приложении :)

Хотя можно было сделать и такой трюк:

Объявить Windows API функции

[DllImport("kernel32")]
static extern bool AllocConsole();

[DllImport("kernel32.dll", SetLastError=true, ExactSpelling=true)]
static extern bool FreeConsole();

и спокойно использовать ConsoleAppender из стандартной поставки log4net.

Но первый способ элегантнее в том плане, что код остается "платформенно независимым" да и просто было интересно посмотреть исходники этой бибилиотеки и протестировать возможность создания своего формата данных и организовать вывод в свое "устройство".
Все гениальное действительно просто. Все исходники библиотеки просты и понятны. А это ведь искусство! :) чтобы писать код так, чтобы он был сразу понятен всем, кто его читает.

Wednesday, September 2, 2009

Core Redesign

В связи с тем, что проект направлен на довольно сильное расширение при помощи плагинов, (в планах поддержки - hdd логические диски, CD/DVD,USB Portable Devices, FTP, Видео база, Аудио база, База документов, База изображений) пришлось сделать интерфейсы хранилищ данных более общими, из-за чего существенная часть наработок нуждается в коррекции.

Теперь хранилище данных может не хранить полное дерево каталогов у себя внутри,
для перехода по папкам организовано скрытое взаимодействие папки с хранилищем, т.е. при вызове GetSubFolder(name) папка обращается к хранилищу за объектом FolderInfo, получает его и возвращает вызывающему объекту актуальный объект с данными о папке.

Что самое замечательное в полиморфизме в данном случае - все типы хранилищ могут визуализироваться при помощи одного Viewer практически без ущерба в информативности.

Thursday, August 27, 2009

I'll be back!

Съездил на недельку на море. "Отдохнул" телом и душой. На выходных обязательно продолжу писать проект. Пару идей уже есть. Осталось начать их реализовывать.