Столкнулся с интересным багом при получении информации о версии из ресурсов (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. Реализация приведена ниже.
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:
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) следующим образом:
Чтобы убрать зависимость от Microsoft Visual C++ Runtime нужно в свойствах проекта выбрать Runtime Library: Multi-threaded (/MT). Получающийся при компиляции exe/dll будет несколько больше, зато не нужно будет устанавливать Visual C++ Redistributable Package.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
В моем посте про проверку серверного сертификата SSL в Delphi 7 проверка этого самого сертификата выполнялась вызовом функции CryptoAPI CertGetIssuerCertificateFromStore. Я столкнулся на практике со следующим: оказывается, что на эту функцию оказывает влияние Internet Explorer Enhanced Security (по умолчанию оно включено на всех серверах Windows 2003). Т.е. до тех пор, пока хост не будет включен в список доверенных, его сертификат тоже может не проходить проверку. Как-то так (слишком глубоко эту тему не копал). В общем, если на одном компьютере проверка сертификата работает, а на другом - нет (ошибка “Error connecting with SSL”), то следует проверить - включен ли IE Enhanced Security, и если да, то либо отключить его, либо добавить хост в список доверенных.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru
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
Это не первый мой аккаунт в github, поэтому потребовалось как-то организовывать одновременный доступ к нему из под разных учеток, с разными SSHKey. По этому поводу маленький “трик” - как я все это сделал:
1. При установке Git for Windows создает такой ярлык для Git Bash:
Как мы видим текущий каталог Git Bash - %HOMEDRIVE%%HOMEPATH%. В нем хранятся все настройки git, каталог .ssh с SSHKey и проекты обычно тоже там.
Я создал в %HOMEDRIVE%%HOMEPATH% каталог Coolsoftware и в нем git.cmd следующего содержания:
Затем создал ярлык на этот git.cmd и вынес его на рабочий стол, обозвав “Git Bash - Coolsoftware”. Теперь при его запуске текущий каталог пользователя в “баше” %HOMEDRIVE%%HOMEPATH%\Coolsoftware, а при запуске старого ярлыка “Git Bash” текущий каталог %HOMEDRIVE%%HOMEPATH%. Таким образом получилось два “баша”, в каждом - свои настройки.
Далее запустил “Git Bash - Coolsoftware” произвел установку github в соответствии с инструкцией и выложил проект VHashedStringList - оптимизированный список строк, доступ к элементам которого организован с помощью хеш-индекса.
TVHashedStringList - аналог стандартного THashedStringList (фактически, он сделан на его основе, хотя наследуется не от него, а от TStringList).
Существенное отличие от THashedStringList заключается в том, что оптимизированы операции перестроения индекса при изменении списка.
В THashedStringList полное перестроение индекса требуется после любого изменения, что приводит к значительным задержкам в реальных приложениях.
Например, в следующем примере полное перестроение индекса будет произведено 3 раза:
procedureChangeList(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
В много-поточном приложении довольно часто возникает необходимость синхронизировать обращение к разделяемому ресурсу, который могут одновременно читать несколько потоков “читателей”, а производить запись в этот ресурс может только один поток “писатель”. Когда читатели читают данные, то писатель не может писать и должен ждать, пока все читатели не закончат чтение. Когда писатель пишет, то все читатели должны ждать, пока он не запишет все данные и не освободит ресурс.
Ниже приведена моя реализация “читателя” и “писателя”. В этой реализации есть допущение о том, что нам известно максимальное количество читателей 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, как можно было бы предположить.
Стандартный генератор случайных чисел (ГСЧ) в 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
Обычно решают противоположную задачу - вызов внешних модулей (DLL, COM), написанных на C++, Delphi и т.п., из программы на C#. У меня же возникла необходимость вызвать из программы написанной на Delphi функцию библиотеки, написанной на C#. В этом посте я расскажу о том, как это делается.
Итак, нужно сделать на C# COM объект, к которому затем мы сможем обращаться из нашей программы на Delphi.
В Visual Studio создаем новый проект New/Project/Visual C#/Class Library.
В свойствах проекта на вкладке Application жмем кнопку “Assembly Information” и отмечаем “Make assembly COM-visible”.
В свойствах проекта на вкладке Build отмечаем Register for COM interop.
Для регистрации библиотеки вместо regsvr32 нужно использовать regasm. Вызов будет типа такого:
Sqlite замечательная локальная база данных. Не требует установки, легко встраивается в приложения благодаря API. Шустрая благодаря тому, что данные не гоняются ни по сети, ни даже между процессами. У меня загрузка листа в миллион записей в Sqlite занимает порядка 30 секунд. Для сравнения - та же самая процедура загружает те же данные в локальный MySQL в 10 раз дольше. Однако, для того, чтобы загрузка данных в Sqlite происходила с максимально возможной скоростью нужно использовать две вещи:
1. Параметризованный Sql, типа такого: INSERT INTO `table1` (`A`, `B`) VALUES (:param1, :param2). В начале процедуры загрузки выполнять “prepare” (Sqlite3_Prepare_v2). Перед вставкой “биндить” переменные с помощью sqlite3_bind_text, sqlite3_bind_int64 и т.п., а затем выполнять вставку вызовом функции sqlite3_step.
2. Транзакции: вставлять данные большими порциями, например, по 1000 записей. Перед вставкой очередной порции выполнять BEGIN TRANSACTION. В конце вставки - COMMIT TRANSACTION. Если этого не делать, то после каждой операции вставки данные будут немедленно сбрасываться на диск, что замедлит процесс во много раз.
=== Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru