Запись непосредственно в std::string внутренние буферы

Я искал способ набить некоторые данные в строку через границу DLL. Поскольку мы используем разные компиляторы, все наши интерфейсы dll просты char *.

Есть ли правильный способ передать указатель в функцию dll, чтобы он мог напрямую заполнить буфер строки?

string stringToFillIn(100, '\0');
FunctionInDLL( stringToFillIn.c_str(), stringToFillIn.size() );   // definitely WRONG!
FunctionInDLL( const_cast<char*>(stringToFillIn.data()), stringToFillIn.size() );    // WRONG?
FunctionInDLL( &stringToFillIn[0], stringToFillIn.size() );       // WRONG?
stringToFillIn.resize( strlen( stringToFillIn.c_str() ) );

Тот, который выглядит наиболее перспективным, есть & stringToFillIn [0], но это правильный способ сделать это, учитывая, что вы думаете, что строка:: data() == & string [0]? Это кажется непоследовательным.

Или лучше усвоить дополнительное выделение и избежать вопроса:

vector<char> vectorToFillIn(100);
FunctionInDLL( &vectorToFillIn[0], vectorToFillIn.size() );
string dllGaveUs( &vectorToFillIn[0] );

Ответ 1

Я не уверен, что стандарт гарантирует, что данные в std::string сохраняются как char*. Самый портативный способ, который я могу придумать, - использовать std::vector, который, как гарантируется, будет хранить свои данные в непрерывном блоке памяти:

std::vector<char> buffer(100);
FunctionInDLL(&buffer[0], buffer.size());
std::string stringToFillIn(&buffer[0]);

Конечно, это потребует, чтобы данные копировались дважды, что немного неэффективно.

Ответ 2

После долгих чтений и копаний я обнаружил, что string::c_str и string::data могут законно возвращать указатель на буфер, который не имеет ничего общего с тем, как хранится сама строка. Возможно, что строка хранится в сегментах, например. Запись в эти буферы не влияет на содержимое строки.

Кроме того, string::operator[] не следует использовать для получения указателя на последовательность символов - его следует использовать только для одиночных символов. Это связано с тем, что эквивалентность указателя/массива не поддерживается строкой.

Что очень опасно в этом, так это то, что он может работать в некоторых реализациях, но затем внезапно обрывается без видимой причины в будущем.

Следовательно, единственный безопасный способ сделать это, как уже говорили другие, - это избегать любых попыток напрямую записать в строковый буфер и использовать вектор, передать указатель на первый элемент и затем назначить строку из вектора при возврате из Функция DLL.

Ответ 3

В С++ 98 вы не должны изменять буферы, возвращаемые string::c_str() и string::data(). Кроме того, как объяснено в других ответах, вы не должны использовать string::operator[] для получения указателя на последовательность символов - его следует использовать только для одиночных символов.

Начиная с С++ 11, строки используют непрерывную память, поэтому вы можете использовать &string[0] для доступа к внутреннему буферу.

Ответ 4

Пока С++ 11 дает непрерывные гарантии памяти, в производственной практике этот "хакерский" метод очень популярен:

std::string stringToFillIn(100, 0);
FunctionInDLL(stringToFillIn.data(), stringToFillIn.size());

Ответ 5

Я бы не стал создавать std::string и отправлять указатель на внутренние буферы через границы dll. Вместо этого я бы использовал простой буфер char (статически или динамически размещаемый). После того, как вызов к dll вернется, я позволю std::string принять результат. Просто интуитивно кажется неправильным позволять вызываемым абонентам писать во внутренний буфер классов.

Ответ 6

Учитывая комментарий Патрика, я бы сказал, что это нормально и удобно/эффективно напрямую писать в std::string. Я бы использовал &s.front(), чтобы получить char *, как в этом примере:

#include "mex.h"
#include <string>
void mexFunction(
    int nlhs,
    mxArray *plhs[],
    int nrhs,
    const mxArray *prhs[]
)
{
    std::string ret;
    int len = (int)mxGetN(prhs[0]);
    ret.reserve(len+1);
    mxGetString(prhs[0],&ret.front(),len+1);
    mexPrintf(ret.c_str());
}

Ответ 7

Стандартная часть std::string - это API, а некоторые - поведение, а не структура памяти реализации.

Поэтому, если вы используете разные компиляторы, вы не можете предполагать, что они одинаковы, поэтому вам нужно будет переносить фактические данные. Как уже говорили другие, перенесите символы и вставьте новый std::string.

Ответ 8

Вы все уже обращались к проблеме соприкосновения (т.е. она не гарантировалась соприкосновением), поэтому я просто упомянул точку выделения/освобождения. У меня были проблемы в прошлом, когда я выделил память в dll (т.е. Вернул dll строку), которые вызвали ошибки при уничтожении (вне DLL). Чтобы исправить это, вы должны убедиться, что ваш распределитель и пул памяти согласованы по границе dll. Это избавит вас от времени отладки;)

Ответ 9

Вы можете использовать буфер символов, выделенный в unique_ptr вместо вектора:

// allocate buffer
auto buf = std::make_unique<char[]>(len);
// read data
FunctionInDLL(buf.get(), len);
// initialize string
std::string res { buf.get() };

Вы не можете записывать напрямую в строковый буфер, используя упомянутые способы, такие как & str [0] и str.data():

#include <iostream>
#include <string>
#include <sstream>

int main()
{
    std::string str;
    std::stringstream ss;
    ss << "test string";
    ss.write(&str[0], 4);       // does not working
    ss.write(str.data(), 4);    // does not working
    std::cout << str << '\n';
}

Живой пример.