如何在管道上使用 WriteFile 修复乱码文本?

Posted

技术标签:

【中文标题】如何在管道上使用 WriteFile 修复乱码文本?【英文标题】:How to fix garbled text with using WriteFile on a pipe? 【发布时间】:2009-03-15 12:21:35 【问题描述】:

我正在制作一个 Win32 应用程序,它通过命名管道将字符串从一个进程发送到另一个进程。但是,在管道上调用 ReadFile 的进程会获取其中包含一些乱码数据的字符串。它返回正确写入的字节数,但字符串的最后8个字符左右是乱码。

这是创建管道并写入管道的代码:

myPipe = CreateNamedPipe(L"\\\\.\\pipe\\testpipe", PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT, 10, 512, 512, 10, NULL);
TCHAR title[128];
GetWindowText(foundHwnd, title, 128);
wstring windowTitle(title);
vector<wstring> splitVec;
boost::split(splitVec, windowTitle, boost::algorithm::is_any_of(wstring(L"|")));
WriteFile(myPipe, splitVec[0].c_str(), splitVec[0].size(), &wrote, NULL);

这是读取它的代码:

if (WaitNamedPipe(L"\\\\.\\pipe\\testpipe", 5000) == 0) 
    MessageBox(NULL, L"Unable to wait for pipe", L"Error", MB_OK);
    return false;


myPipe = CreateFile(L"\\\\.\\pipe\\testpipe", GENERIC_READ, FILE_SHARE_READ, NULL,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (myPipe == INVALID_HANDLE_VALUE) 
    MessageBox(NULL, L"Unable to open pipe", L"Error", MB_OK);
    return false;

    // Other code here...
    TCHAR buf[512];
    DWORD read;
    success = ReadFile(myPipe, buf, 512, &read, NULL);
    if (read > 0)
        MessageBox(NULL, buf, L"Got Data", MB_OK);

当显示 MessageBox 时,字符串的结尾是乱码,我不知道为什么。有什么想法吗?

谢谢!

【问题讨论】:

【参考方案1】:

我认为这里的关键是确保您的字符串是空终止的,并且您也发送终止字符。如果通信是同步的,或者如果您在PIPE_READMODE_MESSAGE 中设置它,则不必发送整个缓冲区。当读取指定数量的字节或在管道的另一端完成写入操作时,ReadFile 将返回。我相信“乱码”文本在管道客户端的读取缓冲区中确实是垃圾,并且因为您没有传输字符串终止字符,所以它包含在发送到消息框的文本中。要么在发送之前清除你的读取缓冲区,要么在消息中发送字符串终止字符,我认为它可以在没有发送完整缓冲区的开销的情况下工作。

这里是来自 MSDN 的 sample client。请注意客户端如何准确发送消息 + 1 中的字符数(包括终止字符)并接收到大小为 512 的固定缓冲区。如果您查看 server example,您将看到相同的模式。

【讨论】:

谢谢!我现在在发送一半字符串时遇到问题。我需要 (lstrlen(..)+1) * sizeof(TCHAR),如示例所示。 啊,是的。多字节字符。我什至没有想到这一点。 很好,我更新了我的答案,用 *sizeof(TCHAR) 修复它【参考方案2】:

对您发布的代码的一些观察:

您需要 1) 显式发送空终止字节,或 2) 将一个附加到您读取的数据中。 由于您正在读取 512 个字节,因此您也应该准确发送 512 个字节。 您可以发送可变长度的字符串,方法是先发送字符串的大小,然后发送那么多字节。这样,当您读取数据时,您将知道要为实际字符串读取多少字节。 一旦您通过管道发送 2 件内容,您所做的事情就会出现问题,并且您在第一次阅读时阅读了您真正想要的内容。 如果您只通过管道发送 1 个东西,您可以保留您的代码,但在写入管道时发送 size() + 1。 ReadFile / WriteFile 用于发送二进制数据,不一定是字符串。所以你可以创建一个名为 ReadString 和 WriteString 的函数来实现我关于先读/写大小然后才是实际字符串的建议。

