Qt и Visual Studio 2008

Чтобы использовать Qt совместно со средой разработки Visual Studio нужно выполнить следующие шаги:

1. Скачать и установить Qt for Visual Studio 2008: http://qt.nokia.com/downloads/windows-cpp-vs2008 (каталог установки не должен содержать пробелов, например это может быть C:\qt\4.8.2)

2. Скачать и установить Visual Studio Add-in: http://qt.nokia.com/downloads/visual-studio-add-in

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:
  • Запустить Visual Studio 2008 Command Prompt
  • Выполнить команду: cd  C:\qt\4.8.2
  • Выполнить команду: configure -platform win32-msvc2008 -static
  • Выполнить команду: nmake

Сборка Qt у меня заняла несколько часов.

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) следующим образом:

заменить строки

  QMAKE_CFLAGS_RELEASE    = -O2 -MD

  QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi

  QMAKE_CFLAGS_DEBUG      = -Zi -MDd

на

  QMAKE_CFLAGS_RELEASE    = -O2 -MT

  QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MT -Zi

  QMAKE_CFLAGS_DEBUG      = -Zi -MTd

===

Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Зависимость от Microsoft Visual C++ Runtime

Чтобы убрать зависимость от Microsoft Visual C++ Runtime нужно в свойствах проекта выбрать Runtime Library: Multi-threaded (/MT). Получающийся при компиляции exe/dll будет несколько больше, зато не нужно будет устанавливать Visual C++ Redistributable Package.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Проверка серверного сертификата и Enhanced Security

В моем посте про проверку серверного сертификата SSL в Delphi 7 проверка этого самого сертификата выполнялась вызовом функции CryptoAPI CertGetIssuerCertificateFromStore. Я столкнулся на практике со следующим: оказывается, что на эту функцию оказывает влияние Internet Explorer Enhanced Security (по умолчанию оно включено на всех серверах Windows 2003). Т.е. до тех пор, пока хост не будет включен в список доверенных, его сертификат тоже может не проходить проверку. Как-то так (слишком глубоко эту тему не копал). В общем, если на одном компьютере проверка сертификата работает, а на другом - нет (ошибка “Error connecting with SSL”), то следует проверить - включен ли IE Enhanced Security, и если да, то либо отключить его, либо добавить хост в список доверенных.

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Delphi XE 2 & ASProtect

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

Coolsoftware on github

Завел аккаунт в github: https://github.com/coolsoftware/

Это не первый мой аккаунт в github, поэтому потребовалось как-то организовывать одновременный доступ к нему из под разных учеток, с разными SSHKey. По этому поводу маленький “трик” - как я все это сделал:

1. При установке Git for Windows создает такой ярлык для Git Bash:

Как мы видим текущий каталог Git Bash - %HOMEDRIVE%%HOMEPATH%. В нем хранятся все настройки git, каталог .ssh с SSHKey и проекты обычно тоже там.

Я создал в %HOMEDRIVE%%HOMEPATH% каталог Coolsoftware и в нем git.cmd следующего содержания:

@set HOME=%HOMEDRIVE%%HOMEPATH%\Coolsoftware

C:\Windows\SysWOW64\cmd.exe /c “”C:\Program Files (x86)\Git\bin\sh.exe” –login -i”

Затем создал ярлык на этот git.cmd и вынес его на рабочий стол, обозвав “Git Bash - Coolsoftware”. Теперь при его запуске текущий каталог пользователя в “баше” %HOMEDRIVE%%HOMEPATH%\Coolsoftware, а при запуске старого ярлыка “Git Bash” текущий каталог %HOMEDRIVE%%HOMEPATH%. Таким образом получилось два “баша”, в каждом - свои настройки.

Далее запустил “Git Bash - Coolsoftware” произвел установку github в соответствии с инструкцией и выложил проект VHashedStringList - оптимизированный список строк, доступ к элементам которого организован с помощью хеш-индекса.

TVHashedStringList - аналог стандартного THashedStringList (фактически, он сделан на его основе, хотя наследуется не от него, а от TStringList).

Существенное отличие от THashedStringList заключается в том, что оптимизированы операции перестроения индекса при изменении списка.

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

Например, в следующем примере полное перестроение индекса будет произведено 3 раза:

procedure ChangeList(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, как можно было бы предположить.

UPD. LockLib - классы C++ для блокировки разделяемых ресурсов: http://blog.coolsoftware.ru/2013/12/locklib.html

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Свой ГСЧ

Стандартный генератор случайных чисел (ГСЧ) в 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

Вызов кода на C# из программы на Delphi

Обычно решают противоположную задачу - вызов внешних модулей (DLL, COM), написанных на C++, Delphi и т.п., из программы на C#. У меня же возникла необходимость вызвать из программы написанной на Delphi функцию библиотеки, написанной на C#. В этом посте я расскажу о том, как это делается.

Итак, нужно сделать на C# COM объект, к которому затем мы сможем обращаться из нашей программы на Delphi.

  1. В Visual Studio создаем новый проект New/Project/Visual C#/Class Library.

  2. В свойствах проекта на вкладке Application жмем кнопку “Assembly Information” и отмечаем “Make assembly COM-visible”.

  3. В свойствах проекта на вкладке Build отмечаем Register for COM interop.

  4. Для регистрации библиотеки вместо regsvr32 нужно использовать regasm. Вызов будет типа такого:

    regasm.exe vcomclasslibrary.dll /tlb:vcomclasslibrary.tlb

    regasm.exe обычно расположен тут:

    c:\Windows\Microsoft.NET\Framework\v2.0.50727\

  5. Код на C#:


    using System;
    using System.Drawing;
    using System.Runtime.InteropServices;
    namespace VCOMClassLibrary
    {
    [ComVisible(true)]
    public interface IVCOMInterface
    {
    string LoadImageFromStream(System.IO.Stream stream);
    }
    [ComVisible(true), ClassInterface(ClassInterfaceType.None)]
    public class VCOMClass : IVCOMInterface
    {
    public string LoadImageFromStream(System.IO.Stream stream)
    {
    Image img = Image.FromStream(stream);
    //put code to load image here
    return "OK";
    }
    }
    }
  6. В проекте на Delphi импортируем VCOMClassLibrary (Project/Import Type Library). Пример вызова (в котором я заодно показал как передавать Stream):


    procedure TForm1.Button1Click(Sender: TObject);
    var
    cls: IVCOMInterface;
    Stream: IStream;
    FileStream: TFileStream;
    begin
    cls := CreateComObject(CLASS_VCOMClass) as IVCOMInterface;
    FileStream := TFileStream.Create('c:\temp\1.jpg', fmOpenRead);
    Stream := TStreamAdapter.Create(FileStream, soOwned) as IStream;
    cls.LoadImageFromStream(Stream);
    FileStream.Free;
    end;

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Быстрая загрузка данных в Sqlite

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

Проверка серверного сертификата SSL в Delphi

Понадобилось мне осуществить проверку серверного сертификата в моей программе на 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;

PS. Проверка серверного сертификата и Enhanced Security

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru