Last edit: 05-03-17 Graham Wideman
|Hardware I/O Port Programming with Delphi and NT
Article created: 98-06-01
Minor edits: 98-06-18
Engineers and others interested in using their PCs to manipulate external hardware have long been drawn to TurboPascal/Delphi, because of the combination of power, predictability and speedy compilation that it offers while avoiding some of the complexity of C++. You can afford to be a specialist at something other than the programming language. This was great under DOS, and even under W3.1 and W95 with Delphi 1. But with Delphi 2 and 3, and Win NT, the direct hardware manipulation features are not so directly accessible.
This article covers these general topics:
Version applicability: This article applies to (and the software has been tested with) Windows NT 4 (SP3). It uses techniques that have applied certainly since 1996 (hence should work with NT 3.5x). It has not been tried with NT 5.
This is actually the easy part. In Turbo/Borland Pascal and Delphi 1 there are Port arrays which you can read and write (like any other array). These are absent from Delphi 2 and 3, presumably because of the additional wrinkles added by Win32 and NT programming. No matter, we can easily add PortIn and PortOut functions/procedures to call the CPU's I/O instructions. These are in unit gwportio.pas, and are similar to other such functions floating around the web. (Onno Kortmann authored a unit called simport.pas with a slightly cooler object-based implementation that looks like the old Port arrays. I decided to go for simplest implementation, so mine are just functions and procedures.)
Under NT, "user-mode" code (ie: applications written by mere mortals) is not allowed to access hardware directly, hence when your application attempts to execute an I/O instruction you get an exception. The idea is, of course, that hardware resources are things that no application should just take over at will, instead it should be up to the operating system (and its drivers) to arbitrate between different apps requests to use those resources.
That's the theory. Turns out that the NT kernel maintains a map of I/O port addresses that each process is allowed to access, and for your apps that's normally set to "none". But we can tell NT to use a different I/O Permissions Map (IOPM) for our process and thereby gain access to the ports. This approach is of course very naughty from a disciplined OS standpoint, so not recommended for widely distributed commercial apps. But for those times when you just need to hack on some hardware, who has time to write a proper NT device driver?
The only problem is that user-mode code is not allowed to execute the kernel functions to change the permissions map. The workaround for that problem is to create an NT driver (drivers have sufficient privileges) to twiddle the IOPM at the request of your app. Just such a driver, giveio.sys, has been floating around the net since '96, authored by Dale Roberts in conjunction with a May 96 Dr Dobbs article.
Giveio.sys demonstrates the concept and most importantly clues us in to the undocumented kernel functions, but it throws open access to all I/O ports. So I decided to take a crack at an improved driver that would allow giving access only to selected ports, and would provide a few diagnostic functions in to the bargain. The result is gwiopm.sys (source code included if you want to browse). A Delphi interface to gwiopm.sys is provided in gwiopm.pas.
If you too want to have a go at device driver development, be forewarned that the only known way to compile one is with the MS NT Device Driver Development Kit (NT DDK), which in turn needs the Win32 SDK, and relies on some pretty specific feature of the MS C environment (Visual Studio). (Oh, and supposedly you can't compile on a W95 machine either...) This leads to the somewhat incongruous necessity to install about about 500 Meg of cra... er, I mean, "supporting code" in order to compile a couple of hundred lines of source code into a 4K executable. Recommended: Art Baker's "The Windows NT Device Driver Book" (Prentice Hall '97) helps to smooth out the bumps.
So now we have a driver that can give our application permission to use I/O instructions. To install this driver you could create an installation program, or you could manually edit the registry to have NT load up the driver on boot up. But though you're not really going to distribute your hardware hack, there are just a couple of other people who'll need to install it, right? And making them hassle with installing a driver is tedious.
For this reason, gwiopm.pas includes functions that your Delphi app can call to transparently install the gwiopm driver on the fly, start it up and then later stop and uninstall it after you are done. (I first learned about the functions to do this from example loaddrv.c code written by Paula Tomlinson, associated with a Windows Dev Journal article.)
I/O Port Driver: The port I/O approach shown here takes the point of view that if you're wanting to manipulate ports directly, you probably want as few extraneous mechanisms involved as possible. And if you have dedicated hardware attached to your PC, in many instances you probably have a single piece of dedicated software to control it. So there's no need to worry about arbitration. For these reasons, the approach described in this article provides actual cpu I/O instructions right in your Delphi/assembler code.
However, in other situations you may genuinely need a more civilized approach: even though you are manipulating the hardware in a custom manner, you might still like your port usage to be arbitrated by a "good citizen" driver. An example of this scenario would be where you are twiddling some peripheral that's attached to a PC parallel printer port... but you don't want to clobber NT's use of that same port.
This, of course, requires a more sophisticated driver that request and gains control of the port in the polite and proper manner, and intermediates your app's "requests" for I/O, translating them into actual I/O actions. Such an approach is taken by Victor I. Ishikeev in his TVicPort driver and accompanying examples for Win95 and NT. Find it at the main Delphi freeware/shareware sites (look for recent versions as early ones didn't cover NT.) Or one could look at the samples in the NT DDK if you are considering writing your own.
Writing Drivers in Delphi: On the surface, it would appear that you can't use Delphi to write drivers. (The advance word on D4 indicates some features for writing services... it remains to be seen whether this includes device drivers, and in any case this is a feature of the expensive version.) The primary reason is that Delphi lacks the compile/link functions to produce a .sys file, not to mention missing the Delphi-ized DDK header files.
However, you can get generic "adapter" drivers which will callback your Delphi code. There is some overhead involved, but for some situations it could be just the ticket. Other related products offer "Wizard"-style driver development, which again avoids the necessity to understand device drivers in depth (one hopes) See:
Start at Cherry Hill Software
Please feel free to distribute or publish this article and associated code, provided some note of credit for me remains attached. Permission is also granted to include this article on CDROM collections, again provided credit remains attached. Thanks!