LockLib

LockLib это набор классов для организации доступа к разделяемым ресурсам в программе на C++ под Windows.
Исходники доступны на GitHub: https://github.com/coolsoftware/LockLib.

class VLock

Класс VLock используется как альтернатива CRITICAL_SECTION (на самом деле это “обертка” над CRITICAL_SECTION).

void Lock(int lPosition, volatile LONG * lpThreadLock = NULL)

Блокировка ресурса для монопольного использования. Если ресурс уже кем-то заблокирован, то происходит ожидание когда ресурс снова станет свободен и его удастся заблокировать.

Параметр lPosition служит для идентификации места вызова метода Lock и может использоваться при отладке.

Необязательный параметр lpThreadLock служит для подсчета вызовов метода Lock в текущем потоке. Подробности смотрите ниже в разделе посвященном lpThreadLock.

void Unlock(volatile LONG * lpThreadLock = NULL)

Снятие блокировки.

static void OutputDebugLocks()

При отладке и оптимизации приложений иногда нужно видеть список всех существующих блокировок и статистику по ним: сколько в данный момент активных блокировок, в каком месте они заблокированы. Посмотреть такую статистику можно вызвав OutputDebugLocks. Эта статистика доступна в Debug-версии приложении, когда объявлен _DEBUG, или когда объявлен макрос DEBUG_LOCK.

volatile LONG * lpThreadLock

Проблема с блокировками в много-поточном приложении связана с тем, что поток, который установил блокировку, может быть остановлен с помощью TerminateThread. В этом случае Unlock не будет вызван никогда и блокировка ресурса “повиснет”, а другие потоки, которые попытаются заблокировать этот ресурс, также “повиснут”.
Чтобы разрулить эту ситуацию, необходимо вести подсчет блокировок для каждого потока и вызывать Unlock после TerminateThread.

VLock lock;

unsigned int __stdcall LockThreadProc(void * lpParam)
{
lock.Lock(1, reinterpret_cast(lpParam)); //lock resource

//do something here

lock.Unlock( reinterpret_cast(lpParam)); //unlock resource

//contuinue working

return 0;
}

class VRWLock

Класс VRWLock позволяет реализовать стратегию блокировки ресурса “один писатель - много читателей”.

VRWLock(LONG lMaxReaders = 65535, DWORD dwSpinCount = 1000, DWORD dwTimeout = 5)

Конструктор класса VRWLock имеет следующие параметры:

lMaxReaders - максимальное количество читателей, которые могут одновременно читать ресурс (не должно быть = 0!). Значение по-умолчанию 65535.

dwSpinCount - количество неудачных попыток блокировки ресурса (занятого), после которых происходит переключение контекста с небольшой задержкой, определяемой параметром dwTimeout (в миллисекундах).

void LockRead(int lPosition, volatile LONG * lpThreadLock = NULL)

Блокировка ресурса читателем. Если ресурс занят писателем или превышено максимальное количество читателей, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void LockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)

Блокировка ресурса писателем. Если ресурс занят писателем или одним или несколькими читателями, то происходит ожидание освобождения ресурса, когда удастся его заблокировать. Смотрите описание параметров в описании метода Lock класса VLock.

void ReLockWrite(int lPosition, volatile LONG * lpThreadLock = NULL)

Изменение статуса блокировки с читателя на писателя. Если ресурс не занят ни читателем ни писателем, то действие функции аналогично LockWrite (блокировка писателем). Если ресурс занят одним читателем, то происходит переключение его статуса с читателя на писателя. Если ресурс занят писателем или более чем одним читателем, то происходит ожидание момента, когда ресурс будет не занят писателями и занят не более чем одним читателем. Смотрите описание параметров в описании метода Lock класса VLock.

void Unlock(volatile LONG * lpThreadLock = NULL)

Снятие блокировки.

static void OutputDebugLocks()

Вывод статистики блокировок (см. выше описание одноименного метода для класса VLock).

class VLockPtr, class VReadLockPtr, class VWriteLockPtr

Есть такая идиома: RAII - захват ресурса есть инициализация. Суть ее в следующем: создается класс-обертка такой, что в конструкторе класса вызывается соответствующая функция блокировки ресурса, а в деструкторе блокировка снимается. Это удобно по двум причинам:

  1. Нет необходимости делать явный вызов Unlock (а это часто, как показывает практика, забывают сделать).
  2. В случае возбуждения исключения между вызовами Lock и Unlock ресурс может оказаться занят “навсегда”. А при использования RAII, деструктор класса-обертки, а следовательно и Unlock, будет вызван и в случае исключительной ситуации.

VLockPtr - RAII класс-обертка над VLock.

VReadLockPtr - RAII класс-обертка над VRWLock::LockRead.

