Как добавить "резиновый" SplashScreen в XE7

Как известно, в XE7 упростили возможность добавления заставки (SplashScreen) для нашего приложения. В этой заметке я расскажу как добавлять «статичную» и «резиновую» заставки. Данная заметка

Читать на сайте автора.

Спрайтовая анимация в FM

Читать на сайте автора.

Немного про замыкания

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

program closuretest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
System.SysUtils;

procedure Test;
var
i: Integer;
m: string;
proc, proc2: TProc;

function GetClosure1:TProc;
begin
Result := proc;
end;

function GetClosure2:TProc;
begin
Result := proc2;
end;

var
intf1, intf2: IInterface;
begin
i := 0;
proc := procedure()
begin
inc(i,2);
Writeln(i);
end;
proc2 := procedure()
begin
inc(i,3);
Writeln(i);
end;

proc();
proc2();

GetClosure1.QueryInterface(IInterface, intf1);
GetClosure2.QueryInterface(IInterface, intf2);
Writeln(GetClosure1 = GetClosure2); //False
Writeln(intf1 = intf2); // TRUE, что и требовалось доказать

Read(m);
end;

begin
try
Test;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

Читать на сайте автора.

Android 4.4 и запись на внешнюю карту памяти…

До Android 4.4 (API 19), нам приходилось читать файлы (vold.fstab, mounts), чтобы понять, установлена ли внешняя карта памяти и какой до неё путь.  Теперь у

Читать на сайте автора.

Переход с Delphi 2010 на Delphi XE7

Чуть больше года назад я писал об успешном переходе с Delphi 7 на Delphi 2010 приложений в нашей компании (Вот и мы перешли на Юникод).

В этой заметке кратенько опишу об особенностях перехода с Delphi 2010 на Delphi XE7.

Требования остались прежними – надо чтобы работало и там, и там. Впоследствии, возможно, с отказом от использования Delphi 2010. (От Delphi 7 мы уже отказались… ну за исключением совсем старых программ, которые не развиваются.)

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

Черновой переход – компиляция и запуск программ со случайным “прокликиванием” интерфейсов и без прогонки тестов – у меня занял всего несколько часов (менее одного рабочего дня). Тяжелее скучнее всего было делать замены такого плана:

  с DecimalSeparator на
  {$if CompilerVersion >= 24}FormatSettings.{$ifend}DecimalSeparator

потому что надо было:

  • найти номер версии Delphi, где это произошло в RTL (в данном случае это XE3 – произошёл отказ аж от 20 штук глобальных переменных в пользу всего одной);
  • таких подобных мест очень много – кроме групповой замены я себе ещё помечал, что не плохо бы пересмотреть код.

Удивила так называемая нормализация типов TPoint, TRect, TSize (в XE2). В одном месте даже написал такой хелпер (хелпер оказался лишним, всё решилось проще, путём переименования локальной переменной).

Ну и некоторые другие редкие мелочи, типа UNIT_PTR или LRESULT.

Переход пока черновой, ради спортивного интереса. Скорость полной сборки приложений можно сказать, что осталась прежней – я проводил по несколько измерений после разных сценариев использования IDE – разница в среднем на 0.4 сек дольше, чем в Delphi 2010 (редко бывало и наоборот). Размер исполняемого exe-файла, получаемого на выходе – в XE7 в среднем на 2.5 мегабайта больше (в Release-сборке, проверял на семи проектах с размером экзешников от 6 до 19 мегабайт).

При этом, стоит отметить как плюс в сторону IDE – Find Declaration (он же Ctrl+Click) стал стабильно добираться до цели, в Delphi 2010 же он через какое-то время просто отваливается.

Сейчас в XE7 пока достаёт только то, что при открытии проекта IDE сразу загружает все модули,объявленные в dpr-файле. И из-за того, что компоненты в IDE я пока не устанавливал, Delphi выдаёт десятки окон вида:

image

Приходится зажимать Escape и ждать… ну секунд 10, терпимо.

Пока я доволен.

P.S.: не спешно (ну очень не спешно) готовлю обновление для BaseForms и dnSplitter. Ваши комментарии могут сыграть роль волшебного пендаля :с).

