среда, 28 ноября 2012 г.

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

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

REPLACE INTO Table1 (FieldA) VALUES (:v)

При этом, использование REPLACE в примере выше предполагает, что построен уникальный индекс для поля FieldA.

Для того, чтобы загрузка в таблицу с индексом осуществлялась максимально быстро, необходимо, чтобы загружаемые данные были отсортированы по этому индексу.

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

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

четверг, 18 октября 2012 г.

среда, 19 сентября 2012 г.

Web Service в Visual Studio 10 и клиент на Delphi 7

Part 1. Веб-сервис (Web Service) в Visual Studio 10 называется "WCF Service Application".


Сразу после создания нового сервиса он содержит класс Service1 (в файле Service1.svc.cs), реализующий интерфейс IService1 (в файле IService1.cs), в котором объявлены два публичных демонстрационных метода GetData и GetDataUsingDataContract и композитный тип данных CompositeType. Веб-сервис можно сразу запустить (Debug). В окне броузера появится следующее сообщение: "Служба создана... необходимо создать клиент... Это можно сделать запустив программу svcutil.exe..."


В результате запуска svcutil.exe будет создан файл Service1.cs, который нужно добавить в тестовый проект (я сделал консольное тестовое приложение TestApplication1). В References тестового проекта нужно добавить System.Runtime.Serialization и System.ServiceModel. Пример вызова веб-службы см. на рисунке выше. 

Теперь немного про развертывание веб-службы на сервере IIS. Можно воспользоваться визардом (Publish), а можно сделать все "ручками":
  1. Установить .Net Framework 4.0, если он еще не установлен.
  2. Создать каталог на сайте, например C:\inetpub\wwwroot\Service1.
  3. Скопировать туда Service1.svc файлы Service1.svc, Web.Config и папку bin с файлами WcfService1.dll и WcfService1.pdb.
  4. Дать права "Чтение, Чтение и выполнение" пользователю IIS (IUSR)  на каталог Service1 и все скопированные в него файлы/подкаталоги.
  5. В диспетчере служб IIS в контекстном меню каталога Service1 выбрать "Преобразовать в приложение". В диалоге "Добавление приложения" нажать кнопку "Выбрать..." справа от "Пул приложений", выбрать пул приложений с версией среды .Net Framework соответствующей веб-сервису (ASP .NET v4.0), и нажать Ok. Каталог Service1 будет преобразован в приложение.
Иногда сразу же приложение не хочет работать в IIS (как случилось и у меня) с формулировкой типа "HTTP Error 404.3 - Not Found The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map." В таком случае помогает выполнение команды ServiceModelReg.exe -i. ServiceModelReg.exe находится в C:\Windows\Microsoft .NET\Framework\v4.0.30319. Там же находится aspnet_regiis.exe. Он помогает в том случае, если .Net 4 вдруг оказывается не зарегистрированным (см. картинку ниже) и по этой причине веб-сервис не запускается.

Part 2. Создание клиента для веб-сервиса на Delphi 7.

1. Сервис должен использовать привязку basicHttpBinding, а не wsHttpBinding. Регулируется это в Web.Config. Для его правки в Visual Studio 10 можно воспользоваться утилитой WCF Service Configuration Editor (можно выбрать пункт Edit WCF Configuration из контекстного меню для Web.Config).



2. Необходимо обновить утилиту импорта WSDL. Взять ее можно здесь: http://cc.embarcadero.com/item/24535. Пользоваться ею просто, например так:

    WSDLImp.exe -P http://localhost:2708/Service1.svc?wsdl

В результате будет создан файл Service1.pas, который нужно подключить к проекту клиента в Delphi 7. Пользоваться очень просто:

uses Service1.pas;

var
  Client: IWcfService1;
  ws: WideString;
begin
  Client := GetIWcfService1(True, 'http://localhost:2708/Service1.svc?wsdl');
  ws := Client.GetDate(10);
  MessageBoxW(0, PWideChar(ws), 'Result', MB_ICONINFORMATION);
end;

