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, как можно было бы предположить.
Стандартный генератор случайных чисел (ГСЧ) в C++ - функция rand(). Он работает по так-называемому конгруэнтному линейному алгоритму. Для инициализации генератора используется srand(seed).
У этого генератора есть несколько неприятных особенностей.
1. Он более-менее сносно работает, когда в своей программе вы генерируете небольшое количество независимых между собой случайных величин. Но когда с помощью одного и того же датчика генерируется с десяток различных переменных (A, B, C, D, E, F, G, H, I, J), то последовательность псевдослучайных значений, например, переменной A: A1, A2, A3, A4, A5, … уже не будет выглядеть как случайная.
2. Требуется производить инициализацию счетчика с помощью функции srand(seed) в каждом потоке, в котором вызывается rand().
В общем, столкнувшись на практике со странностями работы стандартного ГСЧ, я решил написать свой. Основные требования - скорость работы и возможность обращаться к одному и тому же ГСЧ из разных потоков. И вот, что у меня получилось:
class CRnd { private: unsigned long m_iran; public: void Init(unsigned long seed); unsigned long Rand(); } void CRnd::Init(unsigned long seed) { m_iran = seed; } unsigned long CRnd::Rand() { unsigned long old_iran = m_iran; unsigned long new_iran, cur_iran; for (;;) { new_iran = 1664525L*old_iran+1013904223L; cur_iran = InterlockedCompareExchange((volatile long*)&m_iran, new_iran, old_iran); if (cur_iran == old_iran) break; old_iran = cur_iran; } return new_iran; }
UPD. Множитель a = 1664525 и слагаемое c = 1013904223 были взяты из статьи в вики. На практике, однако, выяснилось, что получаемая последовательность чисел имеет короткий период, поэтому имеет смысл использовать другие параметры: a = 1103515245, c = 12345, m = 2^31.
unsigned long CRnd::Rand()
{ unsigned long old_iran = m_iran; unsigned long new_iran, cur_iran; for (;;) { new_iran = 1103515245L*old_iran+12345L; cur_iran = InterlockedCompareExchange((volatile long*)&m_iran, new_iran, old_iran); if (cur_iran == old_iran) break; old_iran = cur_iran; } return ((new_iran >> 16) & 32767L); }
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Обычно решают противоположную задачу - вызов внешних модулей (DLL, COM), написанных на C++, Delphi и т.п., из программы на C#. У меня же возникла необходимость вызвать из программы написанной на Delphi функцию библиотеки, написанной на C#. В этом посте я расскажу о том, как это делается.
Итак, нужно сделать на C# COM объект, к которому затем мы сможем обращаться из нашей программы на Delphi.
В Visual Studio создаем новый проект New/Project/Visual C#/Class Library.
В свойствах проекта на вкладке Application жмем кнопку “Assembly Information” и отмечаем “Make assembly COM-visible”.
В свойствах проекта на вкладке Build отмечаем Register for COM interop.
Для регистрации библиотеки вместо regsvr32 нужно использовать regasm. Вызов будет типа такого:
Sqlite замечательная локальная база данных. Не требует установки, легко встраивается в приложения благодаря API. Шустрая благодаря тому, что данные не гоняются ни по сети, ни даже между процессами. У меня загрузка листа в миллион записей в Sqlite занимает порядка 30 секунд. Для сравнения - та же самая процедура загружает те же данные в локальный MySQL в 10 раз дольше. Однако, для того, чтобы загрузка данных в Sqlite происходила с максимально возможной скоростью нужно использовать две вещи:
1. Параметризованный Sql, типа такого: INSERT INTO `table1` (`A`, `B`) VALUES (:param1, :param2). В начале процедуры загрузки выполнять “prepare” (Sqlite3_Prepare_v2). Перед вставкой “биндить” переменные с помощью sqlite3_bind_text, sqlite3_bind_int64 и т.п., а затем выполнять вставку вызовом функции sqlite3_step.
2. Транзакции: вставлять данные большими порциями, например, по 1000 записей. Перед вставкой очередной порции выполнять BEGIN TRANSACTION. В конце вставки - COMMIT TRANSACTION. Если этого не делать, то после каждой операции вставки данные будут немедленно сбрасываться на диск, что замедлит процесс во много раз.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Понадобилось мне осуществить проверку серверного сертификата в моей программе на Delphi. Эта программа была написана довольно давно, на Delphi 7 с использованием Indy 9.
Вот как выглядит проверка сертификата:
var IdHTTP: TIdHTTP; IdSSLIOHandlerSocket: TIdSSLIOHandlerSocket; begin IdHTTP := TIdHTTP.Create(nil); IdSSLIOHandlerSocket := TIdSSLIOHandlerSocket.Create(nil); IdHTTP.IOHandler := IdSSLIOHandlerSocket; with IdSSLIOHandlerSocket do begin SSLOptions.Method := sslvSSLv23; SSLOptions.Mode := sslmClient; SSLOptions.VerifyMode := [sslvrfPeer]; SSLOptions.VerifyDepth := 10; end; IdHTTP.Get('https://www.google.com'); FreeAndNil(IdHTTP); FreeAndNil(IdSSLIOHandlerSocket); end;
Если попробовать выполнить выше приведенный код, то результат будет всегда такой: Error connecting with SSL.
Проблема тут в том, что в Indy используется OpenSSL, который не умеет работать с хранилищем сертификатов Windows и не может проверить подписан ли сертификат, полученный от сервера, одним из доверенных корневых сертификатов из этого хранилища.
Посмотрим в исходник модуля IdSSLOpenSSL. В нем есть такая функция:
function VerifyCallback(Ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl; var hcert: PX509; Certificate: TIdX509; hSSL: PSSL; IdSSLSocket: TIdSSLSocket; // str: String; VerifiedOK: Boolean; Depth: Integer; // Error: Integer; begin LockVerifyCB.Enter; try VerifiedOK := True; try hcert := IdSslX509StoreCtxGetCurrentCert(ctx); hSSL := IdSslX509StoreCtxGetAppData(ctx); Certificate := TIdX509.Create(hcert); if hSSL <> nil then begin IdSSLSocket := TIdSSLSocket(IdSslGetAppData(hSSL)); end else begin Result := Ok; exit; end; //Error := IdSslX509StoreCtxGetError(ctx); // Depth := IdSslX509StoreCtxGetErrorDepth(ctx); // str := Format('Certificate: %s', [Certificate.Subject.OneLine]); {Do not Localize} // str := IdSSLSocket.GetSessionIDAsString; // ShowMessage(str); if (IdSSLSocket.fParent is TIdSSLIOHandlerSocket) then begin VerifiedOK := TIdSSLIOHandlerSocket(IdSSLSocket.fParent).DoVerifyPeer(Certificate); end; if (IdSSLSocket.fParent is TIdServerIOHandlerSSL) then begin VerifiedOK := TIdServerIOHandlerSSL(IdSSLSocket.fParent).DoVerifyPeer(Certificate); end; if not ((Ok>0) and (IdSSLSocket.fSSLContext.VerifyDepth>=Depth)) then begin Ok := 0; {if Error = OPENSSL_X509_V_OK then begin Error := OPENSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG; end;} end; FreeAndNil(Certificate); except end; if VerifiedOK and (Ok > 0) then begin Result := 1; end else begin Result := 0; end; // Result := Ok; // testing finally LockVerifyCB.Leave; end; end;
Эта callback-функция вызывается при проверке сертификата библиотекой OpenSSL, и для того, чтобы не случилось ошибки “Error connecting with SSL” она должна возвращать значение Result = 1. Видно, что VerifyCallback возвращает Result = 1 только в том случае, когда Ok > 0. А параметр Ok для последнего сертификата в цепочке проверяемых всегда будет = 0, т.к. корневой сертификат, с помощью которого его можно проверить, не определен.
Таким образом, задача в следующем: проверить - подписан ли последний в цепочке сертификат одним из доверенных корневых (ROOT) сертификатов в хранилище Windows, и если подписан, то присвоить Ok = 1.
К сожалению, при проектировании Indy 9 было допущено много ошибок, и для того, чтобы решить поставленную задачу, придется вносить изменения в код модуля IdSSLOpenSSL, а затем перекомпилировать пакет Indy70.dpk.
Внесем следующие изменения:
1. Изменим определение события TVerifyPeerEvent на следующее:
TVerifyPeerEvent = function(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean of object;
2. Изменим определение метода DoVerifyPeer в классах TIdSSLIOHandlerSocket и TIdServerIOHandlerSSL на следующее:
function DoVerifyPeer(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean; virtual;
3. Изменим реализации метода DoVerifyPeer в классах TIdSSLIOHandlerSocket и TIdServerIOHandlerSSL на следующие:
function TIdServerIOHandlerSSL.DoVerifyPeer(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean; begin Result := True; if Assigned(fOnVerifyPeer) then Result := fOnVerifyPeer(Certificate, Error, Ok); end; function TIdSSLIOHandlerSocket.DoVerifyPeer(Certificate: TIdX509; Error: Integer; var Ok: Integer): Boolean; begin Result := True; if Assigned(fOnVerifyPeer) then Result := fOnVerifyPeer(Certificate, Error, Ok); end;
4. Добавим public-свойство X509 классу TIdX509:
property X509: PX509 read FX509;
5. Изменим функцию VerifyCallback на следующую:
function VerifyCallback(Ok: Integer; ctx: PX509_STORE_CTX): Integer; cdecl; var hcert: PX509; Certificate: TIdX509; hSSL: PSSL; IdSSLSocket: TIdSSLSocket; // str: String; VerifiedOK: Boolean; Depth: Integer; Error: Integer; begin LockVerifyCB.Enter; try VerifiedOK := True; try hcert := IdSslX509StoreCtxGetCurrentCert(ctx); hSSL := IdSslX509StoreCtxGetAppData(ctx); Certificate := TIdX509.Create(hcert); if hSSL <> nil then begin IdSSLSocket := TIdSSLSocket(IdSslGetAppData(hSSL)); end else begin Result := Ok; exit; end; Error := IdSslX509StoreCtxGetError(ctx); // Depth := IdSslX509StoreCtxGetErrorDepth(ctx); // str := Format('Certificate: %s', [Certificate.Subject.OneLine]); {Do not Localize} // str := IdSSLSocket.GetSessionIDAsString; // ShowMessage(str); if (IdSSLSocket.fParent is TIdSSLIOHandlerSocket) then begin VerifiedOK := TIdSSLIOHandlerSocket(IdSSLSocket.fParent).DoVerifyPeer(Certificate, Error, Ok); end; if (IdSSLSocket.fParent is TIdServerIOHandlerSSL) then begin VerifiedOK := TIdServerIOHandlerSSL(IdSSLSocket.fParent).DoVerifyPeer(Certificate, Error, Ok); end; if not ((Ok>0) and (IdSSLSocket.fSSLContext.VerifyDepth>=Depth)) then begin Ok := 0; {if Error = OPENSSL_X509_V_OK then begin Error := OPENSSL_X509_V_ERR_CERT_CHAIN_TOO_LONG; end;} end; FreeAndNil(Certificate); except end; if VerifiedOK and (Ok > 0) then begin Result := 1; end else begin Result := 0; end; // Result := Ok; // testing finally LockVerifyCB.Leave; end; end;
Теперь нужно перекомпилировать Indy70.dpk.
В результате наших изменений обработчик события OnVerifyPeer получает два дополнительных параметра: Error - код ошибки проверки сертификата (это может быть, например, OPENSSL_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) и параметр Ok, который можно изменить. Ниже приведена реализация обработчика:
function TForm1.SSLIOHandlerVerifyPeer(ThePeerCert: TIdX509; Error: Integer; var Ok: Integer): Boolean; var LBIO: PBIO; LLen: Integer; LStr: String; hStore: HCERTSTORE; pCert, pIssuer: PCCERT_CONTEXT; dwFlags: DWORD; begin //Result := False; // if (Ok = 0) then begin hStore := nil; pCert := nil; pIssuer := nil; LBIO := BIO_new(BIO_s_mem()); try i2d_X509_bio(LBIO, ThePeerCert.X509); LLen := BIO_ctrl_pending(LBIO); SetLength(LStr, LLen); BIO_read(LBIO, @LStr[1], LLen); BIO_free(LBIO); LBIO := nil; hStore := CertOpenSystemStore(0, 'ROOT'); if hStore <> nil then begin pCert := CertCreateCertificateContext( PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, @LStr[1], LLen); if pCert <> nil then begin dwFlags := CERT_STORE_REVOCATION_FLAG or CERT_STORE_SIGNATURE_FLAG or CERT_STORE_TIME_VALIDITY_FLAG; pIssuer := CertGetIssuerCertificateFromStore( hStore, pCert, nil, @dwFlags); if pIssuer <> nil then begin Ok := 1; CertFreeCertificateContext(pIssuer); pIssuer := nil; end; CertFreeCertificateContext(pCert); pCert := nil; end; CertCloseStore(hStore, 0); hStore := nil; end; finally if LBIO <> nil then BIO_free(LBIO); if pIssuer <> nil then CertFreeCertificateContext(pIssuer); if pCert <> nil then CertFreeCertificateContext(pCert); if hStore <> nil then CertCloseStore(hStore, 0); end; end; // Result := (Ok > 0); end;
В этом обработчике использованы функции OpenSSL и СryptoAPI, объявления которых можно найти в libeay32.pas и wcrypt2.pas. Собственно, из-за необходимости подключения этих модулей я и вынес проверку в обработчик события OnVerifyPeer, вместо того, чтобы сделать все в функции VerifyCallback.
Подключаем обработчик SSLIOHandlerVerifyPeer к IdSSLIOHandlerSocket и окончательно получаем загрузку страницы по протоколу https: с проверкой серверного сертификата:
var IdHTTP: TIdHTTP; IdSSLIOHandlerSocket: TIdSSLIOHandlerSocket; begin IdHTTP := TIdHTTP.Create(nil); IdSSLIOHandlerSocket := TIdSSLIOHandlerSocket.Create(nil); IdHTTP.IOHandler := IdSSLIOHandlerSocket; with IdSSLIOHandlerSocket do begin SSLOptions.Method := sslvSSLv23; SSLOptions.Mode := sslmClient; SSLOptions.VerifyMode := [sslvrfPeer]; SSLOptions.VerifyDepth := 10; OnVerifyPeer := SSLIOHandlerVerifyPeer; end; IdHTTP.Get('https://www.google.com'); FreeAndNil(IdHTTP); FreeAndNil(IdSSLIOHandlerSocket); end;