суббота, 24 июля 2010 г.

Remote debugging with MS Visual Studio

При разработке системных утилит, вносящих изменения в работу ОС, отладка похожа на проведении хирургической операции на своем теле: в случае ошибки все может закончиться. Да и некоторые программы вообще невозможно отладить на ПК, где они работают, например DirectX приложения с эксклюзивным режимом.

Что нам расскажет Microsoft про удаленную отладку? Читаем: http://msdn.microsoft.com/ru-ru/library/bt727f1t%28v=VS.90%29.aspx

Устанавливать среду разработки, или отдельные ее части, на тестовый ПК необязательно. Достаточно скопировать Remote Debugger Monitor с основного компьютера (он обычно лежит в директории \Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Remote Debugger, если он установлен).
На тестовом компьютере запускаем монитор – файл msvsmon.exe из директории x86.

После этого нужно настроить тип транспорта (Tools - Options). По умолчанию для связи используются именованные пайпы. На практике мне удалось соединиться через них только в доменной сети. Поэтому если вы являетесь счастливым пользователем контроллера домена, то, немного поплясав с бубном, возможно у вас все получится. Положительный момент соединения через пайпы – Windows-авторизация для доступа к удаленной отладке.
Я же пошел более простым путем, настроив соединение через TCP. К сожалению, с авторизацией в этом случае все совсем плохо: нет даже примитивной парольной защиты. Ну да ладно, мы же в отладке.

Проверяем, что все настроено правильно. На машине с Visual Studio пытаемся подключиться к удаленному процессу (Tools – Attach to process).
Если все выполнено правильно, то появится список удаленных процессов:

Теперь уже можно отлаживаться: копируем на удаленную машину собранное приложение (с отладочной информацией, естественно), запускаем его там и отсюда подключаемся к процессу. Но это не очень удобно.
Хорошо бы автоматизировать копирование и запуск.
Расшариваем директорию на тестовой машине, например C:\Debug. Следует только не забыть разрешить запись для всех пользователей и включить гостевую учетную запись из «Локальной политики безопасности», если она отключена (Пуск – Панель управления – Администрирование – Локальная политика безопасности):

У меня получился адрес \\192.168.92.1\Debug, путь на тестовой машине C:\Debug
Проверяем доступность директории: переходим по адресу \\192.168.92.1\Debug и закидываем туда файл. Если все получилось, то директория настроена правильно.

Копировать можно двумя способами:
1. Настроить Ouput Directory проекта на \\192.168.92.1\Debug
2. Настроить через Post Build Events копирование из командной строки

Рекомендую второй способ, поскольку если у вас в проекте используются сторонние DLL или другие бинарники, то копировать их все равно придется Post Build-ом.

Заходим в свойства проекта и настраиваем копирование:

и параметры запуска:

Вот теперь все как надо. Делаем Rebuild и отлаживаемся удаленно :)
Читать дальше...

пятница, 23 июля 2010 г.

Идиома RAII

Resource Acquisition Is Initialization – «Завладение ресурсом есть его инициализация». Если вдуматься в смысл, то чем-то напоминает ленивую инициализацию.
Однако для нас, для C++ программистов, наиболее важным является следствие из этой идиомы: «Освобождение ресурса есть его деинициализация».

К примеру, нужно скопировать содержимое одного файла в другой:

FILE* file1 = fopen("first.bin","rb");
if (!file1)
throw;

FILE* file2 = fopen("second.bin","wb");
if (!file2)
{
fclose(file1);
throw;
}

fseek(file1,0,SEEK_END);
unsigned long size = ftell(file1);
rewind(file1);

char* buffer = (char*)malloc(sizeof(char)*size);
if (!buffer)
{
fclose(file2);
fclose(file1);
throw;
}

unsigned long result = fread (buffer,1,size,file1);
if (result != size)
{
free(buffer);
fclose(file2);
fclose(file1);
throw;
}

result = fwrite(buffer,1,size,file2);
if (result != size)
{
free(buffer);
fclose(file2);
fclose(file1);
throw;
}

free(buffer);
fclose(file2);
fclose(file1);

Если на миг забыть то, что этот код исправно работает, во всем остальном он просто ужасен. Невооруженным взглядом видно, что большей частью он состоит из проверки результатов операций и откатов. Притом что, в конце концов, делается запланированный откат.

