win api readConsole()

5

我正在尝试使用Win API的ReadConsole(...),并且我想传递一个分隔符字符来停止从控制台输入。 以下代码可以工作,但它只会在\r\n上停止读取输入。 例如,我想让它在'.'上停止读取控制台输入。

void read(char *cIn, char delim)
{
    HANDLE hFile;
    DWORD charsRead;
    DWORD charsToRead = MAX_PATH;
    CONSOLE_READCONSOLE_CONTROL cReadControl;

    cReadControl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);
    cReadControl.nInitialChars = 0;
    cReadControl.dwCtrlWakeupMask = delim;
    cReadControl.dwControlKeyState = NULL;

    DWORD lpMode;



//    char cIn[MAX_PATH];    //-- buffer to hold data from the console

    hFile = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
                    FILE_SHARE_WRITE | FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, 0, NULL);

    GetConsoleMode(hFile,&lpMode);
//    lpMode &= ~ENABLE_LINE_INPUT;   //-- turns off this flag
//    SetConsoleMode(hFile, lpMode);  //-- set the mode with the new flag off

    bool read = ReadConsole(hFile, cIn, charsToRead * sizeof(TCHAR), &charsRead, &cReadControl);
    cIn[charsRead - 2] = '\0';
}

我知道有其他简单的方法来做这件事,但我只是想了解一些Win API函数以及如何使用它们。

谢谢。


如果您想要完全控制控制台输入并获取有关任何按键按下的信息,只需在按下时(但不等待VK_RETURN)使用ReadConsoleInput而不是ReadConsole - RbMm
@RbMm 谢谢,这很有帮助。 - dmaelect
2个回答

14

我看到了这个问题并认为它很简单,但花了最后30分钟试图弄清楚并最终有了一些东西。

dwCtrlWakeupMaskCONSOLE_READCONSOLE_CONTROL 中的文档非常差。MSDN说:“用于发出读取完成信号的用户定义控制字符。”,但是为什么它被称为 mask?为什么它是一个 ULONG 而不是像 TCHAR 这样的东西?我尝试输入字符和宽字符,但没有任何反应,所以故事背后肯定还有更多内容。

我在网上搜索那个特定的变量,并找到了这个链接:https://groups.google.com/forum/#!topic/golang-codereviews/KSp37ITmcUg。这是一个随机的 Go 库编码者寻求帮助的问题,答案是 tab 是 1 << '\t'。我尝试了一下,它起作用了!

因此,对于未来的网络搜索者,dwCtrlWakeupMask 是 ASCII 控制字符的位掩码,将导致 ReadConsole 返回。您可以将许多 1 << ctrl_char 进行按位或操作,但它不能是任意字符,因为它是一个32位值中的掩码,只有字符1-31(包括)是可行的(这个组别被称为控制字符,它包括诸如制表符、退格键、铃声等不代表可打印字符的东西)。

因此,这个掩码:

cReadControl.dwCtrlWakeupMask = (1 << '\t') | (1 << 0x08);

当按下制表符 (\t) 或退格键 (0x08) 时,将导致 ReadConsole 返回。

ctrl+some_ascii_value 表示的字符是该字母在英文字母表中的数字位置,从 a == 1 开始。因此,ctrl+d4ctrl+z26

因此,当用户按下 ctrl+dctrl+z 时,将返回结果:

cReadControl.dwCtrlWakeupMask = (1 << 4) | (1 << 26);