试试这样的:

这是创建管道并写入管道的代码:

myPipe = CreateNamedPipe(L"\\\\.\\pipe\\testpipe", PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT, 10, 512, 512, 10, NULL);
TCHAR title[128];
GetWindowText(foundHwnd, title, 128);
WriteFile(myPipe, title, 128*sizeof(TCHAR), &wrote, NULL);//<---In this case we are sending a null terminated string buffer.

这是读取它的代码:

if (WaitNamedPipe(L"\\\\.\\pipe\\testpipe", 5000) == 0) 
    MessageBox(NULL, L"Unable to wait for pipe", L"Error", MB_OK);
    return false;


myPipe = CreateFile(L"\\\\.\\pipe\\testpipe", GENERIC_READ, FILE_SHARE_READ, NULL,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (myPipe == INVALID_HANDLE_VALUE) 
    MessageBox(NULL, L"Unable to open pipe", L"Error", MB_OK);
    return false;

    // Other code here...
    TCHAR buf[128];
    DWORD read;
    success = ReadFile(myPipe, buf, 128*sizeof(TCHAR), &read, NULL);
    if (read > 0)
        MessageBox(NULL, buf, L"Got Data", MB_OK);

【讨论】:

谢谢,我没有意识到管道需要准确的长度。以为他们会为我做那些事情。它现在正在工作!【参考方案3】:

在编写通用函数以从在命令提示符处执行的任何进程中读取标准输出时,我遇到了“管道中的垃圾”这个问题。因此,我无法更改写入管道的内容(正如通常建议的那样),我只能更改读取端。于是,我“作弊”了。

如果管道数据没有以空终止符结尾,我将最后一个字符替换为一个!它似乎对我有用。我完美地看到了这项工作,在我的数据块末尾有空值和没有空值的地方。

我担心我可能会丢失一个关键的最后一个字符(你可能会这样!),但就我的直接目的而言,这并没有发生。在某些情况下,您可能会考虑添加 null 而不是替换结尾...

这里是代码片段:

const unsigned int MAX_PIPE_PEEKS = 100;
DWORD bytesInPipe = 0;
unsigned int pipePeeks=0;
while( (bytesInPipe==0) && (pipePeeks < MAX_PIPE_PEEKS) )

    bSuccess = PeekNamedPipe( g_hChildStd_OUT_Rd, NULL, 0, NULL, 
                              &bytesInPipe, NULL );
    if( !bSuccess ) return bSuccess;  // Bail on critical failure                
    ++pipePeeks;

if( bytesInPipe > 0 )

    // Read the data written to the pipe (and implicitly clear it)
    DWORD dwRead; 
    CHAR *pipeContents = new CHAR[ bytesInPipe ];    
    bSuccess = ReadFile( g_hChildStd_OUT_Rd, pipeContents, 
                         bytesInPipe, &dwRead, NULL );
    if( !bSuccess || dwRead == 0  )  return FALSE;  // Bail on critical failure              

    // "Cheat" - eliminate garbage at the end of the pipe
    if( pipeContents[ bytesInPipe ] != '\0' )
        pipeContents[ bytesInPipe ] = '\0';

更新:

经过进一步测试,我发现这不太可靠(令人震惊,是吧?)。我认为对于一个相对简单的解决方案,我走在正确的轨道上。让这个快速补丁工作的任何想法?

【讨论】:

以上是关于如何在管道上使用 WriteFile 修复乱码文本?的主要内容,如果未能解决你的问题,请参考以下文章

fprintf vs WriteFile 写入管道:无法从所有管道读取

使用 ReadFile 和 WriteFile 时出现死锁

如何在 HTML 中使用 fs.writeFile(使用 node.JS)?

WriteFile() 块(通过命名管道从 C++ 客户端写入 C# 服务器)

客户端断开连接后如何使命名管道不忙?

如何修复特征联合和管道中的元组对象错误(使用 sklearn 时)?