среда, 20 мая 2009 г.

Работа с Excel из C#.

Столкнулся с необходимостью поработать с файлами Excel из C#. Задачи простые - считать данные из книги Excel, создать новую книгу Excel, внести в нее данные. Предполагается, что Excel на компьютере имеется, но какой версии, не известно. Файлы по размерам маленькие, скорость работы не критична.

Существует несколько вариантов работы с Excel из C#: автоматизация Excel, подключение через OleDB/ODBC, дополнительные библиотеки (Aspose Excel), работа через XML, через Open XML и т.п.

Наиболее простой вариант - воспользоваться автоматизацией Excel. Да, скорость работы - не блеск. Зато удобно использовать, код пишется быстро, объемы кода не велики. Из .NET автоматизация подключается парой кликов мыши - добавил в References сборку Microsoft.Office.Interop.Excel - и работай с привычными com-объектами Application,Workbook, Worksheet и т.п.

Проблема одна - сборки "Microsoft.Office.Interop.Excel" для каждой версии Excel разные. У меня установлен office 2003 и, соответственно, interop-сборка версии 11. А что делать, если мне нужно разработать приложение, которое может работать с Excel 97? Или с любыми версиями Excel? Для того, чтобы считать или записать пару значений в ячейки листа Excel сложного api не требуется - любой Excel сгодится. А мне приходится привязываться к конкретной версии.

Можно воспользоваться поздним связыванием. Но если его использовать "в лоб", то ни о никаком удобстве работы речи уже не идет - код станет сложным, а вызовы методов - косвенными, нетипизированными. Ошибок будет - вагон.

Выход нашелся. На СodeProject обнаружил замечательную статью "SafeCOMWrapper - Managed Disposable Strongly Typed safe wrapper to late bound COM". В ней изложена элегантная методика использования позднего связывания, устраняющая все проблемы: связывание становится поздним (привязки к конкретной версии Excel нет), для объектов автоматизации автоматически реализуется шаблон IDisposable (отработанные объекты уничтожаются автоматически), все вызовы методов явные.

Идея реализации, вкратце, следующая. Для каждого COM-объекта автоматизации вы прописываете отдельный интерфейс. В интерфейс включаете все методы и свойства, которые вам необходимо использовать у этого COM-объекта. Обратите внимание - только те, которые необходимо использовать, а вовсе не все, реализуемые COM-объектом.

Каждый COM-объект автоматизации "заворачивается" в класс COMWrapper, унаследованный от RealProxy. RealProxy - это стандартный класс, позволяющий организовать перехват вызовов методов. При создании COM-объекта вы создаете экземпляр COMWrapper и указываете требуемый вам интерфейс. Среда динамически генерирует прокси-объект, реализующий этот интерфейс. С этим прокси-объектом вы в дальнейшем и работаете как с объектом автоматизации. Вызов любого метода прокси-объетка перехватывается и транслируется в вызов метода Invoke класса RealProxy, перекрытый в классе COMWrapper. Здесь его можно обработать как душе угодно. В реализации по умолчанию, вызовы свойств транслируются в вызовы соответствующих методов get_ и set_, создаваемых .NET, возвращаемые объекты автоматизации автоматически заворачиваются в COMWrapper и т.п.

В оригинально статье приведен пример использования данной методики для Microsoft.Outlook. Я, на базе приведенных исходных кодов, адаптировал методику для работы с Microsoft.Excel. Естественно, реализовав интерфейсы только для тех объектов автоматизации, которые потребовались мне для работы. Дополнить интерфейсы недостающими методами или добавить интерфейсы для других объектов автоматизации не составляет труда. Достаточно посмотреть через Object Browser метаданные сборки "Microsoft.Office.Interop.Excel" и скопировать оттуда необходимые объявления методов и интерфейсов (с минимальной адаптацией).