Давайте подумаем над недостатками такого кода. Вносить изменения в него крайне опасно: что-нибудь пропустишь – и ресурс (память или файл) останется неосвобожденным. Кроме того, если по каким-либо причинам исключение произойдет при работе с файлом через fread или fwrite, то все ресурсы останутся жить в зомбиленде. Кроме того, присутствует дублирование кода, читабельность стремиться к нулю со всеми вытекающими.

И тут приходят на помощь особенности языка C++. Давайте вспомним локальные переменные и области видимости.

{
int i;
MyClass myClass;

...
} // выход из области видимости

Что произойдет с переменными i и myClass по достижению конца области видимости? Переменная i будет вытолкнута из стека, а для myClass будет вызван деструктор класса. А ведь это уже интересно...

{
int i;
MyClass myClass;

...
if (i==1)
return 0;

if (i==2)
throw;
}

Когда в этом случае будет вызван деструктор myClass? Правильно, в каждом из случаев выхода из области видимости: если i=1, то перед return, если i=2 – перед генерацией исключения. При всех остальных значениях i – естественным путем, так как достигнута закрывающая скобка.
Это напоминает первый пример. Если написать откаты в деструкторах объектов, то не нужно их специально вызывать: они будут вызваны автоматически, когда это понадобится. И не останется зомби-файлов и утечек памяти, даже случайно. Ну, и по аналогии, в конструкторе нужно инициализировать объекты. Это и есть RAII.
Пишем код, лишенный всех недостатков. Сначала определяем классы, которые будут служить RAII-врапперами:

class File // RAII обертка для файла
{
public:
File(char* name, char* mode)
{
file_ = fopen(name,mode);
if (!file_)
throw;
}

~File()
{
if (file_)
fclose(file_);
}

void fread(char* buffer, unsigned long elements, unsigned long bytes)
{
if (::fread(buffer,elements,bytes,file_) != bytes)
throw;
}

void fwrite(char* buffer, unsigned long elements, unsigned long bytes)
{
if (::fwrite(buffer,elements,bytes,file_) != bytes)
throw;
}

unsigned long GetSize()
{
fseek(file_,0,SEEK_END);
unsigned long size = ftell(file_);
rewind(file_);

return size;
}

private:
FILE* file_;
};

class Memory // RAII обертка для памяти
{
public:
Memory(unsigned long size)
{
memory_ = (char*) malloc (size);

if (!memory_)
throw;
}

~Memory()
{
if (memory_)
free(memory_);
}

char* Get()
{
return memory_;
}
};

И, собственно, рефакторинг участка кода:

{
File file1("file1.bin","rb");
File file2("file2.bin","wb");

unsigned long fileSize = file1.GetSize();

Memory buffer(fileSize);

file1.fread(buffer.Get(),1,fileSize);
file2.fwrite(buffer.Get(),1,fileSize);
}

Вот и все! Память больше освобождать не нужно: она освободится автоматически, когда переменная buffer станет недоступна. И с закрытиями файлов также: все будет закрыто при выходе из области видимости переменных file1 и file2.
Стоит ли использовать идиому RAII? Поверьте, стоит. Даже если у вас феноменальная память и bounds checker в голове, рано или поздно вы допустите ошибку. Так почему бы не использовать RAII, хуже то от этого не становится.

P.S. Обмозговав класс Memory, можно прийти к выводу, что механизм автоматического освобождения памяти через RAII очень даже хорош. Но не спешите писать свой класс Memory: он уже есть в библиотеке STL, правильное его название «интеллектуальный указатель», а класс называется std::auto_ptr.

Читать дальше...

четверг, 8 июля 2010 г.

Upgradeable read для QReadWriteLock

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

Но этому объекту не хватает одного интересного механизма. Частенько нужно атомарно с точки зрения ресурса сначала захватить его на чтение, затем, если выполнились какие-то условия, не освобождая, повысить уровень эксклюзивности до возможности записи и вернуться обратно в режим чтения. К сожалению так нельзя: мы или читаем, или пишем. А вход-выход из режимов лишает операцию атомарности.

Следует доработать QReadWriteLock таким образом, чтобы кроме двух базовых состояний (read и write) у него появилось «третье» состояние upgradeable read. Когда поток находится в состоянии upgradeable read, другие потоки могут блокировать объект синхронизации только на чтение. Если в это же время другой поток попытается войти в состояние upgradeable read, то он будет остановлен до тех пор, пока текущий не выведет объект из этого состояния. Повышение уровня эксклюзивности из upgradeable read до write выглядит так же, как обычный вход в режим write.


