How to get single keystroke in D2 (Phobos)?

2020-03-13 09:07发布

问题:

Is there a simple, cross-platform way to get a single keystroke in D2 using Phobos?

For instance, a "Press any key to continue..." prompt, or a Brainfuck interpreter.

All the methods I've tried require an Enter keypress before passing the input (getchar(), for instance).

回答1:

I've done some research on the matter, and I've found that, while the Phobos library under D 1.0 had exactly what you need in the form of std.c.stdio.getch(), D 2.0 lacks this function. None of the other standard input functions in Phobos appear to have the behavior you want.

As I understand it, this is because the desired behavior (that is, getting a single character without the need for an Enter keypress) is rather nonstandard and has to be implemented in relatively ugly, platform-specific ways. (In its initial form, the function getch existed in C's <conio.h>, a DOS-specific header that has become somewhat of a de facto cross-platform standard despite not being part of the standard C library.) Evidently the maintainers of the Phobos runtime library decided to strip out this particular bit of backwards-compatible functionality in the name of a cleaner library, but at the expense of this functionality.

Manual declaration

Reportedly, you can get around this missing function declaration by adding this to your source file:

extern (C) int getch();

However, I have found that this produces a linker error, suggesting that the function was removed from the runtime library entirely, rather than merely having its declaration removed from std.c.stdio. It's certainly worth a try—it might happen to work on your system and compiler, I really don't know.

Edit 2: This actually seems to work on Windows; it failed for me on the Linux side. It appears that DMD under Windows first links to the Phobos/D runtime (phobos.lib), then the C runtime (snn.lib); however, on Linux, DMD links to one runtime library that supplies both parts. This difference appears to cause linkage to undeclared functions (getch among them) to only work on Windows. If Windows is the only platform you're concerned with, this solution is probably suitable. If you need more cross-platform compatibility, read on.

ncurses

Another possibility is to use the ncurses library. It implements a getch function that will definitely do what you want—provided that you're cool with finding D bindings for the library or just using the C interface. Be aware that it requires a smidgen more setup than just calling the function you want; this thread has more information on the matter.

D 1.0

Now, for some considerably uglier solutions. Using D 1.0 would allow you to find what you need in the Phobos standard library—but this obviously entails using the older, crustier version of the language and I personally do not find the lack of one console IO function in the standard library to be justification for using the old version of D.

I believe that Tango also lost its getch declaration (under tango.stdc.stdio) over the switch to D 2.0, but my knowledge of Tango is extremely limited, so I may be wrong.

Write it yourself

If you're determined, you could just write your own getch. I wasn't able to find a cross-platform C implementation of getch using Google Code Search, which leaves me pessimistic about the likelihood of a relatively simple, 10-line-or-so function implementation that could simply be adapted to D.

On the other hand, Walter Bright—you know, the guy who designed the D language—provides a D implementation of just this sort of function here. However, even this appears to be somewhat outdated, because one of the symbols, cfmakeraw, is undefined under the current version of the DMD2 compiler. However, it's really close to being a workable solution.



回答2:

The simplest solution that works with D2 on Windows:

import std.stdio : writefln;

extern(C) int kbhit();
extern(C) int getch();

void main()
{
    while(!kbhit())
    {
        // keep polling
        // might use Thread.Sleep here to avoid taxing the cpu.
    }

    writefln("Key hit was %s.", cast(char)getch());
}

It might even work with D1, but I haven't tried it out.

Here's a Linux version, modified from Walter's post:

import std.stdio : writefln;
import std.c.stdio;
import std.c.linux.termios;

extern(C) void cfmakeraw(termios *termios_p);

void main() 
{
    termios  ostate;                 /* saved tty state */
    termios  nstate;                 /* values for editor mode */

       // Open stdin in raw mode
       /* Adjust output channel        */
    tcgetattr(1, &ostate);                       /* save old state */
    tcgetattr(1, &nstate);                       /* get base of new state */
    cfmakeraw(&nstate);
    tcsetattr(1, TCSADRAIN, &nstate);      /* set mode */

      // Read characters in raw mode
    writefln("The key hit is %s", cast(char)fgetc(stdin));

       // Close
    tcsetattr(1, TCSADRAIN, &ostate);       // return to original mode
}