请注意,Linux终端驱动程序在用户按下ctrl+d时也会在read上返回,因此这可能是一个很好的兼容性问题。
我认为这个参数的目的是允许在处理输入模式下更容易地进行制表符自动完成;否则,您必须关闭处理过的输入并逐个处理键才能做到这一点。现在您不必了...虽然说实话,对于交互式程序,我仍然喜欢用ReadConsoleInput获取我的输入,因为你可以更好地控制它。
但是,虽然有很多其他方法可以实现您想要的内容,但在此处使用“.”作为分隔符是不可能的,因为它的值>= 32,因此您需要自己处理它...了解这样做的原理对我来说很有趣,而且网络资源很稀缺,所以我写了这篇文章供日后参考。
请注意,这似乎在wineconsole中无法正常工作,因此请确保您在真正的Windows系统上测试它。
现在,“dwControlKeyState”实际上是由该函数设置的。您传入的值将被忽略(至少我所知道的),但是在函数返回给定标志时,您可以检查它。例如,在调用ReadConsole并敲击键后,如果您的数字锁定打开,则为32。如果数字锁定打开并按shift + tab,则为48(而且数字锁定打开)。所以您需要在函数返回后测试它。
我通常喜欢MSDN文档,但我认为他们完全没有解释这个参数!

1
@ilw 实际上,空格是代码32,所以稍微有点大了,无法放在那里。 - Adam D. Ruppe
我要重新使用这个函数,并想写更多关于它如何工作的细节。当你传递一个CONSOLE_READCONSOLE_CONTROL结构体时,nInitialChars成员会将一些输入缓冲区保留在控制台的编辑缓冲区中。当它返回时,输入将用字符替换缓冲区中光标位置的字符,并将其余的编辑行保留在其中。您可以扫描并编辑缓冲区,然后再次调用并设置nInitialChars成员以供下一个操作使用。但是,如果您在一行的中间进行制表符补全,则无法将光标保留在中间。 - Adam D. Ruppe
谁能帮我解决一个问题,为什么如果请求缓冲区大小太大(如65000个字节),ReadConsole、WriteConsole、ReadFile(console_in)和WriteFile(console_out)会失败呢?我本来想它只是尽可能多地读取或写入。而posix的替代方式_write和_read却完美运行。 - user9599745
@JaveneCPPMcGowan 文档中没有说明,但我看到有人报告某些函数有64kb的限制。由于它是宽字符,这意味着最多支持32k个元素。但你绝对可以使用比可用空间更大的缓冲区——这通常是你所做的,就像在posix上一样。只需尝试一个较小的缓冲区并查看发生了什么即可。 - Adam D. Ruppe
@Adam D. Ruppe 谢谢。虽然没有解决我的问题,但还是谢谢你?我的问题在于需要一致性。MSDN 应该记录这样的错误。而且其他人没有抱怨意味着有些不对劲。显然,posix 函数是设计没有这些限制的。可能在 posix 模式下打开终端会解决我的问题。 - user9599745
@Adam D. Ruppe t GetLastError() == 8。我看到的唯一匹配它的错误代码是ERROR_NOT_ENOUGH_MEMORY。使用FILE_FLAG_NO_BUFFERING、FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH或FILE_FLAG_WRITE_THROUGH打开控制台并不能解决我的内存不足问题。然后,FILE_FLAG_POSIX_SEMANTICS也不能解决我大量读写控制台的问题。 - user9599745

0
你会觉得这段代码很荒谬。这很可能是唯一的方法。如果你以后必须要适应使用 ReadFile,那么这是唯一不会消耗更多输入的方式。
大多数情况下,你实际上并不需要使用 ReadConsole,而是想要在标准输入句柄上使用 ReadFile,但我偏离了主题。
char *cInptr = cIn;
do {
    bool read = ReadConsole(hFile, cInptr, sizeof(TCHAR), &charsRead, &cReadControl);
    if (read) cInptr += charsRead;
} while (read && charsRead > 0 && cInptr[-1] && cInptr[-1] != '.');

我可能在循环中有太多的测试,因为我很谨慎。我不想查找所有谓词来确定哪些是由ReadConsole的合同所暗示的。


ReadConsole相比ReadFile具有重要优势:它可以读取Unicode字符。使用ReadConsoleW和WriteConsoleW,您可以获得宽字符,从而避免了其他读取方法中的全部代码页麻烦。 - Adam D. Ruppe
根据我的经验,当出现这个问题时,stdin 通常连接到一个管道。或许在这种情况下不是这样。 - Joshua
请注意,ReadFile是一个二进制读取器,其计数以字节为单位,而不是像ReadConsole一样以字符为单位。 - David A. Gray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接