VWriteLockPtr - RAII класс-обертка над VRWLock::LockWrite.

unsigned int __stdcall LockPtrThreadProc(void * lpParam)
{
{
VLockPtr lockptr(&lock, 1, reinterpret_cast(lpParam)); //lock resource

//do something here

} //unlock will be done here

//contuinue working

return 0;
}

===

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

Failure during conversion to COFF: file invalid or corrupt

Вылезла сегодня с утра ошибка при компиляции любого проекта в  Visual Studio 2010:

LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

Установка в свойствах проекта Linker->Enable Incremental Linking значения NO (/INCREMENTAL:NO) на помогла.

Полез смотреть, что же поменялось за последнее время. Оказалось вечером с авто-обновлениями винды поставился .NET Framework 4.5.1. Он то и нагадил!

1. Снес .NET Framework 4.5.1. Visual Studio перестал запускаться.
2. Установил .NET 4.0. Visual Studio починился, все проекты стали компилироваться нормально.
3. Слетел Mysql .Net Connector. Переустановил. Причем, Repair не помог, сделал сперва Remove, потом Install.

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

MySQL не видит my.ini

Понадобилось мне поднять max_connections в MySQL, который по-умолчанию был установлен инсталлером в C:\Program Files\MySQL\MySQL Server 5.6.
В этом каталоге лежит my-default.ini, который я переименовал в my.ini и прописал в нем max_connections=250. Перезапустил сервис MySQL - не работает! select @@max_connections возвращает 100. Не видит MySQL моего my.ini.
Полчаса потратил на поиски откуда же MySQL читает настройки. Поэтому и решил написать это сообщение здесь - вдруг кому-нибудь поможет и сэкономит время.

Оказывается сервис запускается с параметром –defaults-file=”C:\ProgramData\MySQL\MySQL Server 5.6\my.ini”. Вот, значит, где находится my.ini.

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

PerlVCBuildScripts

Выложил на github perl-скрипты, которые я использую для автоматического изменения номера билда в проектах на Visual C++, а также генерации файлов xxx-build.txt с информацией о версии и контрольной суммой (MD5). Взять можно тут: https://github.com/coolsoftware/PerlVCBuildScripts

Использовать просто:

  1. Нужно создать файл с информацией о билде (build-файл) в каталоге Release (где будет создан exe или dll). Например, мой проект называется HTTPGet, имя исполняемого (генерируемого) файла HTTPGet.exe, значит имя build-файла должно быть HTTPGet-build.txt. Вот как выглядит структура каталогов проекта:

HTTPGet \
  HTTPGet.sln
  HTTPGet.aspr
  Release \
    HTTPGet-build.txt
    HTTPGet.exe

Содержимое build-файла должно быть таким:

1.0.0.0
http://127.0.0.1/HTTPGet.exe
HTTPGet.exe
b9f2c07999dcafe9fe544a00521829c1

Первая строка - версия приложения (будет автоматически обновляться при сборке).
Вторая строка - URL для скачки последней версии.
Третья строка - имя файла.
Четвертая строка - MD5-подпись (будет автоматически обновляться при сборке).

Я использую build-файлы в функции авто-обновления в приложениях. Реализацию этой функции я выложу как-нибудь позже.

  1. Нужно прописать в Pre-Build Event->Command Line вызов:

perl $(ProjectDir)..\incbuild.pl $(ProjectDir) $(Configuration)

а в Post-Build Event->Command Line прописать:

perl $(ProjectDir)..\makebuildinfo.pl $(TargetPath)

Если вы используете ASProtect для упаковки/защиты приложения, то можно прописать путь к вашему ASPR-проекту, чтобы ASProtect был вызван автоматически при сборке:

perl $(ProjectDir)..\makebuildinfo.pl $(TargetPath) $(ProjectDir)..\$(TargetName).aspr

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

Сборка boost в Visual Studio 2010

1. Скачиваем boost с официального сайта http://www.boost.org/. Текущая версия на момент написания поста: 1.53.0.

2. Распаковываем архив на диск (я распаковал в c:\boost\boost_1_53_0).

3. Запускаем Visual Studio Command Prompt (2010).

4. Переходим в каталог boost и собираем bjam запустив bootstrap.bat.

5. Собираем boost командой: bjam toolset=msvc-10.0 variant=debug,release threading=multi link=static runtime-link=static

6. Пьем кофе… Ждем завершения…

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++: обработка исключений

В 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 для идентификации структурного исключения:

#include <eh.h>
#include <stdexcept>