Скрытая имплементация:

#include <QReadWriteLock>
#include <QMutex>
#include <QThread>
#include <QWaitCondition>

struct QUpgradeableReadWriteLockPrivate
{
QUpgradeableReadWriteLockPrivate(QReadWriteLock::RecursionMode recursionMode)
: accessCount(0), waitingReaders(0), waitingWriters(0),upgradeableReadAccessCount(0),
recursive(recursionMode == QReadWriteLock::Recursive), currentWriter(0),
waitingUpgradeableReaders(0), currentUpgradeableReader(0)
{ }

QMutex mutex;
QWaitCondition readerWait;
QWaitCondition writerWait;
QWaitCondition upgradeableReaderWait;

int accessCount;
int upgradeableReadAccessCount;

int waitingReaders;
int waitingUpgradeableReaders;
int waitingWriters;

bool recursive;
Qt::HANDLE currentUpgradeableReader;
Qt::HANDLE currentWriter;

QHash<Qt::HANDLE, int> currentReaders;
};


И, собственно, сам класс:

class QUpgradeableReadWriteLock
{
public:
QUpgradeableReadWriteLock()
:d(new QUpgradeableReadWriteLockPrivate(QReadWriteLock::NonRecursive))
{ }

QUpgradeableReadWriteLock(QReadWriteLock::RecursionMode recursionMode)
: d(new QUpgradeableReadWriteLockPrivate(recursionMode))
{ }

~QUpgradeableReadWriteLock()
{
delete d;
}

void lockForRead()
{
tryLockForRead(ULONG_MAX);
}

bool tryLockForRead()
{
return tryLockForRead(0);
}

void lockForUpgradeableRead()
{
tryLockForUpgradeableRead(ULONG_MAX);
}

bool tryLockForUpgradeableRead()
{
return tryLockForUpgradeableRead(0);
}

bool tryLockForUpgradeableRead(int timeout)
{
QMutexLocker lock(&d->mutex);
return upgradeableReadLockRequest(timeout);
}

bool tryLockForRead(int timeout)
{
QMutexLocker lock(&d->mutex);
return readLockRequest(timeout);
}

void lockForWrite()
{
tryLockForWrite(ULONG_MAX);
}

bool tryLockForWrite()
{
return tryLockForWrite(0);
}

bool tryLockForWrite(int timeout)
{
QMutexLocker lock(&d->mutex);
return writeLockRequest(timeout);
}

void unlock()
{
QMutexLocker lock(&d->mutex);

Qt::HANDLE self = QThread::currentThreadId();

bool unlocked = false;

if (d->accessCount < 0 && d->currentWriter == self)
{
unlocked = unlockFromWrite();
}
else
{
if (d->upgradeableReadAccessCount > 0 && d->currentUpgradeableReader == self)
{
unlocked = unlockFromUpgradeableRead();
}
else
{
if (d->accessCount > 0)
unlocked = unlockFromRead();
else
qFatal("Try to unlock object that was not locked");
}
}

if (unlocked)
wakeUp();
}

protected:
inline bool unlockFromWrite()
{
if (d->accessCount < 0 && ++d->accessCount == 0)
{
d->currentWriter = 0;
return true;
}
return false;
}

inline bool unlockFromUpgradeableRead()
{
if (d->upgradeableReadAccessCount > 0 && (--d->upgradeableReadAccessCount == 0))
{
d->currentUpgradeableReader = 0;
return true;
}
return false;
}

inline bool unlockFromRead()
{
Qt::HANDLE self = QThread::currentThreadId();

if (d->recursive)
{
QHash<Qt::HANDLE, int>::iterator it = d->currentReaders.find(self);

if (it != d->currentReaders.end())
{
if (--it.value() <= 0)
d->currentReaders.erase(it);
}
}

return --d->accessCount == 0;
}

inline bool writeLockRequest(int timeout)
{
Qt::HANDLE self = QThread::currentThreadId();

QHash<Qt::HANDLE, int>::iterator it = d->currentReaders.find(self);
Q_ASSERT_X(it == d->currentReaders.end() || !it.value(),"QUpgradeableReadWriteLock::writeLockRequest",
"This thread already locked for read");

if (d->currentWriter == self)
{
Q_ASSERT_X(d->recursive, "QUpgradeableReadWriteLock::writeLockRequest","Recursion is not supported");

--d->accessCount;
Q_ASSERT_X(d->accessCount < 0, "QUpgradeableReadWriteLock::writeLockRequest","Overflow in lock counter");
return true;
}

if (!timeout)
{
if (d->accessCount != 0)
return false;
}
else
{
while ((d->accessCount != 0) || (d->upgradeableReadAccessCount && (self != d->currentUpgradeableReader)))
{
++d->waitingWriters;
bool success = d->writerWait.wait(&d->mutex, timeout < 0 ? ULONG_MAX : timeout);
--d->waitingWriters;

if (!success)
return false;
}
}

d->currentWriter = self;

--d->accessCount;
Q_ASSERT_X(d->accessCount < 0, "QUpgradeableReadWriteLock::writeLockRequest",
"Overflow in lock counter");

return true;
}

inline bool readLockRequest(int timeout)
{
Qt::HANDLE self = QThread::currentThreadId();

if (d->upgradeableReadAccessCount>0 && (self == d->currentUpgradeableReader))
{
// already in upgradeable read lock
Q_ASSERT_X(d->recursive, "QUpgradeableReadWriteLock::readLockRequest","Recursion is not supported");
return upgradeableReadLockRequest(timeout);
}

if (d->recursive)
{
QHash<Qt::HANDLE, int>::iterator it = d->currentReaders.find(self);
if (it != d->currentReaders.end())
{
++it.value();
++d->accessCount;
Q_ASSERT_X(d->accessCount > 0, "QUpgradeableReadWriteLock::readLockRequest",
"Overflow in lock counter");
return true;
}
}

if (!timeout)
{
if (d->accessCount < 0)
return false;
}
else
{
while (d->accessCount < 0 || d->waitingWriters)
{
++d->waitingReaders;
bool success = d->readerWait.wait(&d->mutex, timeout < 0 ? ULONG_MAX : timeout);
--d->waitingReaders;
if (!success)
return false;
}
}

if (d->recursive)
d->currentReaders.insert(self, 1);

++d->accessCount;
Q_ASSERT_X(d->accessCount > 0, "QUpgradeableReadWriteLock::readLockRequest", "Overflow in lock counter");

return true;
}

inline bool upgradeableReadLockRequest(int timeout)
{
Qt::HANDLE self = QThread::currentThreadId();

QHash<Qt::HANDLE, int>::iterator it = d->currentReaders.find(self);
Q_ASSERT_X(it == d->currentReaders.end() || !it.value(),
"QUpgradeableReadWriteLock::upgradeableReadLockRequest",
"This thread already in read lock");

if (self == d->currentUpgradeableReader)
{
Q_ASSERT_X(d->recursive, "QUpgradeableReadWriteLock::writeLockRequest","Recursion is not supported");

++d->upgradeableReadAccessCount;
Q_ASSERT_X(d->upgradeableReadAccessCount > 0, "QUpgradeableReadWriteLock::upgradeableReadLockRequest",
"Overflow in lock counter");
return true;
}

if (!timeout)
{
if (d->upgradeableReadAccessCount != 0)
return false;
}
else
{
while (d->accessCount < 0 || d->waitingWriters || d->upgradeableReadAccessCount)
{
++d->waitingUpgradeableReaders;
bool success = d->upgradeableReaderWait.wait(&d->mutex, timeout < 0 ? ULONG_MAX : timeout);
--d->waitingUpgradeableReaders;
if (!success)
return false;
}
}

d->currentUpgradeableReader = self;

++d->upgradeableReadAccessCount;
Q_ASSERT_X(d->upgradeableReadAccessCount > 0, "QUpgradeableReadWriteLock::upgradeableReadLockRequest", "Overflow in lock counter");

return true;
}

void wakeUp()
{
if (d->waitingWriters)
d->writerWait.wakeAll();
else
if (d->waitingUpgradeableReaders)
d->upgradeableReaderWait.wakeOne();
else
if (d->waitingReaders)
d->readerWait.wakeAll();
}

private:
QUpgradeableReadWriteLockPrivate *d;
};


