Парсеры

Связь событийного парсера, типа SAX-парсера, с вызывающим приложением обычно осуществляется следующим образом: нужно создать экземпляр класса, реализующего Callback-интерфейс и сообщить о нем парсеру:

class MyCallback : public Callback
{
virtual void doc_start() { printf("Start of document"); }
virtual void doc_end() { printf("End of document"); }
// ...
};
MyCallback obj;
Parser parser;
parser.parse("c:\\test.xml", &obj);

Вся “сила” событийного SAX-парсера заключается в том, что нет необходимости держать в памяти весь XML-документ (или его DOM-модель).

А как может выглядеть парсер, который бы также не требовал загрузки всего документа в память, на не-объектно ориентированном языке программирования, например, на C ? Оказывается это может выглядеть достаточно просто и элегантно:

struct ParseStatus
{
int currentPos; //current position in file
int tagName[MAX_TAG_NAME_SIZE]; //current tag name
// ...
};
ParseStatus status;
if (parse_begin("c:\\test.xml", &status))
{
while (parse_next_tag(&status))
{
printf("Next tag: %s", status.tagName);
}
}

===

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

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

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

REPLACE INTO Table1 (FieldA) VALUES (:v)

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

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

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

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

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

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

Обновился VHashedStringList

В GitHub я выложил обновленную версию VHashedStringList. Сделаны следующие добавления/изменения:

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

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

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

Вызов консольного приложения из программы на 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

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

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

#ifndef TQCHAR_H
#define TQCHAR_H

#include <tchar.h>

#ifdef _UNICODE

#define _TQCHAR(x) (wchar_t*) x.utf16()
#define _TQSTRING(x) QString::fromUtf16((x))

#else

#define _TQCHAR(x) x.toLocal8Bit().constData()
#define _TQSTRING(x) QString::fromLocal8Bit((x))

#endif

#endif //TQCHAR_H

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

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

ASProtect 1.65 - Delphi XE2 Compatible

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

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