Пример кода для работы с Excel:
using (Application app = ExcelApplication.Create()) {
String s = app.Version;
app.Visible = true; // make excel visible
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
wb.Title = "new workbook";
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
//try to assign some values to some cells
using (Range cells = ws.Cells) {
for (int i = 1; i < 10; ++i) {
using (Range r = cells[i, i]) {
r.Value = i * i;
}
}
}
}
}
wb.Saved = true;
wb.Close();
}
app.Quit();
}
Оригинальные исходные коды лежат на codeproject вместе со статьей. Исходные коды, адаптированные для Microsoft Excel, можно взять здесь.Update. Практика использования выявила небольшую проблему - забыть вызвать Dispose у оберток объектов Excel очень легко. После чего Excel висит в памяти до тех пор, пока не закроешь приложение (или до следующей сборки мусора). Пример:
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheet ws = wb.Sheets[1]) 
/*Здесь потерян Dispose у объекта Worksheets, который создается при 
вызове wb.Sheets*/
{ .... }}
вместо
using (Workbook wb = app.Workbooks.Add(Type.Missing)) {
using (Worksheets worksheets = wb.Sheets) {
using (Worksheet ws = worksheets[1]) {
.... }}

Просто надо быть внимательнее.

понедельник, 4 мая 2009 г.

Ссылки: доклад Авантюриста, переписка Стругацкого с Ходорковским

Стенограмма доклада Авантюриста о глобальном экономическом кризисе на "Большом кофе-брейке для бизнеса", в г. Томске, 27 марта 2009 года. Вот здесь обработанные выдержки из доклада - уже более официальный источник. P.s.Похоже расшифровка выступления авантюриста изначально взята отсюда.

Во что вкладывать деньги? В профессию и в детей...

Переписка Бориса Стругацкого и Михаила Ходорковского в Новой газете:
письмо 1,
письмо 2,
письмо 3.

понедельник, 27 апреля 2009 г.

Самооценка компетентности программиста.

Хороший ли я программист? Как самому себе ответить на такой вопрос?

Достаточно давно слышал о таком способе самооценки. Если пять программистов из тех, кто на голову превосходят вас по уровню программирования, отозвались о вас как об очень хорошем программисте, то можно считать, что это действительно так. Тут главное, суметь найти нужное количество таких программистов :)

На днях на глаза попался другой, более формализованный способ: матрица компетентности программиста.

Update. Как вариант, можно попробовать оценить качество кода, и по этой оценке судить о профессионализме программиста.

пятница, 24 апреля 2009 г.

Видео-лекции на английском языке.

Наткнулся недавно на замечательный ресурс: videolectures.net - коллекция лекций. Коллекция содержит видеозаписи лучших лекций, прочитанных на различных конференциях, семинарах, летних школах и т.п. Все лекции на английском языке.

Тематика лекций - научная. Масса лекций на тему компьютерных наук, посвященных Data Mining, анализу изображений, машинному обучению, компьютерной графике, распознаванию речи и т.п. Есть лекции по биологии, философии, истории, окружающей среде, психологии, бизнесу, экономике и т.д. Кроме лекций, на сайте выложено изрядное количество интервью.

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

Лекции, надо сказать, интересные и информативные. Так что, тренеруем английский, получаем полезную информацию.

Update:

вторник, 24 февраля 2009 г.

Как получить hardware id. Исходники DiskID32 на Delphi

Потребовалось защитить приложение, написанное на Delphi, от копирования. Точнее, потребовалось обеспечить возможность привязывать приложение к конкретному компьютеру, на котором оно устанавливается.

Чтобы "привязать" программу к компьютеру, необходимо уметь генерировать уникальный идентификатор для его "железа" - hardware id. По hardware id генерируется серийный номер, который будет работать только на заданному компьютере. Такой подход активно используется для защиты ПО, хотя для пользователей он порой не слишком удобен - при апгрейде компьютера серийный номер может перестать работать.

Вопрос - как сгенерировать hardware id?

