直接写入 std::string 内部缓冲区

问题描述 投票:0回答:10

我正在寻找一种跨 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] );
c++ string
10个回答
26
投票

我不确定标准是否保证

std::string
中的数据存储为
char*
。我能想到的最便携的方法是使用
std::vector
,它保证将其数据存储在连续的内存块中:

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

这当然需要将数据复制两次,效率有点低。


22
投票

更新(2021): C++11 解决了这个问题,这里表达的担忧不再相关。

经过大量阅读和挖掘后,我发现

string::c_str
string::data
可以合法地返回指向缓冲区的指针,该指针与字符串本身的存储方式无关。 例如,字符串可能存储在段中。 写入这些缓冲区会对字符串的内容产生未定义的影响。

此外,

string::operator[]
不应该用于获取指向字符序列的指针 - 它应该仅用于单个字符。 这是因为指针/数组等价不适用于字符串。

这样做非常危险的是,它可以在某些实现上工作,但随后在未来的某个日期突然无明显原因地崩溃。

因此,正如其他人所说,执行此操作的唯一安全方法是避免任何直接写入字符串缓冲区并使用向量的尝试,将指针传递给第一个元素,然后在返回时从向量分配字符串dll 函数。


12
投票

在 C++98 中,您不应更改

string::c_str()
string::data()
返回的缓冲区。另外,正如其他答案中所解释的,您不应该使用
string::operator[]
来获取指向字符序列的指针 - 它应该仅用于单个字符。

从 C++11 开始,字符串使用连续内存,因此您可以使用

&string[0]
访问内部缓冲区。


8
投票

只要 C++11 提供连续内存保证,在生产实践中这种“hacky”方法就非常流行:

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

3
投票

考虑到帕特里克的评论,我想说,直接写入 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());
}

3
投票

我不会构造一个

std::string
并跨 dll 边界传送指向内部缓冲区的指针。相反,我会使用简单的
char
缓冲区(静态或动态分配)。对 dll 的调用返回后,我会让
std::string
接管结果。让被调用者在内部类缓冲区中写入只是直观上感觉错误。


2
投票

您可以使用

char
中分配的
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.read(&str[0], 4);       // doesn't work
    ss.read(str.data(), 4);    // doesn't work
    std::cout << str << '\n';
}

现场示例


0
投票

你们都已经解决了连续性问题(即不保证连续),所以我只提一下分配/释放点。 我过去遇到过一些问题,我在 dll 中分配了内存(即让 dll 返回一个字符串),这些问题在销毁时(在 dll 之外)导致了错误。 要解决此问题,您必须确保分配器和内存池在 dll 边界上保持一致。 它会节省你一些调试时间;)


0
投票

std::string
的标准部分是API和一些行为,而不是实现的内存布局。

因此,如果您使用不同的编译器,您不能假设它们是相同的,因此您需要传输实际数据。正如其他人所说,传输字符并推入新的

std::string


0
投票

C++17 起:“basic_string 是一个连续的容器” 所以你可以这样做:

DMAFlash::datinfo_s datinfo;
string appname = "none";
uint8_t err = _dma.getDataInfo("appselect", datinfo); 
if (err != 2) {
    if (err>0) return 1;
    appname.resize(datinfo.datsize);
    if (_dma.readData("appselect", (void*)appname.c_str())) return 1;
}
printf("Current app loaded: %s\n", appname.c_str());
fflush(stdout);
© www.soinside.com 2019 - 2024. All rights reserved.