零终止字符串的 C++ LPSTR 和字符串问题

Posted

技术标签:

【中文标题】零终止字符串的 C++ LPSTR 和字符串问题【英文标题】:C++ LPSTR and string trouble with zero-terminated strings 【发布时间】:2015-12-10 11:53:12 【问题描述】:

我正在使用来自WinapiGetOpenFileName 函数,并且我正在将过滤器应用于选择文件对话框。

完美运行:

LPSTR mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter;

if(GetOpenFileName(&ofn))
...

THIS 失败(对话框打开但未应用过滤器):

string mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter.c_str();

if(GetOpenFileName(&ofn))
...

我需要使用std:string,因为我通过参数获取文件扩展名,这种类型有助于连接,但我遇到了不兼容问题...

如果它按预期工作,这将是我的代码(失败与前面的示例相同):

const char * ext = &(4:); //Ampersand parameter (from CA Plex) It contains "PDF"
string mfilter = "Filter\0*." + ext + "\0"; //Final string: Filter\0*.PDF\0;
ofn.lpstrFilter = mfilter.c_str();

当我使用这个方法时,我得到了运行时异常:

string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');

ofn.lpstrFilter = mf.c_str();

【问题讨论】:

如果您设置断点并检查 mfilter 缓冲区,您会看到什么? (崩溃前...) 每当我必须使用 MFC/WinAPI 时,我总是使用 MSism 方式。与之抗争是不值得的。 如果你不显示minimal reproducible example,我怀疑这会不会有成效。我猜你修改了字符串,或者销毁了它,从而使指针无效。 事实证明是:***.com/questions/34223930/… 【参考方案1】:

string mfilter = "Filter\0*.PDF\0";

您正在调用 std::string 构造函数,它在第一个 \0 处终止字符串。

以下代码:

string mfilter = "Filter\0*.PDF\0";
cout << "string:" << mfilter << "   len: " << mfilter.length() << endl;

打印

string: Filter   len: 6

字符串仅在第一个 \0 终止符之前构造。难道字符串只由单词“Filter”组成。

【讨论】:

添加示例。谢谢。 std::string 中嵌入 NUL 的使用是否已定义?如果事实证明你真的不能用std::string 做到这一点,(在下面回答你的问题)你将需要使用良好的老式内存分配和strcpy()... @andlabs 我不知道你说我需要定义“嵌入式 NUL 的使用”是什么意思 您不需要定义任何东西,C++ 标准可以。我发现的一切都表明是的,您可以将 NUL 与std::string 一起使用。您需要显示完整代码或使用调试器。 我无法使用调试器,因为我正在使用一个不允许它的 IDE(是的,难以置信)。我不介意展示我的完整代码,但问题出在我所说的地方,当试图对那个 Winapi 函数形成一个“过滤器”时,它不理解它并且做它想做的任何事情,这在 Windows API 中很常见。 【参考方案2】:

GetOpenFileName 函数使用 TCHAR,如果使用 UNICODE 字符集,TCHAR 将变为 WCHAR。

这是一个例子:

std::wstring getOpenFileName(HWND hWnd, const std::wstring& sFilter)

    wchar_t buffer[MAX_PATH] = L"";

    OPENFILENAMEW ofn = 0;

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFilter = sFilter.c_str();
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = buffer;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;

    if( !::GetOpenFileNameW( &ofn ) )
        return L"";

    return buffer;

如果您想基于std::wstring 参数化lpstrFilter,您可以使用wstring::c_str() 来获得LPCTSTR,在UNICODE 的情况下为const wchar*

重要提示:问题在于采用const wchar*std::wstring 构造函数假定输入是C 字符串。 C 字符串以 '\0' 终止,因此在到达 '\0' 字符时停止解析。为了弥补这一点,您需要使用 构造函数,该构造函数接受两个参数,一个指向 char 数组的指针和一个长度。 您也可以使用string::push_back() 方法附加NULL。

std::wstring sFilter = L"PDF Files";
sFilter.push_back('\0');
sFilter.append(L"*.pdf");
sFilter.push_back('\0');

【讨论】:

我唯一遇到的问题是动态设置ofn.lpstrFilter。文件扩展名是通过参数传递的,我需要在对话框打开时将该扩展名过滤器应用于对话框。正如我所看到的,您将过滤器设置为文字字符串,这就是第一个示例显示的对我有用的方式,但这不是解决方案。您将如何使用始终保持空终止符的参数动态设置过滤器字符串?谢谢。 这不是答案。从问题中的工作示例可以明显看出,OP 仅使用 ANSI 函数。 这并不明显等。在 2015 年使用 ASCII 字符集而不使用 UNICODE 是一种犯罪。 我已经更新了答案来描述参数化 lpstrFilter 的方式 Asker 出现运行时错误。无论 ANSI 在 2015 年是否构成犯罪,您都需要解决实际问题。【参考方案3】:
string mfilter = "Filter\0*.PDF\0";

这会调用一个std::basic_string 构造函数,该构造函数使用一个以空字符结尾的字符串。它将停止解析 "Filter" 处的字符串文字。

试试这个:

string mfilter( "Filter\0*.PDF", 13 ); // need double null at end

这调用了一个std::basic_string构造函数,它使用“s指向的字符串的第一个count个字符。s可以包含空字符。”

如果您经常遇到此问题,您必须自己计算字符数,或者编写包装代码。


相关:std::basic_string constructors.


至于你的运行时错误:

string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');

append()does not have an overload for a single character type。您可能遇到了const CharT* s 重载,带有一个空指针。

使用append( 1, '\0' )append( "", 1 ),其中任何一个都应附加一个空字节。

【讨论】:

string mfilter( "Filter\0*.PDF", 12 ); 需要改为 string mfilter( "Filter\0*.PDF\0", 13 );,因为 GetOpenFileName() 期望过滤器以 double 为空终止。 c_str() 将为您添加一个空终止符,但数据需要包含另一个空终止符。 @RemyLebeau:到目前为止,我很高兴能够在我的职业生涯中很好远离 WinAPI。 ;) 感谢您的提醒。但是由于字符串文字末尾带有一个隐含的零,因此我不必加一,只需增加计数即可。 (或者c_str() 是否跳过添加呢?) 计数 12 不包括隐式 null。无论最终的 null 是隐式还是显式,您都必须将计数增加到 13。 c_str() 总是在 std::string 实际持有的任何内容的末尾包含自己的 null。

以上是关于零终止字符串的 C++ LPSTR 和字符串问题的主要内容,如果未能解决你的问题,请参考以下文章

编程题字符集合

如何将零终止字节数组转换为字符串?

从 CString 设置 LPSTR

强制 format_to_n 使用终止零

编组 LPSTR 和浮动时的不平衡堆栈

在向量循环 C++ 中终止字符串输入