Читать на сайте автора.

Про OCIBreak и принудительное прерывание обращения к БД

Я как-то уже не раз писал о том, что мы не используем стандартные компоненты доступа к БД. Почти всё самописное. И работаем мы с Oracle.

Недавно я, наконец-таки, сделал “фишку”, без которой вполне можно жить, но с ней приятнее.

Представьте, что у вас есть запрос к БД, который выполняется длительное время. Ну, например, пользователь указал слишком мягкие критерии для фильтрации данных. Или индекса в БД нет. Или запрос изначально “кривой”. Или всё вместе взятое… Для прерывания выполнения текущего обращения к серверу в OCI есть стандартная функция – OCIBreak.

У нас я реализовывал так: в отдельном потоке запускается запрос к серверу. Если запрос выполняется длительное время, то появляется модальное окошко с кнопкой [Прервать]:

image

По завершению запроса – окошко скрывается. Если пользователь успеет нажать кнопку – вызывается OCIBreak,  и запрос корректно прерывается.

Таймаут до появления такого окошка установлен в две секунды – большинство запросов отрабатывают гораздо быстрее. Но если вдруг пользователь видит это окно, то на интуитивном уровне он может догадаться, что надо как-то ограничивать параметры своего запроса.

Однако иногда бывает так, что OCIBreak не прерывает запрос. Точнее он его прерывает, но приходится долго ждать. Это встречается у нас всё реже, но встречается, обычно в старых запросах, когда клиент говорит серверу – мол сделай то-то, а я подожду. И пока сервер не закончит транзакцию – приложение как бы “висит”. А если пользователь испугался и нажал [Перервать] – начинается откат транзакции. И пользователь снова ждёт, пока сервер не отпустит транзакцию. А приложение – продолжает “висеть”.  И, по хорошему, дождаться бы. Но это раздражает, и есть “продвинутые” пользователи, которые тупо прекращают выполнение программы через диспетчер задач.

Вот для таких, довольно редких случаев, я реализовал дополнительную “фишку” – принудительное прерывание. Работает так: если в течении 5 секунд OCIBreak не отпустил обращение к БД, то кнопка [Перервать] превращается в [Принудительно] и её снова можно нажать.

Что же происходит в этом случае? (Сначала я пробовал убить поток, выполняющий обращение к серверу, но это, конечно же, ничем хорошим не кончилось.)

При нажатии на кнопку [Принудительно] я делаю две вещи:

  • запускаю отдельным потоком вторую сессию к БД и выполняю: alter system kill session »:sid, :serial» immediate;
  • разрываю текущее TCP-соединение на стороне приложения.

Первый пункт нужен, чтобы сервер понял о наших намерениях – мы не собираемся больше ждать. Второй пункт для меня был не тривиальным. Не вдаваясь в подробности поиска решения, привожу код модуля, который у меня получился:

unit MyMIBUtils;

interface

uses
  Windows;

type
  ULONG = Integer;
  PVOID = Pointer;

const
  ANY_SIZE = 1;
  AF_INET = 2;

type
  PMIB_TCPROW = ^MIB_TCPROW;
  _MIB_TCPROW_W2K = packed record
    dwState: DWORD;
    dwLocalAddr: DWORD;
    dwLocalPort: DWORD;
    dwRemoteAddr: DWORD;
    dwRemotePort: DWORD;
  end;
  MIB_TCPROW = _MIB_TCPROW_W2K;
  TMibTcpRow = MIB_TCPROW;
  PMibTcpRow = PMIB_TCPROW;

const
  MIB_TCP_STATE_CLOSED = 1;
  MIB_TCP_STATE_LISTEN = 2;
  MIB_TCP_STATE_SYN_SENT = 3;
  MIB_TCP_STATE_SYN_RCVD = 4;
  MIB_TCP_STATE_ESTAB = 5;
  MIB_TCP_STATE_FIN_WAIT1 = 6;
  MIB_TCP_STATE_FIN_WAIT2 = 7;
  MIB_TCP_STATE_CLOSE_WAIT = 8;
  MIB_TCP_STATE_CLOSING = 9;
  MIB_TCP_STATE_LAST_ACK = 10;
  MIB_TCP_STATE_TIME_WAIT = 11;
  MIB_TCP_STATE_DELETE_TCB = 12;

