запись непосредственно во внутренние буферы std :: string - PullRequest
34 голосов
/ 25 июня 2009

Я искал способ вставить некоторые данные в строку через границу 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], но это правильный способ сделать это, учитывая, что вы думаете, что string :: data () == & string [0]? Это кажется противоречивым.

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

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

Ответы [ 8 ]

23 голосов
/ 25 июня 2009

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

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

Это, конечно, потребует двойного копирования данных, что немного неэффективно.

19 голосов
/ 25 июня 2009

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

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

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

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

9 голосов
/ 11 июля 2015

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

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

5 голосов
/ 25 февраля 2016

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

std::string stringToFillIn(100, 0);
FunctionInDLL(stringToFillIn.data(), stringToFillIn.size());
3 голосов
/ 25 июня 2009

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

2 голосов
/ 10 сентября 2014

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

#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());
}
0 голосов
/ 25 июня 2009

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

0 голосов
/ 25 июня 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...