7. Выполняем шаги 5,6 еще разок с другой командой: bjam toolset=msvc-10.0 variant=debug,release threading=multi link=static
8. Прописываем в Visual Studio пути к бусту. Для этого открываем проект C++ (либо создаем новый), открываем вкладку Property Manager, выбираем Microsoft.Cpp.Win32.user в Debug | Win32 и Release | Win32 и в Property Pages->VC++ Directories добавляем в Include Directories C:\\boost\\boost\_1\_53\_0, а в Library Directories C:\\boost\\boost\_1\_53\_0\\stage\\lib
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
В C++ под Windows есть два способа обработки исключений - традиционный для C++ с пом. try/catch и т.н. структурная обработка исключений или SEH. Основная разница между ними в том, что с помощью try/catch можно реализовывать для разных типов исключений C++ различную реакцию (поведение), а структурная обработка исключений способна отловить ситуации, которые с пом. catch не ловятся, например, Divide By Zero или Access Violation. SEH - это механизм, предоставляемый операционной системой и на самом деле try/catch реализуется также через него.
Одновременно в одной и той же процедуре использовать оба метода обработки исключений (try/catch и SEH) нельзя. Но можно использовать в одной процедуре try/cath, а в другой SEH.
Оба метода обработки исключений не идеальны и имеют свои достоинства и недостатки.
Основной недостаток try/catch заключается в том, что он не позволяет отловить и обработать все ошибки, а также не показывает место возникновения ошибки. Впрочем, в книжке Джеффри Рихтера (Windows для профессионалов. Создание эффективных Win32-пpилoжeний с учетом специфики 64-разрядной версии Windows) приводится способ перехвата структурных исключений:
1. Нужно создать класс CSE для идентификации структурного исключения:
2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:
intDivideByZero() { int x = 100; int y = 0; return x / y; } voidThrowException() { throw std::out_of_range("out_of_range"); } voidTestCSE() { CSE::MapSEToCE(); try { //DivideByZero(); ThrowException(); } catch (CSE se) { switch(se) { case EXCEPTION_INT_DIVIDE_BY_ZERO: printf("Divide by zero!\n"); break; } } catch (std::exception & e) { printf("std::exception: %s\n", e.what()); } }
3. Компилировать нужно с опцией /EHa. В противном случае _set_se_translator не сработает и исключения SEH с помощью catch (…) отлавливаться не будут, а компилятор (Visual Studio 2010) выдаст следующее предупреждение: warning C4535: calling _set_se_translator() requires /EHa
SEH способен отлавливать все исключения и, что важно на мой взгляд, показывать место возникновения ошибки (ExceptionAddress), а также позволяет продолжить выполнение программы с прерванного места. Но, к сожалению, при возникновении исключения C++ в обработчике (фильтре) SEH документированными способами нельзя узнать тип исключения C++ и получить его текст ошибки. Ниже описан недокументированный способ.
Все исключения C++ в фильтре SEH имеют один и тот же код 0xe06d7363. При этом поле NumberParameters содержит 3 (для 32-разрядных приложений), ExceptionInformation[1] указывает на объект-исключение C++, а ExceptionInformation[2] указывает на структуру _ThrowInfo (ExceptionInformation[0] не интересно). _ThrowInfo относится к так называемым Predefined C++ Types. Для использования предопределенных типов не нужно подключать никаких .h файлов. При редактировании IDE Visual Studio подчеркивает их красным цветом и пишет: Error: identifier “_ThrowInfo” is undefined. Но при этом все успешно компилируется и выполняется :-)
Ниже приведен SEH-фильтр, который способен анализировать исключения C++ и выводить информацию об исключении (what()). Стоит отметить, однако, что хотя эта процедура успешно работает в Visual Studio 2010, но нет никаких гарантий, что в будущем Microsoft не поменяет структуру _ThrowInfo.
В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Захотелось мне вынести инициализацию данных класса в виртуальную функцию init(), с тем, чтобы классы-наследники могли переопределить ее и добавить в инициализацию что-то своё. Примерно так:
Опа! Оказывается переопределенная в классе C2 функция init() не вызывается. Получается, что если вызвать из конструктора класса виртуальную функцию этого же класса, то обычный метод вызова виртуальных функций через vtable задействован не будет - функция будет вызвана как если бы они была не-виртуальной. По-моему такое поведение не выглядит логичным, поэтому от вызова виртуальных функций из конструктора лучше отказаться.
PS. Из деструктора вызывать виртуальные функции тоже не следует.
По поводу того, что при вызове виртуальных функций из конструктора (и деструктора) не используется vtable, - тут я, похоже, был не прав. vtable используется, только в конструкторе класса C1 (см. пример) используется vtable класса C1, а не класса-потомка C2 - вот в чем дело. Аналогично, в деструкторе C1 также используется vtable класса С1.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
Связь событийного парсера, типа 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