type
  TCP_TABLE_CLASS = Integer;

const
  TCP_TABLE_BASIC_LISTENER = 0;
  TCP_TABLE_BASIC_CONNECTIONS = 1;
  TCP_TABLE_BASIC_ALL = 2;
  TCP_TABLE_OWNER_PID_LISTENER = 3;
  TCP_TABLE_OWNER_PID_CONNECTIONS = 4;
  TCP_TABLE_OWNER_PID_ALL = 5;
  TCP_TABLE_OWNER_MODULE_LISTENER = 6;
  TCP_TABLE_OWNER_MODULE_CONNECTIONS = 7;
  TCP_TABLE_OWNER_MODULE_ALL = 8;

type
  PMIB_TCPROW_OWNER_PID = ^MIB_TCPROW_OWNER_PID;
  MIB_TCPROW_OWNER_PID = packed record
    dwState: DWORD;
    dwLocalAddr: DWORD;
    dwLocalPort: DWORD;
    dwRemoteAddr: DWORD;
    dwRemotePort: DWORD;
    dwOwningPid: DWORD;
  end;
  TMibTcpRowOwnerPid = MIB_TCPROW_OWNER_PID;
  PMibTcpRowOwnerPid = PMIB_TCPROW_OWNER_PID;

  PMIB_TCPTABLE_OWNER_PID = ^MIB_TCPTABLE_OWNER_PID;
  MIB_TCPTABLE_OWNER_PID = packed record
    dwNumEntries: DWord;
    Table: array [0..ANY_SIZE - 1] of MIB_TCPROW_OWNER_PID ;
  end;
  TMibTcpTableOwnerPid = MIB_TCPTABLE_OWNER_PID;
  PMibTcpTableOwnerPid = PMIB_TCPTABLE_OWNER_PID;


