понедельник, 12 марта 2007 г.

Выходные с Nemerle

Наконец-то дошли руки до популярного ныне в своих кругах языка программирования Nemerle. За развитием этого языка я слежу давно, наблющаю за активным обсуждением этого языка на сайте RSDN. Пробовал писать на этом языке какие-то вещи, но дальше примеров дело не заходило. На сей раз к этому языку меня подтолкнуло любопытство, связанное с парадигомой функционального программирования. Как практику, мне необходимо было выбрать язык программирования для своих экспериментов, а Nemerle изначально был заявлен, как поддерживающий функциональное программирование, да и один из его предков язык ML функционален по определению. Ну и хотелось иметь язык, который нативно впишется в среду .NET. Ну и его "гибридность" теоретически может позволить "быстрый ввод" и легкое "переключение" между парадигмами в зависимости от решаемых задач. Кроме того, система метапрограммирования, реализованная в этом языке, делают его настоящим комбайном, выразительные способности которого ограничиваются исключительно фантазией пишущих на нем. К слову стоит отметить, что большая часть исходников Nemerle написана на самом Nemerle.

Итак, в качестве первого "функционального" примера я решил попробовать реализовать функцию для быстрой сортировки. Этот пример очень показателен для функционального программирования, так как очень здорово иллюстрирует идею о том, что функциональные программы близки внешне к описанию формулы в математической нотации, описывающей результат, чем к пошаговому описанию алгоритма, приводящего к заданному результату в случае императивного программирования. Чтобы проверить эту идею, в принципе, достаточно ознакомится с реализацией этого алгоритма на разных языках в этой статье. Сравните реализацию на С и на Haskell. Впечатляет?
Итак, в общем случае метод сортировки описывается следующей формулой:
   quickSort([]) = []
 qs

Нам ничего не остается, кроме как просто описать эту формулу в несколько иной нотации, а другими словами - реализовать на языке Nemerle:
def qs(l)
{
    match(l)
    {
        | [] => [];
        | x :: xs  => qs( $[ y | y in xs, y < x ] ) + [x] + qs( $[y | y in xs, y >= x] );
    }
}
Неправда ли, очень похоже на исходную формулу?
Ну и пример использования:
System.Console.WriteLine(qs([1, 9, 6, 96, 9, 6, 69, 698, 98, 69, 89, -228, -234, -27, -23, -23, 63407075, 003, 069, 036, 306, 3096]));
Результатом сортировки будет:
[-234, -228, -27, -23, -23, 1, 3, 6, 6, 9, 9, 36, 69, 69, 69, 89, 96, 98, 306, 698, 3096, 63407075]
Ура, первый эксперимент удался! Будем продолжать наши опыты и результат сразу же опубликовывать.
В качестве источника научной информации в данном эксперементе использовался цикл лекций "Основы функционального программирвоания".

Создание собственного дистрибутива Microsoft SQL Server Express

Все разработчики desktop-приложений, работающих с базами данных, всегда сталкивались с проблемами установки этой самой базы данных на машины пользователей. Сценариев, которые можно реализовать для решения данной задачи достаточно много:
  • Использование файловых баз данных, таких, как Microsoft Access, SQLite, Paradox - разворачивание базы данных представляет собой обычное копирование неких эталонных файлов в какое-нибудь общедоступное место;
  • Использование существующих серверов баз данных - в этом случае нужно либо в момент установки, либо в момент запуска приложения настроить соединение, сохранить параметры и создать структуру базы данных;
  • Установка собственных серверов баз данных вместе с приложением - этот вариант я и рассмотрю ниже.

В качестве сервера, который мы будем "встраивать" в наше приложение мы выберем Microsoft SQL Server Express. По функциональным возможностям эта редакция сервера аналогична SQL Server 2005 Standard Edition, с некоторыми ограничениями по размеру базы данных, количеству используемой памяти и процессоров. Но для небольших приложений всех этих возможностей будет более чем достаточно. Нужно отметить, что эта редакция намного удобнее в использовании предыдущей версии MSDE 2000.
Рассмотрим следующий сценарий развёртывания:
  1. Пользователь устанавливает приложение самым обычным образом (пусть даже просто вручную копирует бинарные файлы);
  2. При первом запуске приложение проверяет наличие сконфигурированной базы данных и, если таковая не обнаруживается, то пользователю предлагается установить и настроить сервер баз данных;
  3. Если пользователь соглашается, то запускается процесс установки сервера баз данных с заданными параметрами безопасности, названием экземпляра сервера и паролем для пользователя sa; при этом процесс установки должен быть максимально скрыт от пользователя для того, чтобы не нагружать его лишней информацией о приложении;
  4. На только что установленном сервере создаем базу данных, выполнив установочный DDL-скрипт.
