#c #dll #stdstring
#c #dll #stdstring
Вопрос:
Я пытаюсь создать DLL, которая предоставляет определенный API, и, таким образом, хотел реализовать безопасный способ копирования строк через границы DLL. Реализация DLL очень проста — все функции, возвращающие строковые значения, принимают два аргумента — char*
и size_tamp;
. Если размер достаточно велик, я memcpy
передаю содержимое строки из библиотеки DLL в указанный указатель, устанавливаю фактический размер и возвращаю успешный код возврата. Если это не так, я устанавливаю размер таким, каким он должен быть, и возвращаю код ошибки. Сторона DLL очень проста.
Теперь, что сложнее — как создать хорошую шаблонную функцию, которая, учитывая указатель на некоторую функцию из DLL, выполняла бы все правильные манипуляции для заполнения экземпляра std::string
. Вот к чему я пришел:
template<typename I>
CErrorCode GetStringValue(const Iamp; Instance, CErrorCode(I::*pMethod)(char*, size_tamp;) const, std::stringamp; sValue)
{
std::string sTemporaryValue;
size_t nValueLength = sTemporaryValue.capacity();
sTemporaryValue.resize(nValueLength);
do
{
auto eErrorCode = (Instance.*pMethod)(const_cast<char*>(sTemporaryValue.c_str()), nValueLength);
if (eErrorCode == CErrorCode::BufferTooSmall)
{
sTemporaryValue.resize(nValueLength);
}
else
{
if (eErrorCode == CErrorCode::NoError)
{
sTemporaryValue.resize(nValueLength);
sValue = std::move(sTemporaryValue);
}
return eErrorCode;
}
}
while (true);
}
Итак, я сделал начальное resize
, потому что я не могу этого сделать после первого вызова (с тех пор это приведет к удалению содержимого, поскольку строка изначально пуста). Но resize
заполняет строку нулевыми символами, что меня расстраивает (я имею в виду, я знаю, что все равно буду заполнять сам). И тогда мне нужно resize
даже после успешного запуска, поскольку, если длина была на самом деле меньше, мне нужно уменьшить размер. Любые предложения, если это можно сделать более приятным способом?
Комментарии:
1. Я бы предложил другой шаблон проектирования: вместо передачи буфера и
size_t amp;
ссылки передайте указатель на функцию и непрозрачный указатель. Библиотека DLL вызывает указатель на функцию, передавая емуsize_t
требуемый для строки и непрозрачный указатель без изменений. Указатель функции заботится о выделении достаточногоchar []
количества и возвращает указатель на него. Нет необходимости иметь дело с неуклюжимBufferTooSmall
, и перенос этого в класс C становится тривиальным.2. Ну, но если я знаю, что большую часть времени все вызывающие будут использовать
std::string
, тогда я выполняю выделение плюс копирование при передаче в строку. Теперь у меня есть частичный шанс заполнить значение при первом вызове, таким образом, выполняя только внутренние выделения, выполняемые вstd::string
конструкторе по умолчанию.3. Нет закона, запрещающего реализацию функции, которая преобразует свой непрозрачный указатель в a
std::string *
, затем соответствующим образом изменяет его размер и возвращает указатель на данные строки, а затем передает указатель на эту функцию и astd::string
в вашу DLL.4. @RudolfsBundulis Другая вероятная проблема заключается в том, что вы передаете ссылку. Не гарантируется, что ссылочные параметры будут реализованы одинаково во всех модулях.
5. @SamVarshavchik Я это понимаю 🙂 Я подумаю об этом, спасибо.
Ответ №1:
Поскольку вы используете библиотеки DLL, я понимаю, что вы работаете в Windows, поэтому я собираюсь предложить решение для Windows.
Что ж, проблема безопасной передачи строки через границы модуля уже решена в Windows с помощью распределителя памяти COM и BSTR
.
Итак, вместо передачи указателя на строку и указателя размера и проверки, достаточно ли велик выделенный буфер вызывающего, и если нет, возвращает требуемый размер и т. Д. мне кажется намного проще просто вернуть BSTR
s из DLL.
BSTR
файлы выделяются с помощью распределителя COM, поэтому вы можете распределять и освобождать их по разным границам модуля.
Существуют также хорошие классы-оболочки C вокруг a BSTR
, например _bstr_t
, или ATL CComBSTR
. Вы можете использовать их на сайте вызывающего.
Как только у вас есть BSTR
экземпляр, возможно, должным образом завернутый в удобную оболочку RAII на сайте вызывающего, вы можете легко преобразовать его в std::wstring
.
Или, если вы хотите использовать кодировку Unicode UTF-8 и std::string
, вы можете преобразовать из BSTR
собственной кодировки UTF-16 в UTF-8, используя WideCharToMultiByte()
(эта статья в журнале MSDN может пригодиться).
Бонусное чтение: полное руководство Эрика по семантике BSTR
Постскриптум
Если вы не хотите использовать этот BSTR
тип, вы все равно можете использовать общий распределитель памяти, предоставляемый COM: например, вы можете выделить свою (UTF-8) строковую память, используя CoTaskMemAlloc()
в DLL, и освободить ее, используя CoTaskMemFree()
на сайте вызывающего.
Комментарии:
1. Спасибо за ответ. Я хорошо знаю COM, но я не привязан к Windows, для Linux также будет версия общего объекта. Итак, моей первоначальной целью было сделать это с помощью стандартных методов C / C .