function SetTcpEntry(const pTcpRow: MIB_TCPROW): DWORD; stdcall;
function GetExtendedTcpTable(pTcpTable: PVOID; var dwSize: DWORD; bOrder: BOOL;
  ulAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWORD; stdcall;

function KillProcessAllTCPConnections(AProcessId: DWORD): DWORD;

implementation

const
  iphlpapilib = 'iphlpapi.dll';

function SetTcpEntry; external iphlpapilib name 'SetTcpEntry';
function GetExtendedTcpTable; external iphlpapilib name 'GetExtendedTcpTable';

function KillProcessAllTCPConnections(AProcessId: DWORD): DWORD;
var
  TCPTable: PMibTcpTableOwnerPid;
  Size: DWORD;
  Res: DWORD;
  I: DWORD;
  TCPRow: TMibTcpRow;
begin
  Result := 0;
  TcpTable := nil;
  Size := 0;
  Res := GetExtendedTcpTable(TCPTable, Size, False, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0);
  if Res <> ERROR_INSUFFICIENT_BUFFER then
    Exit;
  GetMem(TCPTable, Size);
  try
    Res := GetExtendedTcpTable(TCPTable, Size, False, AF_INET, TCP_TABLE_OWNER_PID_CONNECTIONS, 0);
    if Res <> NO_ERROR then
      Exit;

    for I := 0 to TCPTable^.dwNumEntries - 1 do
      if TCPTable^.Table[I].dwOwningPID = AProcessId then
        with TCPTable^.Table[I] do
        begin
          TCPRow.dwState := MIB_TCP_STATE_DELETE_TCB;
          TCPRow.dwLocalAddr := dwLocalAddr;
          TCPRow.dwLocalPort := dwLocalPort;
          TCPRow.dwRemoteAddr := dwRemoteAddr;
          TCPRow.dwRemotePort := dwRemotePort;
          Res := SetTCPEntry(TCPRow);
          if Res = NO_ERROR then
            Inc(Result);
        end;
  finally
    FreeMem(TCPTable);
  end;
end;

end.

Этот код работает на Windows XP with SP2 и выше.

Соответственно я вызываю:

  KillProcessAllTCPConnections(GetCurrentProcessId);

и все текущие TCP-соединения моего процесса прерываются (а у меня оно всего одно). Получить информацию о TCP-соединении, которое используется именно текущим OCI-обращением к серверу мне не удалось, да я особо и не пытался. Если вдруг понадобится – это можно сделать, просмотрев активные соединения непосредственно до и после коннекта к БД.

 

P.S.: Пару слов про NonBlocking-mode, который есть в OCI. В современном мире многопоточных операционных систем его не рекомендуется использовать вовсе.

Читать на сайте автора.

Delphi Event bus

Решились на работе поддержать Delphi сообщество и начать выкладывать в open source собственные наработки. 

Первой ласточкой суждено было стать пакету DelphiEventBus — реализация паттерна проектирования Event Bus.  
В Jаva мире есть такие пакеты как guava-libraries, но в Delphi ничего похожего найти не удалось. Потому решено было запилить нечто подобное. 

Из статьи Java event bus library comparison можно выцепить характеристики библиотек реализующих шину сообщений. 

Для DelphiEventBus получается следующие:

  • Объявление слушателя — аннотация

  • Синхронность отправки в шину — по умолчанию отправка синхронна. 

  • Асинхронность — в планах на будующее

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

  • Иерархия событий — да. Событие это объект. Есть базовый класс всех событий. Обработчик может ждать события определенного класса и всех его наследников

  • Строгость ссылочности листенера — строгая. Обязательная дерегистрация. Регистрируются и дерегистрируются  сразу все обработчики в листенере

  • Приоритет обработчиков — отсутствует. Но дополнительно реализован механизм хуков, похожий на виндовый механизм.

  • Реализованы хуки — возможность игнорировать фильтры и выстраивать цепочки обработки. Вызов хуков строго обратен порядку регистрации.

Должно работать на XE3 и выше. Еще бы readme перевести на английский…


Читать на сайте автора.

Коррекция шрифта в Object Inspector’е

Пробую Delphi XE7. Пока нравится. Поставил GExperts – очень уж я привык к сочетаниям Ctrl+Alt+Down и Ctrl+Alt+Up – переход к следующему/предыдущему объявлению идентификатора. CnPackWizards пока не вышел, но и без него вполне можно жить.
И тут вдруг бросилось в глаза, что у Object Inspector’а шрифт не такой, как везде… (везде – Tahoma 8, а тут – Segoe UI 9)
В GExperts вроде и была возможность менять шрифт инспектора, но почему-то пропала. Поэтому “накидал” на скорую руку пакет, исправляющий данную особенность, результат на картинке (было – стало):
image
Исходник пакета доступен на GitHub, или можно скачать zip-архив отсюда – в  папке ObjInspFntChngr. В модуле uObjInspFntChngr.pas через константы:

const
  PreferParentFont = True;
  PreferFontName = 'Tahoma';
  PreferFontSize = 8;

можно настроить шрифт по своему вкусу.

HINT: Возможно пакет поможет тем, у кого масштабирование текста в Windows больше 100%.

Для установки – открыть dproj-файл, правой кнопкой мыши в Project Manager’е – Install. Пакет можно использовать и в предыдущих версиях Delphi, для этого достаточно удалить dproj-файл и открыть пакет через dpk-файл.

Читать на сайте автора.

Helper для TMessage

В предыдущей заметке я упомянул о хелпере для отладки сообщений Windows. Эта заметка будет предельно краткой: ссылки на исходники и скриншот демки.

Исходник: модуль MsgHlpr.pas. Использовать можно так:

procedure TfrmMain.ApplicationEvents1Message(var Msg: TMsg; var Handled: Boolean);
begin
  Memo1.Lines.Add(Msg.ToString);
..

Демо-приложение:

image

Скачать: MsgHlpr.pas + демо — zip-архив, MsgHlpr.pas на GitHub, исходник демо на GitHub, исполняемый exe-файл демки (zip-архив, 447 КБ).

Работает в Delphi 2010 и выше, при желании легко адаптировать и к предыдущим версиям Delphi.

Читать на сайте автора.