Устроим стресс-тест (винда)

#include <windows.h>

QUpgradeableReadWriteLock locker(QReadWriteLock::Recursive);

class TestThread : public QThread
{
public:
int num;

protected:
virtual void run()
{
while(1)
{
if (rand() % 10 > 4)
{
locker.lockForRead();
cout << "Concrete Reading "<< num << " ~~~~~~~" << endl;
Sleep(rand()%5);
cout << "End concrete Reading " << num << " ~~~~~~~~~~" << endl;
locker.unlock();
}

if (rand() % 10 > 3)
{
locker.lockForWrite();
cout << "Concrete Writing " << num << "~~~~~~~~" << endl;
Sleep(rand()%10);
cout << "End concrete Writing " << num << "~~~~~~" << endl;
locker.unlock();
}

locker.lockForUpgradeableRead();
locker.lockForUpgradeableRead();
Sleep(rand()%3);

cout << "UPG Reading " << num << " --------------" << endl;
Sleep(rand()%3);

if (rand() % 10 >= 5)
{
locker.lockForWrite();

cout << "Writing " << num << " <<<<<<<<<<<<" << endl;
Sleep(rand()%10);
cout << "Writing " << num << " Free >>>>>>>>>>" << endl;

locker.unlock();
}

cout << "UPG Reading " << num << " Free ++++++++++++" << endl;

locker.unlock();
Sleep(rand()%2);

locker.unlock();
Sleep(rand()%2);
}
}
};


