Запись непосредственно в 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';
}
Живой пример.