Способы определения наличия уже установленной базы данных и последующее создание новой базы мы отложим и рассмотрим подробнее третий пункт нашего сценария.
Для осуществления нашего плана мы должны иметь в наличии некий исполняемый файл, запустив который из нашего приложения мы и получим по завершению установленный экземпляр SQL Server с заданными параметрами.
Итак, рассмотрим процесс по шагам.
  1. Скачиваем дистрибутив с сайта Microsoft SQL Server Express.
  2. Распаковываем установочные файлы.
    На этом сайте доступны версии, упакованные в некий файл вида SQLEXPR32_RUS.EXE, а нам нужно получить доступ непосредственно к файлу Setup.exe, который можно будет запустить с заданными параметрами. В этом месте мы должны выполнить фокус: дело в том, что перво-наперво скачанный инсталятор просто распаковывает содержимое инсталляционного пакета во временную директорию. Наша задача эти файлы оттуда перехватить. Задача это достаточно проста: после запуска инсталляции этот путь можно получить из такого окошка: 

    Распаковка файлов

    После того, как запустится непосредственно сам инсталлятор из архива, копируем содержимое этот папки, потому как после завершения работы инсталлятора - эта папка будет удалена.
  3. Конфигурация инсталлятора.
    В директории с инсталлятором вы найдете файл template.ini, в котором можно прописать все необходимые параметры установки по умолчанию. Дело в том, что правильно сделанный инсталятор должен уметь принимать все параметры установки из командной строки для того, чтобы всю эту установку автоматизировать. Помогает это в тех случаях, когда, например, от конечного пользователя нужно спрятать всю рутину, связанную с этим процессом, доверившись параметрам по умолчанию. Ведь даже опытные пользователи в 90% случаев установки в основном нажимают кнопку Next, не изменяя параметров по умолчанию.
    В этом файле можно указать все параметры, которые запрашиваются в диалогах. А для того, чтобы запустить установку с этими параметрами нужно указать инсталлятору, откуда брать эти параметры. Это делается с помощью такого запуска:

    setup.exe /qb /settings c:\template.ini

    • Ключ /qb указывает инсталлятору, что нужно принять все параметры и не дать пользователю их изменить через всякие диалоги. Таким образом пользователь будет отслеживать процесс установки, но не сможет ничего изменить. Альтернативный этому ключ /qn указывает инсталлятору, что процесс установки нужно проводить полностью в фоновом режиме.
    • Ключ /settings указывает, что параметры установки следует взять из указанного файла.
    Альтернативой этому способу является указание всех параметр прямо в командной строке:

    setup.exe /qn ADDLOCAL=SQL_Engine SAPWD=password SECURITYMODE=SQL

    Таким образом можно установить экземпляр сервера по умолчанию с заданным паролем для пользователя sa в совершенно невидимом режиме. Завершающем штрихом в этом пункте будет создание некого Install.bat со строкой запуска установки с заданными параметрами. Но мы на этом не остановимся.
  4. Выбор архиватора.
    Если вы помните, скачанный дистрибутив был представлен в виде исполняемого файла SQLEXPR32_RUS.EXE, размер которого был во много раз меньше развёрнутого инсталлятора. Наша задача - получить в результате нечто подобное, с той лишь разницей, что запуск нашего дистрибутива сразу начнёт установку с заданными параметрами. Опустив сравнительный анализ разных архиваторов, остановимся на замечательном архиваторе 7-zip. Для нашей задачи он показался мне наиболее подходящим.
    Следующим шагом будет создание архива максимальной степени сжатия примерно с такими параметрами: 

    Добавить в архив

    Размер архива получился около 36 мегабайт, что очень близко к оригинальному дистрибутиву.
  5. Создание SFX-архива.
    В предыдущем пункте мы сознательно не создали SFX-архив сразу, потому что нам нужна не просто возможность распаковать архив на произвольной машине, а распаковать в директорию по умолчанию и запустить после установки на выполнение установку с заданными параметрами. Все эти задачи делаются легко и просто с помощью существующих SFX-модулей. Детали того, как это делается с помощью стандартных средств 7-zip можно найти по этому адресу. Но мы воспользуемся альтернативным модулем, найти который можно по этому адресу. 
    Из скачанного пакета альтернативных модулей выбираем файл 7ZSD_LZMA.sfx и помещаем его рядом с архивом, который мы хотим "оживить". Если у вас есть желание, то можно с помощью утилиты ResourceHacker и этой заметки поменять иконку у SFX модуля на Иконка, предварительно извлечённую с помощью все того же ResourceHacker-а из инсталлятора Microsoft SQL Server Express. Затем размещаем в этой же директории текстовый файл Config.txt такого содержимого:

    ;!@Install@!UTF-8!
    RunProgram="setup.exe /qn ADDLOCAL=SQL_Engine SAPWD=password SECURITYMODE=SQL"
    GUIMode="2"
    ;!@InstallEnd@!


    После чего в командной строке выполняем следующую команду:

    copy /b 7ZSD_LZMA.sfx + config.txt + SQL2005E.7z SQL2005E.exe

    Файл SQL2005E.exe, полученный в результате этой операции и будет нашим дистрибутивом, который можно распространять со своим приложением.
  6. Интеграция в приложение.
    Этот пункт, пожалуй, самый простой. В зависимости от того, каким образом вы собираетесь устанавливать Microsoft SQL Server Express (в момент установки приложения или после проверки при каждом запуске) и будет проходить интеграция. Это может быть либо Custom Action для Windows Installer, либо в коде вашего приложения будет присутствовать подобный код:
    ProcessStartInfo psi = new ProcessStartInfo();
    psi.FileName = filePath; 
    Process p = new Process();
    p.StartInfo = psi;
    p.Start();
    p.WaitForExit(); 
    if (p.ExitCode != 0)
       throw new ApplicationException("Ошибка при установке");
Более подробную информацию о встраивании Microsoft SQL Server Express в свои приложения можно найти в статье Embedding SQL Server Express into Custom Applications.