int main()
{
const int threads = 100;
srand(GetTickCount());

TestThread t[threads];

for (int i=0; i<threads; i++)
{
t[i].num = i+1;
t[i].start();
}

while(true)
{
Sleep(1000);
}
}

Примеры использования блокировок:

#include <QReadWriteLock>
#include "QUpgradeableReadWriteLock.h"

// Объявление класса без рекурсии
QUpgradeableReadWriteLock locker();

// чтение
locker.lockForRead(); // вход в режим блокировки для чтения
target.read();
locker.unlock(); // выход из блокировки

// запись
locker.lockForWrite(); // вход в режим блокировки
target.read();
locker.unlock(); // выход из блокировки

// чтение с возможностью повышения до записи
locker.lockForUpgradeableRead(); // блокировка для чтения с возможностью повышения до записи
if (target.read() == true)
{
locker.lockForWrite(); // блокировка для записи
target.write();
locker.unlock(); // освобождение записи
}
locker.unlock(); // освобождение чтения


Особенность: в режиме upgradeable read можно входить в режим read. Но не наоборот. Да, и в отличие от обычного QReadWriteLock, при попытке рекурсивной блокировки в нерекурсивном режиме объект кидает исключение, а не замирает в дедлоке.

P.S. Надеюсь, что подобный механизм появится нативно в следующих релизах Qt

Читать дальше...

вторник, 6 июля 2010 г.

MSBuild и Visual Studio 2008

Консольная утилита автоматической сборки MSBuild.exe не работает с файлами сборки Visual Studio 2008, выдавая ошибку:

Solution file error MSB5014: File format version is not recognized. MSBuild can only read solution files between versions 7.0 and 9.0, inclusive

и сообщая нам, что будет работать с файлами сборки версий с 7.0 по 9.0 (2005). Источник проблемы здесь вот в чем. По умолчанию используется MSBuild от .NET Framework версии 2.0, который не понимает солющены от VS 2008.
Так как MSBuild активно используется в крупных проектах и в TFS, то проблема как минимум неприятная.

Бороться с этим можно несколькими способами:

1. Обманываем MSBuild, сообщая ему другую версию файла сборки
На самом деле, утилите абсолютно параллельна разница между файлами sln версии 9 и 10. Поэтому достаточно открыть файл на редактирование и подменить номер версии, и все будет отлично работать.

Способ описан здесь:
http://tfsnow.wordpress.com/2007/08/22/building-visual-studio-2008-under-team-build-2005/
Открываем любым редактором файл sln и в первой же строчке заменяем
Microsoft Visual Studio Solution File, Format Version 10.00

на
Microsoft Visual Studio Solution File, Format Version 9.00

После этого MSBuild сможет обработать такую сборку. Но перед редактированием в IDE нужно не забыть вернуть все назад.
Естественно, для автосборки все это можно автоматизировать, например bat файлом

2. Используем саму среду devenv для консольной сборки вместо MSBuild

Мне предыдущий способ показался слишком неудобным. Как альтернатива, вместо использования MSBuild можно использовать devenv.exe в консольном режиме. Находим в проекте сборки команды, делающие build, rebuild и clean. Они могут быть похожи на такие:

