Tuesday, January 04, 2005

Standard additions...

Okay, while I am not on the C/C++ committees, it has come to my attention over the more recent months and years that the core languages are simply not capable of handling modern devices - particularly dealing with PCs. If you search the archives of several of the many lists I subscribe to, you will frequently find programmers asking about how to use ANSI C/C++ to do:

1) Single keypress keyboard input.
2) Drawing graphics on the screen.
3) Printing to a printer.
4) Accessing USB devices (e.g. a scanner).
5) Responding to mouse movement.
(etc.)

Every single time, without fail, someone replies saying that these are not standard. I have, on occassion, jokingly made the suggestion that C was designed to output to a dot matrix or line printer. So, all of these discussions have me thinking about what to do. The result of all this thinking is that, because C is incapable of these things, people have to resort to using OS-level APIs. One of the major goals of C/C++ was to make it able to compile code for any platform. However, the ANSI standard is simply not cut out to handle devices that are portable to other platforms. Now, I realize embedded devices don't have the capability of a mouse, but think instead of the pen surface of a PDA as being a generic "coordinate input device". There are numerous scenarios where a "coordinate input device" would be useful even in an embedded environment. The keyboard is a "button device". A mouse button is a "button device". The screen (or a "window" in certain environments) is a "coordinate output device" (but can also be an input device). The printer is a "coordinate output device". For all intents and purposes, a "button device" could also be considered a "coordinate input device".

I think the major problem here is that the committee failed to realize that not only could they differentiate between a hosted environment and a non-hosted environment, they could also differentiate between a hosted environment with support for attached devices and a hosted environment without such support. So, how would someone go about defining a device extension standard that supports my idea of generic devices? Well, the first thing to do is add a set of functions to the standard that support the idea. My first rough stab at the prototypes would look something roughly like:

DEVLIST *devlistopen(const char *devicetype);
char *devlistread(DEVLIST *dlp, size_t device);
int devlistclose(DEVLIST *dlp);
DEV *devopen(DEVLIST *dlp, size_t device, void *openinfo);
int devread(DEV *dp, void *readinfo);
int devwrite(DEV *dp, void *writeinfo);
int devclose(DEV *dp);

That's pretty much it. Now you are thinking, "What in the world are those void pointers doing there?!" This is the beautiful part. At this point, ANSI hands off the responsibility of defining the openinfo, readinfo, and writeinfo data types and structures along with device types to another organization. So, say you come up with a new device for USB (let's say you invent the scanner) and want to get it standardized so C/C++ programmers can use it with ANSI-standard functions. The organization will assign your device with a generic device type and you pick a name for your brand of the device type using their online database. Then, you work with the organization to come up with a standard set of openinfo, readinfo, and writeinfo structures that are suitable for any PC scanner device to use. You will note that this method only allows for a single structure for each call. If you need another structure, you actually have two differing types of devices (e.g. mouse movement versus mouse buttons) and therefore require two separate interactions with the organization. The best part of this is that ANSI can leave themselves out of this end of things.

The only remaining issue is determining how to get your device into the compiler. Most compiler writers will likely develop some sort of plug-in style architecture for the device developers to utilize (e.g. DLLs for Windows-based compilers). As a programmer, all you will care about is making code and have it work the first time it compiles. Something like this might be used for determining the coordinate position of the mouse:

PC_MOUSEMOVE_openinfo pmo;
PC_MOUSEMOVE_readinfo pmr;
DEVLIST *dlp;
DEV *dp;


dlp = devlistopen("PC_MOUSEMOVE");
if (dlp != NULL)
{
dp = devopen(dlp, 0, &pmo);
devlistclose(dlp);
if (dp != NULL)
{
devread(dp, &pmr);
devclose(dp);
printf("Current mouse coordinates: %li, %li\n", pmr.x, pmr.y);
}
}

ANSI C/C++ only recognizes four devices: Files, stdin, stdout, and stderr. The concept of monitors, printers, scanners, telephones, modems, audio devices, video cards, the Internet (e.g. sockets), and even memory (in some respects) are all non-standard. All I ask for is a handful of functions to be added to the standard for hosted implementations that support the concept of devices.

See what I'm trying to get at here? There are only ANSI-standard calls being made (assuming you can imagine it as part of the standard - humor me if you can't). If ANSI doesn't want concepts like mice and keyboards in the standard, that's fine, but they need to give the rest of us a way to extend the standard that is acceptable by the standard.

2 comments:

  1. This is a nice general abstraction, but it ignores all the existing technology. There are already two nicely defined methods for getting at the keyboard, mouse, display, and printer.

    1. Microsoft's interface (MFC) provides access to the display and printer via the Device Context (CDC) object, and to the mouse and keyboard by adding handlers for the OnMouseMove, OnxxxButtonDown/Up, OnChar, OnKeyDown/Up messages.

    2. XWindows provides access to the display via the Graphics Context and functions that draw on the current GC. This is usually only used for a display (real or virtual), but I see no reason it shouldn't apply equally well to a printer. Similarly, XWindows provides access to the Keyboard and Mouse via XEvent messages.

    What you probably need to do is find an abstraction that handles the basic operations of these two interfaces (and maybe take a look at the MAC version as well).

    Aside from this, I like your approach. It provides a device-independent method of defining device-specific things. And it's easily extensible without further changes to the "usual" way of doing things. Namely, you can set FIONBLOCK on the device's handle (DP) and "poll" or select() on the device to wait for input from any of a list of devices.

    ReplyDelete
  2. "This is a nice general abstraction, but it ignores all the existing technology."

    But of course. That's how ANSI operates. They live in a completely different world from the rest of us, so I was merely obliging them :)

    As to DC's versus GC's - they are more or less the same thing. DC's are either display or printer (nothing to do with MFC) and I haven't really worked with GC's, but they don't sound that much different from DC's. This takes all of that and makes it very generic. For instance, a device could be a TCP/IP socket (networking), a scanner, a keyboard, a monitor, a window, an audio card, the printer, the radio, or anything really (perhaps even eliminating fopen, fread, etc. because files can be viewed as devices). The programmer need only worry about what device type they are working with and assume that the developer writing the device interface has written it correctly. The result is cross-platform access to any device. It is therefore fairly straight-forward to make a wrapper for DC's and GC's to put them into this particular interface. A wrapper translation layer is only a couple extra function calls and usually doesn't hurt performance.

    ReplyDelete