class CSE
{
public:
static void MapSEToCE(){ _set_se_translator(TranslateSEToCE); }
operator DWORD() { return (m_er.ExceptionCode);}

private:
CSE(PEXCEPTION_POINTERS pep)
{
m_er = *pep->ExceptionRecord;
m_context = *pep->ContextRecord;
if (m_er.ExceptionCode == 0xe06d7363)
{
printf("C++ Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
}
else
{
printf("SEH Exception: Code 0x%x, Address 0x%x\n", m_er.ExceptionCode, m_er.ExceptionAddress);
}
}
static void _cdecl TranslateSEToCE(UINT deEC,PEXCEPTION_POINTERS pep)
{
throw CSE(pep);
}

private:
EXCEPTION_RECORD m_er;
CONTEXT m_context;
};

2. Для каждого потока надо вызвать один раз CSE::MapSEToCE(), после чего можно обрабатывать структурные исключения как обычные исключения C++:

int DivideByZero()
{
int x = 100;
int y = 0;
return x / y;
}

void ThrowException()
{
throw std::out_of_range("out_of_range");
}

void TestCSE()
{

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.

#include <eh.h>
#include <stdexcept>

LONG ExceptionFilter(PEXCEPTION_POINTERS pEP, const char * file, const char * function, int line)
{
PEXCEPTION_RECORD pER = pEP->ExceptionRecord;
if (pER->ExceptionCode == 0xe06d7363)
{
if (pER->NumberParameters == 3)
{
const _ThrowInfo * pThrowInfo = (_ThrowInfo*)pER->ExceptionInformation[2];
if (pThrowInfo != NULL)
{
const _CatchableTypeArray * pTypeArray = pThrowInfo->pCatchableTypeArray;
if (pTypeArray != NULL)
{
const _TypeDescriptor * pExceptionDesc = (_TypeDescriptor*)&typeid(std::exception);
for (int i = 0; i < pTypeArray->nCatchableTypes; i++)
{
const _CatchableType * pCatchableType = pTypeArray->arrayOfCatchableTypes[i];
if (pCatchableType != NULL && pExceptionDesc == pCatchableType->pType)
{
std::exception * e = (std::exception*)pER->ExceptionInformation[1];
printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception: %s\n", file, function, line, e->what());
return EXCEPTION_EXECUTE_HANDLER;
}
}
}
}
}
printf("File: %s\nFunction: %s\nLine: %i\nC++ Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
return EXCEPTION_EXECUTE_HANDLER;
}
else
{
printf("File: %s\nFunction: %s\nLine: %i\nSEH Exception (code 0x%x at 0x%x)\n", file, function, line, pER->ExceptionCode, pER->ExceptionAddress);
return EXCEPTION_EXECUTE_HANDLER;
}
}

int main(int argc, char* argv[])
{
__try
{
//DivideByZero();
ThrowException();
}
__except(ExceptionFilter(GetExceptionInformation(), __FILE__, __FUNCTION__, __LINE__))
{
//handle exception here
}

return 0;
}

В заключении хочу отметить, что ExceptionAddress для исключений C++ не имеет особого смысла, потому что указывает всегда на один и тот же адрес внутри функции __CxxThrowException(), которая выполняется каждый раз, когда вызывается исключение с помощью throw.

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

C++: конструкторы и виртуальные функции

Как говорится, век - живи, век учись.

Захотелось мне вынести инициализацию данных класса в виртуальную функцию init(), с тем, чтобы классы-наследники могли переопределить ее и добавить в инициализацию что-то своё. Примерно так:

class C1
{
protected:
int m_count;
virtual void init() { m_count = 1; }
public:
C1() { init(); }
inline int count() const { return m_count; }
};

class C2: public C1
{
protected:
virtual void init() { C1::init(); m_count = m_count+1; }
public:
C2() : C1() {}
};

int _tmain(int argc, _TCHAR* argv[])
{
C1 c1;
_tprintf(_T("C1 count: %i\n"), c1.count());
C2 c2;
_tprintf(_T("C2 count: %i\n"), c2.count());
return 0;
}

Результат получается такой:

C1 count: 1
C2 count: 1

Опа! Оказывается переопределенная в классе C2 функция init() не вызывается. Получается, что если вызвать из конструктора класса виртуальную функцию этого же класса, то обычный метод вызова виртуальных функций через vtable задействован не будет - функция будет вызвана как если бы они была не-виртуальной. По-моему такое поведение не выглядит логичным, поэтому от вызова виртуальных функций из конструктора лучше отказаться.

PS. Из деструктора вызывать виртуальные функции тоже не следует.

По поводу того, что при вызове виртуальных функций из конструктора (и деструктора) не используется vtable, - тут я, похоже, был не прав. vtable используется, только в конструкторе класса C1 (см. пример) используется vtable класса C1, а не класса-потомка C2 - вот в чем дело. Аналогично, в деструкторе C1 также используется vtable класса С1.

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

Парсеры

Связь событийного парсера, типа 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