msbuild.exe /t:build /p:Configuration=$(ConfigurationName) /p:Platform=$(PlatformName) SolutionFile.sln
msbuild.exe /t:rebuild /p:Configuration=$(ConfigurationName) /p:Platform=$(PlatformName) SolutionFile.sln
msbuild.exe /t:clean /p:Configuration=$(ConfigurationName) /p:Platform=$(PlatformName) SolutionFile.sln

и заменяем на соответствующие:

devenv.exe /Build "$(ConfigurationName)|$(PlatformName)" SolutionFile.sln
devenv.exe /Rebuild "$(ConfigurationName)|$(PlatformName)" SolutionFile.sln
devenv.exe /Clean "$(ConfigurationName)|$(PlatformName)" SolutionFile.sln

Если сборка производится с помощью bat файла, тогда все еще проще.
Полностью отказаться от MSBuild не всегда получается, так как она поддерживает сборочные скрипты. Кроме того, этот метод не проверялся с TFS.
Зато перед сборкой никакие файлы не нужно править.

3. Удаляем "неправильную" версию MSBuild.exe
Сделайте поиск файла MSBuild.exe по директории \Windows. У меня, например, нашлось 2 файла:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe
C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe

дело за малым: нужно либо удалить, либо переименовать файл MSBuild.exe, который находится в директории .NET Framework версии 2.0. После этого система от безвыходности начнет использовать "правильный" файл из .NET версии 3.5.
Вероятно, это самый правильный способ, особенно для TFS. Но недостаток у него тоже есть, в частности для локальных проектов, использующих MSBuild. Проекты будут собираться только у вас на машине, а коллега-программист, которому вы передадите код, столкнется с этой же ошибкой и должен будет тоже все это прочитать...

Читать дальше...

понедельник, 5 июля 2010 г.

Форматированная строка в std::string

Не нравится мне пользоваться STL потоками для форматирования строк в C++. Не нравится, и все. ИМХО, потоки STL удобны для чего угодно, но не для этого. Особенно если это Debug print с кучей отладочной инфы.
Куда краше сишные конструкции с форматированным выводом. Для программистов, которые любят и знают C, запись printf("%d %d %d\n", a,b,c) более приятна и читабельна чем cout << a << b << c << endl.

Как то раз я написал такой класс и использую теперь во всех своих проектах:

#pragma once

#include <string>
#include <stdlib.h>
#include <iostream>
#include <stdarg.h>

class FormattedString : public std::string
{
public:
static std::string Format (const std::string format, ...);

private:
static std::string FormatArgumentsList(const std::string format, va_list arguments);

static const int StartBufferSize = 64;
static const int BufferSizeIncrement = 256;
};


#include "FormattedString.h"

using namespace std;

#include <memory>
#include <stdio.h>

#ifdef ANSI
# include <stdarg.h>
int average( int first, ... );
#else
# include <varargs.h>
int average( va_list );
#endif

string FormattedString::Format(const std::string format, ...)
{
va_list arguments;
va_start(arguments, format);

string resultString = FormatArgumentsList(format, arguments);

va_end(arguments);
return resultString;
}

string FormattedString::FormatArgumentsList(const string format, va_list arguments)
{
int bufferSize = StartBufferSize ;
std::auto_ptr<char> buffer;

do
{
buffer.reset(new char[bufferSize]);

#ifdef _MSC_VER
int result = _vsnprintf_s(buffer.get(), bufferSize, bufferSize-1, format.c_str(), arguments);
#else
int result = vsnprintf(buffer.get(),bufferSize-1,format.c_str(),arguments);
#endif //_MSC_VER

if (result < 0)
{
bufferSize += BufferSizeIncrement;
continue;
}
break;
} while(true);

return buffer.get();
}


Пользоваться этим очень просто:

std::string myString = FormattedString::Format("%d (%s) %5.2f", 10, "Hello world", 3.14f);
// или
std::cout << FormattedString::Format("HEX: %X",0xABCDEF) << std::endl;


С компиляторами, отличными от Visual Studio, библиотечная функция vsnprintf небезопасна. Поэтому, во избежании уязвимостей, рекомендую использовать класс только для статической или отладочной печати, либо хотя бы проверять аргументы перед отправкой в FormattedString::Format.

Читать дальше...