Связь событийного парсера, типа SAX-парсера, с вызывающим приложением обычно осуществляется следующим образом: нужно создать экземпляр класса, реализующего Callback-интерфейс и сообщить о нем парсеру:
classMyCallback : public Callback { virtualvoiddoc_start(){ printf("Start of document"); } virtualvoiddoc_end(){ printf("End of document"); } // ... }; MyCallback obj; Parser parser; parser.parse("c:\\test.xml", &obj);
Вся “сила” событийного SAX-парсера заключается в том, что нет необходимости держать в памяти весь XML-документ (или его DOM-модель).
А как может выглядеть парсер, который бы также не требовал загрузки всего документа в память, на не-объектно ориентированном языке программирования, например, на C ? Оказывается это может выглядеть достаточно просто и элегантно:
structParseStatus { 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
В предыдущем посте на эту тему я не упомянул про важный фактор, который сильно влияет на производительность, - это наличие у таблицы индекса. Индекс способен довольно существенно замедлить процедуру вставки, поэтому лучше если данные будут загружаться в таблицу без индексов. Но иногда, индекс необходим. Например, если мы хотим загрузить в таблицу только отсутствующие данные, то это делается с пом. SQL-конструкции REPLACE:
REPLACE INTO Table1 (FieldA) VALUES (:v)
При этом, использование REPLACE в примере выше предполагает, что построен уникальный индекс для поля FieldA.
Для того, чтобы загрузка в таблицу с индексом осуществлялась максимально быстро, необходимо, чтобы загружаемые данные были отсортированы по этому индексу.
Это нужно для того, чтобы минимизировать количество операций обращения к диску при поиске значения в индексе. Когда значения в индексе располагаются рядом, то эффективно работает буфер чтения. Выигрыш в скорости при вставке отсортированных данных по сравнению со вставкой неупорядоченных данных на больших объемах может оказаться в десятки раз.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
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), а можно сделать все "ручками":
Создать каталог на сайте, например C:\inetpub\wwwroot\Service1.
Скопировать туда Service1.svc файлы Service1.svc, Web.Config и папку bin с файлами WcfService1.dll и WcfService1.pdb.
Дать права “Чтение, Чтение и выполнение” пользователю IIS (IUSR) на каталог Service1 и все скопированные в него файлы/подкаталоги.
В диспетчере служб 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).
В результате будет создан файл 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
HTTP-сервер в Indy 9 представлен компонентом TIdHTTPServer. Чтобы включить использование SSL (HTTPS), нужно у объекта TIdHTTPServer свойство IOHandler связать с объектом типа TIdServerIOHandlerSSL, в котором в поле CertFile прописать путь к файлу сертификата, а в KeyFile - путь к файлу с приватным ключом.
Файл сертификата имеет примерно такое содержимое:
Файл с приватным ключом выглядит примерно так:
Но что делать, если необходимо, чтобы сертификат и ключ были “вшиты” в приложение? Я использую следующую технику - после загрузки и инициализации библиотеки ssleay32.dll подменяю указатели на функции IdSslCtxUseCertificateFile и IdSslCtxUsePrivateKeyFile (они объявлены в IdSSLOpenSSLHeaders) на мои реализации этих функций, которые читают сертификат и ключ не из файлов, а из памяти.
В GitHub я выложил обновленную версию VHashedStringList. Сделаны следующие добавления/изменения:
Добавлено свойство OwnsObjects. Оно позволяет объекту TVHashedStringList контролировать выделение памяти для индексированных объектов - при удалении объекта из списка или при удалении самого списка автоматически освобождается память, отведенная под указанные объекты. (Это аналог одноименного свойства OwnsObjects у TObjectList).
Добавлено свойство ValueObjects. Позволяет обращаться к объекту, хранящемуся в списке, по значению соответствующей ему строки.
При удалении элемента списка hash-индекс остается корректным, т.е. не требуется его полное перестроение перед поиском элементов.
Добавлено свойство AutoUpdateHash (по-умолчанию его значение = True), которое позволяет на время отключать авто-обновление hash-индекса.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
У меня 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 обычно используется функция WinAPI CreateProcess. Но как получить то, что это приложение выводит в консоль, и, например, отобразить в логе программы? Варианта два. Первый: пере-направить вывод консольного приложения в файл и потом прочитать этот файл. Однако, создавать временный файл не охота. А кроме того, такой способ не всегда хорошо работает. Есть другой способ - использовать пайпы (pipes). Ниже приведен код процедуры RunDosCommand на Delphi, который использует этот способ. Весь вывод на консоль записывается в AMemo: TMemo.
functionRunDosCommand(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 <> nilthen 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 ifnot CreatePipe(ReadPipe, WritePipe, @Security, 0) then begin ReadPipe := 0; WritePipe := 0; end; end; if (ReadPipe <> 0) or (WritePipe <> 0) then begin if AMemo <> nilthen 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 <> nilthen 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; endelse if AMemo <> nilthen begin AMemo.SelStart := Length(AMemo.Text); AMemo.SelText :='Command failed.'#13#10; AMemo.SelStart := Length(AMemo.Text); Application.ProcessMessages; end; if AMemo <> nilthen 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
Наконец-то появилась версия ASProtect 1.65 в которой заявлена совместимость с Delphi XE и XE2. Я попробовал - созданный в Delphi XE2 и запакованный новым “аспром” exe действительно запускается и работает. Ошибок пока не замечено.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru