Для вызова внешнего приложения из программы на Delphi обычно используется функция WinAPI CreateProcess. Но как получить то, что это приложение выводит в консоль, и, например, отобразить в логе программы? Варианта два. Первый: пере-направить вывод консольного приложения в файл и потом прочитать этот файл. Однако, создавать временный файл не охота. А кроме того, такой способ не всегда хорошо работает. Есть другой способ - использовать пайпы (pipes). Ниже приведен код процедуры RunDosCommand на Delphi, который использует этот способ. Весь вывод на консоль записывается в AMemo: TMemo.
functionRunDosCommand(ACmdString: String; AMemo: TMemo; AReadPipe, AWritePipe: THandle): Boolean; const ReadBuffer = 4200; var Security : TSecurityAttributes; ReadPipe, WritePipe: THandle; start : TStartUpInfo; ProcessInfo : TProcessInformation; Buffer : Pchar; BytesRead : DWord; begin Result := False; if AMemo <> nilthen begin AMemo.SelStart := Length(AMemo.Text); AMemo.SelText := ACmdString + #13#10; AMemo.SelStart := Length(AMemo.Text); Application.ProcessMessages; end; ReadPipe := AReadPipe; WritePipe := AWritePipe; with Security do begin nLength := SizeOf(TSecurityAttributes); bInheritHandle := true; lpSecurityDescriptor := nil; end; if (ReadPipe = 0) and (WritePipe = 0) then begin ifnot CreatePipe(ReadPipe, WritePipe, @Security, 0) then begin ReadPipe := 0; WritePipe := 0; end; end; if (ReadPipe <> 0) or (WritePipe <> 0) then begin if AMemo <> nilthen begin Buffer := AllocMem(ReadBuffer + 1); end; FillChar(Start, Sizeof(Start), #0); start.cb := SizeOf(start) ; start.hStdOutput := WritePipe; start.hStdError := WritePipe; start.hStdInput := ReadPipe; start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW; start.wShowWindow := SW_HIDE; if CreateProcess(nil, PChar(ACmdString), @Security, @Security, true, NORMAL_PRIORITY_CLASS, nil, nil, start, ProcessInfo) then begin WaitForSingleObject(ProcessInfo.hProcess, INFINITE); if AMemo <> nilthen begin BytesRead := 0; ReadFile(ReadPipe, Buffer[0], ReadBuffer, BytesRead, nil); Buffer[BytesRead] := #0; AMemo.SelStart := Length(AMemo.Text); AMemo.SelText := String(Buffer); AMemo.SelStart := Length(AMemo.Text); Application.ProcessMessages; end; Result := True; endelse if AMemo <> nilthen begin AMemo.SelStart := Length(AMemo.Text); AMemo.SelText :='Command failed.'#13#10; AMemo.SelStart := Length(AMemo.Text); Application.ProcessMessages; end; if AMemo <> nilthen begin FreeMem(Buffer); end; CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); if (AReadPipe = 0) and (AWritePipe = 0) then begin CloseHandle(ReadPipe); CloseHandle(WritePipe); end; end; end;
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Наконец-то появилась версия ASProtect 1.65 в которой заявлена совместимость с Delphi XE и XE2. Я попробовал - созданный в Delphi XE2 и запакованный новым “аспром” exe действительно запускается и работает. Ошибок пока не замечено.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Столкнулся с интересным багом при получении информации о версии из ресурсов (Visual Studio 10, C++).
Access Violation возникает при выполнении VerQueryValue (см. картинку). Причем, ошибка возникает только если используется Multi-Byte Character Set. А если выставить в свойствах проекта Use Unicode Character Set, то ошибка пропадает. Из этого можно сделать вывод, что баг именно в VerQueryValueA, а в VerQueryValueW его нет. Что ж, решение проблемы заключается в переписывании функции чтения информации о версии таким образом, чтобы всегда использовалась функция VerQueryValueW. Ниже приведен листинг готовой функции.
UPD 30.01.2014. Спустя полтора года выяснилось, что и с использованием VerQueryValueW возможен Access Violation. Что же, лучше поздно чем никогда. Дело в том, что эта функция рассчитана на то, чтобы быть использованной совместно GetFileVersionInfo. Возможно, VerQueryValue может в каких-то случаях модифицировать ресурс. В общем, фикс заключается в том, что нужно выделить участок памяти (например, с помощью LocalAlloc), скопировать в него информацию о версии из ресурса (CopyMemory), и использовать его при вызове VerQueryValue. Реализация приведена ниже.
3. Добавить в Переменную среды Path (Пуск->Панель инструментов->Система->Дополнительно->Переменные среды) путь к bin каталогу Qt (например, C:\\qt\\4.8.2\\bin)
Вот, в принципе, все. В Microsoft Visual Studio IDE должен появиться пункт меню Qt. А в диалоге New Project - типы проектов Qt4 Projects.
Есть только два нюанса:
1. Если хочется избавиться от Qt Runtime (статически прилинковать к exe файлу все необходимые библиотеки), то нужно пересобрать Qt с ключом -static:
2. По-умолчанию библиотеки Qt компилируются в режиме Multi-threaded DLL - /MD (для Debug - Multi-threaded Debug DLL - /MDd). Что означает наличие зависимостей от msvcr90.dll и msvcp90.dll. Т.е. при установке программы возможно потребуется устанавливать Microsoft Visual C++ 2008 Redistributable Package. Чтобы избавиться от этой зависимости нужно перед сборкой Qt поправить qmake.conf ( C:\qt\4.8.2 \mkspecs\win32-msvc2008\qmake.conf) следующим образом:
Чтобы убрать зависимость от Microsoft Visual C++ Runtime нужно в свойствах проекта выбрать Runtime Library: Multi-threaded (/MT). Получающийся при компиляции exe/dll будет несколько больше, зато не нужно будет устанавливать Visual C++ Redistributable Package.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
В моем посте про проверку серверного сертификата SSL в Delphi 7 проверка этого самого сертификата выполнялась вызовом функции CryptoAPI CertGetIssuerCertificateFromStore. Я столкнулся на практике со следующим: оказывается, что на эту функцию оказывает влияние Internet Explorer Enhanced Security (по умолчанию оно включено на всех серверах Windows 2003). Т.е. до тех пор, пока хост не будет включен в список доверенных, его сертификат тоже может не проходить проверку. Как-то так (слишком глубоко эту тему не копал). В общем, если на одном компьютере проверка сертификата работает, а на другом - нет (ошибка “Error connecting with SSL”), то следует проверить - включен ли IE Enhanced Security, и если да, то либо отключить его, либо добавить хост в список доверенных.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
ASProtect, к сожалению, не поддерживает приложения, сделанные в Delphi XE 2. Упакованные им exe-ки не запускаются. В Windows Application Log-е появляется соответствующая запись об ошибке в приложении.
Сообщение о несовместимости Delphi XE 2 и ASProtect на официальном форуме датировано 18 ноября 2011, но до сих пор решения нет. Последний ответ от 26 марта 2012 гласит:
Your problem in the queue for the decision and will be solved in the next release of ASProtect (will be released in the nearest future).
Ждем новую версию… Если в ближайшее время не появится, придется переходить на какой-нибудь другой протектор.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Это не первый мой аккаунт в github, поэтому потребовалось как-то организовывать одновременный доступ к нему из под разных учеток, с разными SSHKey. По этому поводу маленький “трик” - как я все это сделал:
1. При установке Git for Windows создает такой ярлык для Git Bash:
Как мы видим текущий каталог Git Bash - %HOMEDRIVE%%HOMEPATH%. В нем хранятся все настройки git, каталог .ssh с SSHKey и проекты обычно тоже там.
Я создал в %HOMEDRIVE%%HOMEPATH% каталог Coolsoftware и в нем git.cmd следующего содержания:
Затем создал ярлык на этот git.cmd и вынес его на рабочий стол, обозвав “Git Bash - Coolsoftware”. Теперь при его запуске текущий каталог пользователя в “баше” %HOMEDRIVE%%HOMEPATH%\Coolsoftware, а при запуске старого ярлыка “Git Bash” текущий каталог %HOMEDRIVE%%HOMEPATH%. Таким образом получилось два “баша”, в каждом - свои настройки.
Далее запустил “Git Bash - Coolsoftware” произвел установку github в соответствии с инструкцией и выложил проект VHashedStringList - оптимизированный список строк, доступ к элементам которого организован с помощью хеш-индекса.
TVHashedStringList - аналог стандартного THashedStringList (фактически, он сделан на его основе, хотя наследуется не от него, а от TStringList).
Существенное отличие от THashedStringList заключается в том, что оптимизированы операции перестроения индекса при изменении списка.
В THashedStringList полное перестроение индекса требуется после любого изменения, что приводит к значительным задержкам в реальных приложениях.
Например, в следующем примере полное перестроение индекса будет произведено 3 раза:
procedureChangeList(lst: THashedStringList); begin lst.Values['key1'] := 'Value1'; lst.Values['key2'] := 'Value2'; //неявный вызов IndexOfName и перестроение индекса lst.Values['key3'] := 'Value3'; //неявный вызов IndexOfName и перестроение индекса lst.Values['key4'] := 'Value4'; //неявный вызов IndexOfName и перестроение индекса end;
В отличие от THashedStringList, TVHashedStringList не требует полного перестроения индекса после добавления нового элемента в конец списка или
при изменении значений элементов списка. Перестроение происходит только после вставки нового элемента в список или удаления из списка.
Поэтому в следующем примере полного перестроения индекса не произойдет ни разу.
procedure ChangeList(lst: TVHashedStringList); begin lst.Values['key1'] := 'Value1'; //индекс модифицирован и не требует перестроения lst.Values['key2'] := 'Value2'; //индекс модифицирован и не требует перестроения lst.Values['key3'] := 'Value3'; //индекс модифицирован и не требует перестроения lst.Values['key4'] := 'Value4'; //индекс модифицирован и не требует перестроения end;
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
В много-поточном приложении довольно часто возникает необходимость синхронизировать обращение к разделяемому ресурсу, который могут одновременно читать несколько потоков “читателей”, а производить запись в этот ресурс может только один поток “писатель”. Когда читатели читают данные, то писатель не может писать и должен ждать, пока все читатели не закончат чтение. Когда писатель пишет, то все читатели должны ждать, пока он не запишет все данные и не освободит ресурс.
Ниже приведена моя реализация “читателя” и “писателя”. В этой реализации есть допущение о том, что нам известно максимальное количество читателей MAX_READERS.
LONG gCounter = 0; //алгоритм читателя for (;;) //бесконечный цикл ожидания освобождения ресурса { LONG n = InterlockedIncrement(&gCounter); //в n - значение gCounter после инкремента if (n <= MAX_READERS) break; //писатель ничего не пишет - можно читать InterlockedDecrement(&gCounter); } // здесь читаем данные ... // InterlockedDecrement(&gCounter); //освобождаем блокировку читателем // алгоритм писателя for (;;) //бесконечный цикл освобождения ресурса читателями/писателями { LONG n = InterlockedCompareExchange(&gCounter, (MAX_READERS+1), 0); //в n - предыдущее значение gCounter, которое было ДО попытки заменить его на MAX_READERS+1 в InterlockedCompareExchange; //если там был 0, то никаких читателей/писателей не было, новое значение в gCounter будет MAX_READERS+1; //если в gCounter был не 0, то это значение НЕ будет заменено на MAX_READERS+1, а останется прежним if (n == 0) break; } // здесь пишем данные ... // InterlockedExchangeAdd(&gCounter, -(MAX_READERS+1)); //освобождаем блокировку писателем
Обращаю внимание на использование Interlocked-функций. Это такие функции, которые обеспечивают атомарность. Например, InterlockedIncrement(&gCounter) - это атомарное увеличение на 1 (инкремент) значения gCounter. Вообще, операция инкремента gCounter не атомарна, она состоит из следующих 3-х операций:
1. прочитать значение из памяти по адресу &gCounter 2. увеличить значение на 1 3. записать результат в память по адресу &gCounter
Если выполняется параллельно 2 потока, то возможна такая ситуация :
[поток 1] читает gCounter, прочитан 0 [поток 2] читает gCounter, прочитан 0 [поток 1] увеличивает значение на 1, получается 1 [поток 2] увеличивает значение на 1, получается 1 [поток 1] записывает 1 в gCounter [поток 2] записывает 1 в gCounter
В итоге в gCounter будет 1, а не 2, как можно было бы предположить.