3. Delphi 7 BUG. При попытке обратиться к веб-сервису из клиента, написанного на Delphi 7, может возникать Access Violation (см. картинку ниже). Возникает эта ошибка не везде. У меня на Windows 7 ее нет, а на Windows Server 2008 - проявилась.


Фикс для этой ошибки находится в том же архиве, что и новая утилита импорта WSDL (http://cc.embarcadero.com/item/24535). НО НЕ ТОРОПИТЕСЬ заменять ваши исходники SOAP на файлы из архива. Этот "патч" что-то лечит, а что-то калечит :-) У меня при его использовании перестали передаваться в веб-сервис строковые параметры вызова (в значениях параметров всегда содержался null). Для того, чтобы справиться с ошибкой Access Violation, нам необходимы только два файла из этого патча: PrivateHeap.pas и Rio.pas. Их (только их!) нужно добавить в Delphi-проект клиента веб-сервиса.

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

вторник, 11 сентября 2012 г.

SSL HTTP сервер на Indy 9 (Delphi 7)

HTTP-сервер в Indy 9 представлен компонентом TIdHTTPServer. Чтобы включить использование SSL (HTTPS), нужно у объекта TIdHTTPServer свойство IOHandler связать с объектом типа TIdServerIOHandlerSSL, в котором в поле CertFile прописать путь к файлу сертификата, а в KeyFile - путь к файлу с приватным ключом.

Файл сертификата имеет примерно такое содержимое:

Файл с приватным ключом выглядит примерно так:

Но что делать, если необходимо, чтобы сертификат и ключ были "вшиты" в приложение? Я использую следующую технику - после загрузки и инициализации библиотеки ssleay32.dll подменяю указатели на функции IdSslCtxUseCertificateFile и IdSslCtxUsePrivateKeyFile (они объявлены в IdSSLOpenSSLHeaders) на мои реализации этих функций, которые читают сертификат и ключ не из файлов, а из памяти.

unit uOpenSSL;

(*
Copyright 2012 Coolsoftware. http://blog.coolsoftware.ru/
You can freely use this code for your needs.
Please, don't remove this copyright.
*)

interface

uses Windows, SysUtils, IdSSLOpenSSL, IdSSLOpenSSLHeaders;

function MySslCtxUsePrivateKeyFile(ctx: PSSL_CTX; const _file: PChar; _type: Integer): Integer cdecl;
function MySslCtxUseCertificateFile(ctx: PSSL_CTX; const _file: PChar; _type: Integer): Integer cdecl;

type
  PBIO            = Pointer;
  PPBIO           =^PBIO;
  PBIO_METHOD     = Pointer;
  PRSA         = Pointer;

var
  MySslCtxUsePrivateKey : function(ctx: PSSL_CTX; pkey: PEVP_PKEY): 
    Integer cdecl = nil;
  MySslCtxUseCertificate : function(ctx: PSSL_CTX; x: PX509): 
    Integer cdecl = nil;

  EVP_PKEY_new : function(): PEVP_PKEY cdecl = nil;
  EVP_PKEY_free : procedure(pkey: PEVP_PKEY) cdecl = nil;

  EVP_PKEY_set1_RSA : function(pkey: PEVP_PKEY; key: PRSA) : 
    Integer cdecl = nil;

  BIO_s_mem : function () : PBIO_METHOD cdecl = nil;
  BIO_new : function(t: PBIO_METHOD): PBIO cdecl = nil;
  BIO_free : function(a: PBIO): Integer cdecl = nil;
 BIO_puts : function(bp: PBIO; buf: PChar): Integer cdecl = nil;

  RSA_new: function (): pRSA; cdecl = nil;
  RSA_free: procedure (r: pRSA); cdecl = nil;
  RSA_public_encrypt: function (flen: integer; from: PChar; _to: PChar;
    rsa: pRSA; padding: integer): integer; cdecl = nil;
  RSA_public_decrypt: function (flen: integer; from: PChar; _to: PChar;
    rsa: pRSA; padding: integer): integer; cdecl;

  d2i_RSAPrivateKey : function(a: Pointer; pp: Pointer; length: Integer): 
    PRSA cdecl = nil;

  PEM_ASN1_read_bio : function(d2: Pointer; name: PChar; bp: PBIO; x: Pointer;
    cb: Pointer; u: Pointer): PChar cdecl = nil;
  PEM_read_bio_X509 : function(bp: PBIO; x: Pointer; cb: Pointer; u: Pointer): 
    PX509 cdecl = nil;
  PEM_read_bio_RSAPublicKey: function (bp: pBIO; x: pRSA;
    cb: Pointer; u: pointer): pRSA; cdecl = nil;

implementation

uses SyncObjs;

const
  SSL_DLL_name       = 'ssleay32.dll';  {Do not localize}
  SSLCLIB_DLL_name   = 'libeay32.dll';  {Do not localize}

var
  hIdSSL    : Integer = 0;
  hIdCrypto : Integer = 0;

function LoadFunction(FceName:String):Pointer;
begin
  FceName := FceName+#0;
  Result := GetProcAddress(hIdSSL, @FceName[1]);
//  if (Result = nil) then ShowMessage('Error loading: ' + FceName);  {Do not localize}
end;

function LoadFunctionCLib(FceName:String):Pointer;
begin
  FceName := FceName+#0;
  Result := GetProcAddress(hIdCrypto, @FceName[1]);
//  if (Result = nil) then ShowMessage('Error loading: ' + FceName);  {Do not localize}
end;

const
  fn_SSL_CTX_use_RSAPrivateKey = 'SSL_CTX_use_RSAPrivateKey';  {Do not localize}
  fn_SSL_CTX_use_PrivateKey = 'SSL_CTX_use_PrivateKey';  {Do not localize}
  fn_SSL_CTX_use_certificate = 'SSL_CTX_use_certificate';  {Do not localize}

  fn_EVP_PKEY_new = 'EVP_PKEY_new';  {Do not localize}
  fn_EVP_PKEY_free = 'EVP_PKEY_free';  {Do not localize}

  fn_EVP_PKEY_set1_RSA = 'EVP_PKEY_set1_RSA';

  fn_BIO_s_mem = 'BIO_s_mem';  {Do not localize}
  fn_BIO_new = 'BIO_new';  {Do not localize}
  fn_BIO_free = 'BIO_free';  {Do not localize}
  fn_BIO_puts = 'BIO_puts';  {Do not localize}

  fn_RSA_new = 'RSA_new';  {Do not localize}
  fn_RSA_free = 'RSA_free';  {Do not localize}
  fn_RSA_public_encrypt = 'RSA_public_encrypt';  {Do not localize}
  fn_RSA_public_decrypt = 'RSA_public_decrypt';  {Do not localize}

  fn_d2i_RSAPrivateKey = 'd2i_RSAPrivateKey';  {Do not localize}

  fn_PEM_ASN1_read_bio = 'PEM_ASN1_read_bio';  {Do not localize}
  fn_PEM_read_bio_X509 = 'PEM_read_bio_X509';  {Do not localize}
  fn_PEM_read_bio_RSAPublicKey = 'PEM_read_bio_RSAPublicKey';  {Do not localize}

var
  c_PrivateKeyPassword: PChar = 'put the password of your private key here';
  c_PrivateKey: PChar = 'put your private key here';
  c_Certificate: PChar = 'put your certificate here';

  LockPassCB: TCriticalSection;

function MyPasswordCallback(buf:PChar; size:Integer; rwflag:Integer; 
  userdata: Pointer):Integer; cdecl;
begin
  LockPassCB.Acquire;
  try
    size := StrLen(c_PrivateKeyPassword);
    StrCopy(buf, c_PrivateKeyPassword);
    Result := size;
  finally
    LockPassCB.Release;
  end;
end;

function MySslCtxUsePrivateKeyFile(ctx: PSSL_CTX; const _file: PChar; 
  _type: Integer):Integer cdecl;
var
  pk: PEVP_PKEY;
  rsakey: PRSA;
  pMemBIO: PBIO;
begin
    pk := EVP_PKEY_new();
    try
      pMemBIO := BIO_new(BIO_s_mem());
      try
        BIO_puts(pMemBIO, c_PrivateKey);
        rsakey := PEM_ASN1_read_bio(
          @d2i_RSAPrivateKey, OPENSSL_PEM_STRING_RSA, pMemBIO, nil,
          @MyPasswordCallback, nil);
      finally
        BIO_free(pMemBIO);
      end;
      EVP_PKEY_set1_RSA(pk, rsakey);
      Result := MySslCtxUsePrivateKey(ctx, pk);
    finally
      EVP_PKEY_free(pk);
    end;
end;

function MySslCtxUseCertificateFile(ctx: PSSL_CTX; const _file: PChar; 
  _type: Integer):Integer cdecl;
var
  pCert: PX509;
  pMemBIO: PBIO;
begin
    pMemBIO := BIO_new(BIO_s_mem());
    try
      BIO_puts(pMemBIO, c_Certificate);
      pCert := PEM_read_bio_X509(pMemBIO, nil,
        @MyPasswordCallback, nil);
    finally
      BIO_free(pMemBIO);
    end;
    Result := MySslCtxUseCertificate(ctx, pCert);
end;

var
  ctx: TIdSSLContext;

initialization
  LockPassCB := TCriticalSection.Create;
    
  //this will load the SSL library
  ctx := TIdSSLContext.Create;
  try

    hIdCrypto := GetModuleHandle(SSLCLIB_DLL_name);
    hIdSSL := GetModuleHandle(SSL_DLL_name);

    @MySslCtxUsePrivateKey := LoadFunction(fn_SSL_CTX_use_PrivateKey);
    @MySslCtxUseCertificate := LoadFunction(fn_SSL_CTX_use_certificate);

    @EVP_PKEY_new := LoadFunctionCLib(fn_EVP_PKEY_new);
    @EVP_PKEY_free := LoadFunctionCLib(fn_EVP_PKEY_free);

    @EVP_PKEY_set1_RSA := LoadFunctionCLib(fn_EVP_PKEY_set1_RSA);

    @BIO_s_mem := LoadFunctionCLib(fn_BIO_s_mem);
    @BIO_new := LoadFunctionCLib(fn_BIO_new);
    @BIO_free := LoadFunctionCLib(fn_BIO_free);
    @BIO_puts := LoadFunctionCLib(fn_BIO_puts);

    @RSA_new := LoadFunctionCLib(fn_RSA_new);
    @RSA_free := LoadFunctionCLib(fn_RSA_free);
    @RSA_public_encrypt := LoadFunctionCLib(fn_RSA_public_encrypt);
    @RSA_public_decrypt := LoadFunctionCLib(fn_RSA_public_decrypt);

    @d2i_RSAPrivateKey := LoadFunctionCLib(fn_d2i_RSAPrivateKey);

    @PEM_ASN1_read_bio := LoadFunctionCLib(fn_PEM_ASN1_read_bio);
    @PEM_read_bio_X509 := LoadFunctionCLib(fn_PEM_read_bio_X509);
    @PEM_read_bio_RSAPublicKey := LoadFunctionCLib(fn_PEM_read_bio_RSAPublicKey);

    IdSslCtxUsePrivateKeyFile := MySslCtxUsePrivateKeyFile;
    IdSslCtxUseCertificateFile := MySslCtxUseCertificateFile;

  finally
    ctx.Free;
  end;

finalization
  LockPassCB.Free;
end.

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

среда, 5 сентября 2012 г.

Обновился VHashedStringList

В GitHub я выложил обновленную версию VHashedStringList. Сделаны следующие добавления/изменения:
  1. Добавлено свойство OwnsObjects. Оно позволяет объекту TVHashedStringList контролировать выделение памяти для индексированных объектов - при удалении объекта из списка или при удалении самого списка автоматически освобождается память, отведенная под указанные объекты. (Это аналог одноименного свойства OwnsObjects у TObjectList).
  2. Добавлено свойство ValueObjects. Позволяет обращаться к объекту, хранящемуся в списке, по значению соответствующей ему строки.
  3. При удалении элемента списка hash-индекс остается корректным, т.е. не требуется его полное перестроение перед поиском элементов.
  4. Добавлено свойство AutoUpdateHash (по-умолчанию его значение = True), которое позволяет на время отключать авто-обновление hash-индекса.
===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

вторник, 4 сентября 2012 г.

GitHub and TortoieseGit: error code 128 git did not exit cleanly

У меня 2 аккаунта на GitHub. И несколько разных репозиториев для разных проектов - некторые должны заливаться на GitHub под первым аккаунтом, другие - под вторым. Я использую Git-клиент TortoiseGit. И столкнулся со странной проблемой - под первым аккаунтом все хорошо работает, а под вторым Push обламывается в формулировкой "error code 128 git did not exit cleanly". Оказалось, что иногда для второго аккаунта используется ssh-key от первого. Лечится проблема удалением ключей (Remove Key) из Pagent (PuTTY authentication client).

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

пятница, 31 августа 2012 г.

Вызов консольного приложения из программы на Delphi

Для вызова внешнего приложения из программы на Delphi обычно используется функция WinAPI CreateProcess. Но как получить то, что это приложение выводит в консоль, и, например, отобразить в логе программы?  Варианта два. Первый: пере-направить вывод консольного приложения в файл и потом прочитать этот файл. Однако, создавать временный файл не охота. А кроме того, такой способ не всегда хорошо работает. Есть другой способ - использовать пайпы (pipes). Ниже приведен код процедуры RunDosCommand на Delphi, который использует этот способ. Весь вывод на консоль записывается в AMemo: TMemo.

function RunDosCommand(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 <> nil then
  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
    if not CreatePipe(ReadPipe, WritePipe, @Security, 0) then
    begin
      ReadPipe := 0;
      WritePipe := 0;
    end;
  end;
  if (ReadPipe <> 0) or (WritePipe <> 0) then
  begin
    if AMemo <> nil then
    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 <> nil then
      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;
    end else
    if AMemo <> nil then
    begin
      AMemo.SelStart := Length(AMemo.Text);
      AMemo.SelText :='Command failed.'#13#10;
      AMemo.SelStart := Length(AMemo.Text);
      Application.ProcessMessages;
    end;
    if AMemo <> nil then
    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

воскресенье, 26 августа 2012 г.

Преобразование _TCHAR в QString

Для преобразования _TCHAR в QString и обратно (QString в _TCHAR) я объявил в tqchar.h пару простых макросов: _TQSTRING_TQCHAR.

  1. #ifndef TQCHAR_H
  2. #define TQCHAR_H
  3.  
  4. #include <tchar.h>
  5.  
  6. #ifdef _UNICODE
  7.  
  8. #define _TQCHAR(x)        (wchar_t*) x.utf16()
  9. #define _TQSTRING(x)    QString::fromUtf16((x))
  10.  
  11. #else
  12.  
  13. #define _TQCHAR(x)        x.toLocal8Bit().constData()
  14. #define _TQSTRING(x)    QString::fromLocal8Bit((x))
  15.  
  16. #endif
  17.  
  18. #endif //TQCHAR_H

Кстати, нашел хороший плагин для Visual Studio чтобы копировать код в HTML с подсветкой синтаксиса: Copy As HTML.

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

четверг, 23 августа 2012 г.

ASProtect 1.65 - Delphi XE2 Compatible

Наконец-то появилась версия ASProtect 1.65 в которой заявлена совместимость с Delphi XE и XE2. Я попробовал - созданный в Delphi XE2 и запакованный новым "аспром" exe действительно запускается и работает. Ошибок пока не замечено.

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

VerQueryValue Access Violation

Столкнулся с интересным багом при получении информации о версии из ресурсов (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. Реализация приведена ниже.

  1. void ReadVersion(_TCHAR * szVer, int cbVer, const _TCHAR * szEntry)
  2. {
  3.     _tcscpy(szVer, _T("\n"));
  4.     HRSRC hVersion = FindResource(
  5.         NULL,
  6.         MAKEINTRESOURCE(VS_VERSION_INFO),
  7.         RT_VERSION);
  8.     if (hVersion)  
  9.     {
  10.         DWORD dwSize = SizeofResource(NULL, hVersion);
  11.         HGLOBAL hGlobal = LoadResource(NULL, hVersion);
  12.         if (hGlobal != NULL)  
  13.         {
  14.             LPVOID versionInfoGlobal  = LockResource(hGlobal);
  15.             if (versionInfoGlobal != NULL)
  16.             {
  17.                 LPVOID versionInfoLocal = LocalAlloc(LMEM_FIXED, dwSize);
  18.                 CopyMemory(versionInfoLocal, versionInfoGlobal, dwSize);
  19.  
  20.                 DWORD vLen, langD;
  21.                 BOOL retVal;
  22.  
  23.                 LPVOID retbuf = NULL;
  24.  
  25. #ifdef _UNICODE
  26.                 const _TCHAR * entry = szEntry;
  27. #else                    
  28.                 WCHAR entry[64]; //I assume here that length of szEntry string is less than 64
  29.                 MultiByteToWideChar(CP_ACP, 0, szEntry, -1, entry, 64);
  30. #endif
  31.  
  32.                 WCHAR fileEntry[1024];
  33.  
  34.                 _swprintf(fileEntry, L"\\VarFileInfo\\Translation");
  35.                 retVal = VerQueryValueW(versionInfoLocal, fileEntry, &retbuf, (UINT*)&vLen);
  36.                 if (retVal && vLen == 4)
  37.                 {
  38.                     memcpy(&langD, retbuf, 4);            
  39.                     _swprintf(fileEntry, L"\\StringFileInfo\\%02X%02X%02X%02X\\%s",
  40.                             (langD & 0xff00)>>8,langD & 0xff,(langD & 0xff000000)>>24,
  41.                             (langD & 0xff0000)>>16, entry);            
  42.                 }
  43.                 else
  44.                     _swprintf(fileEntry, L"\\StringFileInfo\\%04X04B0\\%s",
  45.                     GetUserDefaultLangID(), entry);
  46.  
  47.                 retVal = VerQueryValueW(versionInfoLocal, fileEntry, &retbuf, (UINT*)&vLen);
  48.                 if (retVal)
  49.                 {
  50. #ifdef _UNICODE
  51.                     _sntprintf(szVer, cbVer-1, _T(" %s\n"), (_TCHAR*)retbuf);
  52.                     szVer[cbVer-1] = '\0';
  53. #else
  54.                     szVer[0] = '\0';
  55.                     int n = WideCharToMultiByte(CP_ACP, 0, (LPWSTR)retbuf, -1, szVer+1, cbVer-2, NULL, NULL);
  56.                     if (n > 0)
  57.                     {
  58.                         szVer[0] = ' ';
  59.                         szVer[n] = '\n';
  60.                         szVer[n+1] = '\0';
  61.                     }
  62. #endif
  63.                 }
  64.  
  65.                 LocalFree(versionInfoLocal);
  66.             }
  67.             FreeResource(hGlobal);
  68.         }
  69.     }
  70. }

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

среда, 18 июля 2012 г.

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

воскресенье, 15 июля 2012 г.

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

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



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

четверг, 5 июля 2012 г.

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

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

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

воскресенье, 1 апреля 2012 г.

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

вторник, 13 марта 2012 г.

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

вторник, 14 февраля 2012 г.

Много читателей и один писатель

В много-поточном приложении довольно часто возникает необходимость синхронизировать обращение к разделяемому ресурсу, который могут одновременно читать несколько потоков "читателей", а производить запись в этот ресурс может только один поток "писатель". Когда читатели читают данные, то писатель не может писать и должен ждать, пока все читатели не закончат чтение. Когда писатель пишет, то все читатели должны ждать, пока он не запишет все данные и не освободит ресурс.

Ниже приведена моя реализация "читателя" и "писателя". В этой реализации есть допущение о том, что нам известно максимальное количество читателей 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

четверг, 9 февраля 2012 г.

Свой ГСЧ

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