Показаны сообщения с ярлыком src. Показать все сообщения
Показаны сообщения с ярлыком src. Показать все сообщения

пятница, 29 апреля 2011 г.

Android. Реализация SendEmail

Отправить email из приложения. Это типичная задача, с которой сталкиваешься при разработке под Android. И делов то всего - скопипастить готовый код. Но есть несколько особенностей, которые легко пропустить... и потратить кучу времени в поисках ошибки.

вторник, 12 апреля 2011 г.

Делокализация Delphi приложения

Достался мне недавно в сопровождение проект на Delphi. Проект старый и довольно большой по объему. Все бы ничего, да только написан он был датскими программистами для датских клиентов. В результате - GUI на датском, текстовые сообщения в исходниках - на датском, комментарии - на датском. Даже названия функций - и те во многих случаях написаны по датски.

Я датский практически не знаю. Для нормальной работы мне нужен английский язык. Комментарии не проблема - их можно перевести прямо в исходниках. А вот что делать с самими исходниками?. С одной стороны, все строки в исходниках должны быть написаны на английском. С другой стороны, для пользователя ничего не должно измениться. Интерфейс программы был на датском языке, значит должен оставаться на датском.

В результате, мне пришлось провести де-локализацию исходных кодов с последующей обратной локализацией приложения. Другими словами: в исходниках датские строки я заменил английскими, а в процессе работы приложение подменяет английские строки датским переводом.

Далее речь пойдет о том, как все это было сделано.

суббота, 13 ноября 2010 г.

воскресенье, 3 октября 2010 г.

Каждому списку - по фильтру.

Вам приходилось сталкиваться с программами, в которых значение поля выбирается из длинного списка, а поиска по списку не предусмотрено? Работать с такими программами - сущее мучение. Всегда снабжайте списки средствами быстрого поиска. Ваши пользователи скажут вам большое спасибо.

Одним из вариантов такого поиска является динамическая фильтрация списка по ключевым словам. Динамическая - значит налету, прямо в процессе ввода ключевых слов пользователем. Скажем, у вас есть список сотрудников, в котором отображаются: идентификатор, ФИО, год рождения, адрес. Вы набираете "77 кр". Список сокращается. В нем остаются только те сотрудники, в данных у которых встречаются число 77 и/или слово "кр". Например, с сотрудники с фамилиями "Кравцов", "Окрошкин", проживающие на улице "Крапивина", с почтовым индексом 667770, с 1977 годом рождения, c идентификаторами 77, 777, 6772 и т.п. Поиск может осуществляться по принципу '77 ИЛИ кр' или '77 И кр' - как удобнее.

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

Конечно, можно сделать специальную форму поиска с четырьмя полями: ФИО, адрес, год рождения, идентификатор. Да еще поместить ее в отдельное окно. Можно.. Но зачем? Такие формы оправданы только в случае больших массивов данных, когда поиск ведется с помощью SQL-запросов и для ускорения поиска используются индексы по полям таблиц базы данных. Если же список целиком сидит в памяти (десяток, сотня, несколько тысяч строк), то достаточно одной строки поиска, которая легко помещается рядом со списком. Согласитесь, что место для одного дополнительного поля ввода над/под списком выделить можно всегда.

Теперь о реализации такого фильтра на Delphi. Здесь два важных момента. Во-первых, фильтр должен прикручиваться к любой форме буквально одной-двумя строками кода. Тогда его будет удобно использовать. Во-вторых, фильтр должен быть ориентирован на работу TDataSet, т.к. именно этот класс (и его наследники) используется для хранения списков в клиентских приложениях для работы с базами данных.

Для себя я реализовал такой фильтр в виде модуля FastFilter. Делать отдельный компонент я не стал - фильтр должен годится и для приложений, использующих не только стандартный TEdit, но и сторонние компоненты ввода.

Чтобы прикрутить фильтр к форме достаточно создать экземпляр класса
TFastFilterCommon:
TForm1 = class(TForm)  
  ...  
  private    
   m_F: TFastFilterCommon;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin 
  m_F := TFastFilterCommon.Create(  
    editFilter //поле ввода фильтра  
    , dataSetToFilter //фильтруемый DataSet  
    , false //'a b' означает 'a ИЛИ b' (а не 'a И b') 
  ); 
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin  
  m_F.Free;
end;
Фильтрация будет вестись по всем полям DataSet. Если нужна фильтрация только по выбранным полям, то нужно перечислить эти поля в конструкторе:
m_F := TFastFilterCommon.Create(  
  editFilter //поле ввода фильтра  
  , dataSetToFilter //фильтруемый DataSet  
  , ['Name', 'Surname', 'Address'] //поля для фильтрации
  , false //'a b' означает 'a ИЛИ b' (а не 'a И b') 
); 
Класс содержит внутренний таймер, определяющий задержку между завершением ввода и началом фильтрации. Пользователь набрал слово, прошло 0.2 сек, если пользователь ничего больше не нажимает, начинается фильтрация. Если пользователь не хочет ждать, он может нажать Ctrl+Enter и фильтрация начнется мгновенно.

В фильтре работают клавиши Up и Down. Они позволяют перемещаться по датасету не выходя из поля ввода.

Модуль FastFilter содержит два класса: TFastFilter и TFastFilterCommon. Первый содержит только статические функции, которые реализуют необходимые обработчики в общем виде. Второй заточен под стандартный TEdit. Если используется нестандартный вариант TEdit, то потребуется создать дополнительный класс TFastFilterXXX (или вызывать функции TFastFilter прямо из формы).

Важное ограничение: FastFilter предполагает, что все данные, по которым нужно вести фильтрацию, содержатся в отображаемом на экране DataSet. Но так бывает далеко не всегда. Пример - фильтрация списка клиентов по номерам телефонов. Номера телефонов хранятся, естественно, в другом DataSet, отдельно от списка клиентов, т.к. у каждого клиента может быть по несколько телефонов. В подобных случаях стандартный TFastFilterCommon потребует доработки.

Исходные коды FastFilter и демонстрационного приложения.

Browse source codes,
download source codes, view short description in English.

пятница, 10 сентября 2010 г.

Критические секции с таймаутом в Delphi

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

Стандартные критические сеации таймауты не поддерживают - секции с таймаутом нужно реализовывать самостоятельно. На RSDN есть отличная статья на эту тему - Критические секции. В ней приведен вариант реализации критических секций с таймаутом (см. листинг 14). Код дан на С++. Я портировал этот код на Delphi и, на его основе, реализовал класс TCriticalSectionDbg. Этот класс можно использовать при отладке вместо TCriticalSection (замену удобно проводить с помощью директив условной компиляции). Выкладываю исходные коды. Если вы обнаружите в них ошибку, пожалуйста сообщите о ней в комментариях, буду очень признателен.

К исходникам TCriticalSectionDbg приложен тестовый проект на Delphi 2010, демонстрирующий блокировку потоков, а так же аналогичный С++-ный проект (можно сравнивать результаты работы функций построчно). Мне этот проект потребовался при портировании вызова функции InterlockedCompareExchangePointer, который в Delphi оказался несколько, гм.. нетривиален.

Скачать исходные коды TCriticalSectionDbg.

вторник, 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.