В много-поточном приложении довольно часто возникает необходимость синхронизировать обращение к разделяемому ресурсу, который могут одновременно читать несколько потоков “читателей”, а производить запись в этот ресурс может только один поток “писатель”. Когда читатели читают данные, то писатель не может писать и должен ждать, пока все читатели не закончат чтение. Когда писатель пишет, то все читатели должны ждать, пока он не запишет все данные и не освободит ресурс.
Ниже приведена моя реализация “читателя” и “писателя”. В этой реализации есть допущение о том, что нам известно максимальное количество читателей 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;
Затем к тегу нужно добавить: onload=”prettyPrint()”
Использовать так:
<pre class="prettyprint"> // put your code here </pre>
Можно указать явным образом язык, например lang-html:
<pre class="prettyprint lang-html"> The lang-* class specifies the language file extensions. File extensions supported by default include "bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm", "html", "java", "js", "m", "mxml", "perl", "pl", "pm", "py", "rb", "sh", "xhtml", "xml", "xsl". </pre>
PS. Гугловский Highlighter похоже Delphi не поддерживает. Поэтому я его удалил, а вместо него подключил highlight.js. Сделал я это так:
1. Скачал HIGHLIGHT.JS (при этом выбрал нужный мне Delphi). 2. Файлы highlight.js, highlight.pack.js и стили styles/*.css из скачанного архива разместил в google apps. 3. В шаблоне Blogger-а (Дизайн->Изменить HTML) перед добавил:
1. Если при попытке обратиться к динамической странице (ASP) на вашем сервере IIS выдается ошибка “HTTP Error 404 - File or Directory not found” (при этом статические страницы нормально выдаются), то нужно включить “Active Server Pages” в настройках “Web Service Extensions”.
2. При обращении из ASP к базе Access (MDB): если SELECT выполняется нормально, а при попытке добавить, удалить или отредактировать записи выдаются сообщения “В операции должен использоваться обновляемый запрос.”, “Невозможно удаление записей из указанных таблиц.”, то нужно дать права пользователю, от имени которого выполняется сайт (IUSR или что-то похожее), на файл базы MDB и на каталог, в котором этот файл расположен (права на создание/удаление/изменение).
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
64-битного драйвера MDB в природе не существует, поэтому есть проблема доступа к MDB из 64-битных приложений. В частности из ASP-приложений под IIS. Проблема решается разрешением 32-битных приложений в настройках IIS:
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
invalid command-line parameter: Files. Hint: use ‘@foo’ to launch a virtual device named ‘foo’.
please use -help for more information
Причина - эмулятор андроида не любит длинные пути к SDK (у меня: c:\program files (x86)\Android\android-sdk). Заработало, когда заменил путь на короткий: C:\Progra~2\Android\android-sdk.
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru