Безопасное копирование строк через границы DLL

#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 * , затем соответствующим образом изменяет его размер и возвращает указатель на данные строки, а затем передает указатель на эту функцию и a std::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 .