Изучение интернета показало, что имеется три основных варианта.
  1. Использовать MAC-адрес сетевой карты.
  2. Использовать информацию из WMI.
  3. Использовать серийный номер жесткого диска, на котором установлена операционная система.
Недостатки использования MAC-адреса описаны здесь:
  • Сетевых карт может быть несколько.
  • При включении bluetooth/wi-fi появляется новая "сетевая карта".
  • При отключенном сетевом проводе MAC-адрес не определяется.
Недостатки WMI:
  • В Me/2000/XP эта служба встроена, но в более ранних версиях Windows ее нужно доставлять.
  • Под Vista, с включенным UAC, без прав администратора доступ к WMI не получить.
  • Cлужба WMI может быть отключена.
  • Получение информации из WMI - очень медленный процесс. Несколько секунд задержки при запуске гарантированы.
Вариант с серийным номером жесткого диска - хорош. Тем более, что есть программа DiskID32, с открытыми исходными кодами, которая позволяет получать серийный номер жесткого диска в любых версиях Windows (от 9X до 64-bit), причем как при наличии прав администратора, так и при их отсутствии. Бери и пользуйся.

Беда только в том, что у нас приложение написано на Delphi. Значит и процедура получения hardware id нужна на Delphi (вариант с DLL не подходит). А DiskID32 написан на С++...

Делать нечего. Вооружился отладчиком и перевел исходные коды DiskID32 с C++ на Delphi. Основные проблемы, с которыми столкнулся: в Delphi нет битовый полей (они использовались при описании ряда структур) и нет полноценной адресной арифметики. К счастью, битовые поля оказались не принципиальны и я их просто отбросил. Операции над указателями (инкремент, декремент) заменил на аналогичные операции с массивами. Не слишком изящно, но работает. Код старался транслировать один в один, ничего не удаляя. Оригинальные исходники DiskID содержат код для определения MAC-адресов сетевых карт. В моем случае MAC-адрес не требовался, поэтому эти функции я не транслировал.

Код был протестирован на Windows XP, Vista, Server 2003 (с рейдом и без), а так же под Vista 64-bit. Под 9x код не тестировался, поэтому гарантий, что он заработает, нет никаких. Если вы найдете ошибку в коде - пожалуйста, сообщите мне о ней на email, указанный в исходных кодах.

Остается выразить благодарность датской компании Efaktum, для которой была выполнена эта трансляция кода, и которая любезно разрешила мне выложить переведенный код в открытый доступ.

Скачать исходные коды DiskID32 for Delphi.

Update: В первой версии исходных кодов нашлась ошибка, см. комментарии. Исправил ее и, заодно, перевел проект на Delphi 2010 (совместимость с Delphi 7 сохранена):

Скачать исходные коды DiskID32 for Delphi 2010.

Download source codes of DiskId32 for Delphi.
Download source codes of DiskId32 for Delphi (updated 16.01.2012)

View source codes of DiskId32 for Delphi.
Short description in English.

P.s. если вы не хотите, чтобы diskID выводил информацию на консоль, закомментируйте
макрос {$DEFINE PRINTING_TO_CONSOLE_ALLOWED}. В таком виде исходники пригодны для использования в неконсольных приложениях.

Update: Под x64 нет crtdll.dll. Вместо нее следует использовать msvcrt.dll. Т.е. в crtdll_wrapper объявить crt-функции следующим образом:
function crt_isspace(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isspace';
function crt_isalpha(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalpha';
function crt_tolower(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'tolower';
function crt_isprint(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isprint';
function crt_isalnum(ch: Integer): Integer; cdecl; external 'msvcrt.dll' name 'isalnum';
Библиотека crtdll.dll нужна только если требуется совместимость с win95.

Update: август 2013: Прислали на email обновление: "1. Сделал рабочей ReadIdeDriveAsScsiDriveInNT - char/ansichar ошибка; 2. Избавился от msvcrt.dll (переписал функции на дельфи); 3. Немного оптимизации." Обновленную версию следует брать в svn.