June 1998

Formatting a floppy from within an application

by Kent Reisdorph

Whenever you have an application that allows saving data to a floppy disk, you may run into situations where the disk in the floppy drive is unformatted. At that point, you have two choices. First, you could ask the user to format a floppy and put it into the drive--a clumsy solution at best. A better way is to allow the user to format the floppy from within your application. Although this may seem like a simple task, the truth is that there's no obvious way to format a floppy from within a Windows program. In this article, we'll show you how to format a floppy via code using the undocumented Shell API function, SHFormatDrive.

 

This should be easy...

At first glance, it appears that formatting a floppy should be a very simple task. Surely the VCL contains a routine that will copy files? If not, there's certainly a Windows API function, right? Sorry, but no to both. You could use the DOS function format by shelling out to the command prompt. While that command might work, it's a hack solution. Besides, it doesn't give your user any control over disk-formatting options. Next, you turn to the Windows API. After hours of searching the Win32 API online help, you stumble on the DeviceIoControl function and its IOCTL_DISK_FORMAT_TRACKS flag. Sure enough, DeviceIoControl will allow you to format a disk. Unfortunately, since it's a typical Windows API function, you're required to write dozens of lines of code to carry out what should be a simple operation. To make matters worse, you discover that you can't use DeviceIoControl anyway, since the IOCTL_DISK_FORMAT_TRACKS flag is available only in Windows NT. Is there any hope of finding a documented way of formatting a floppy in Windows, something that will work in both Win95 and NT, something that will give you and your users control over disk-formatting operations? Unfortunately, the answer is no.

Undocumented Windows: SHFormatDrive

If the story ended there, this wouldn't make much of an article! Fortunately, the story doesn't end on such a sad note. As a matter of fact, the Windows Shell API is a very powerful programming interface. Using the Shell API, you can execute programs (ShellExecute), copy, rename, move, or delete files (SHFileOperation), allow users to select a directory (SHBrowseForFolder), create shortcuts (via IShellLink), create tray icons (Shell_NotifyIcon), and much, much more. In this case, we're interested in an undocumented Windows Shell API function called SHFormatDrive. This function gives you everything you need to format a floppy disk from your C++Builder programs. Why this function isn't documented is a mystery. SHFormatDrive isn't one of those super-secret Windows internal functions that only those with the secret decoder ring can use. More likely, it was just overlooked when the Shell API was documented. For whatever reason, it remains undocumented even though years have passed since the Shell API was written.

When you call SHFormatDrive, Windows will display the Format dialog. The exact dialog you get depends on whether you're running Windows 95 or Windows NT. Figure A shows the Format dialog as it appears under Windows NT 4.

Figure A: Windows displays the Format dialog when you call SHFormatDrive.

Now, let's look at what you need to do to call SHFormatDrive in your C++Builder programs.

Declare SHFormatDrive and constants

Since SHFormatDrive is undocumented, it's not declared in the SHELLAPI.H header file. (Although SHFormatDrive is undeclared in SHELLAPI.H, it is contained in SHELLAPI.LIB and ready to use once you've declared it.) Because SHFormatDrive isn't declared in the Windows headers, you must declare the function, and the constants it uses, yourself. The declaration of the constants and the function itself resembles the following:
#define SHFMT_ID_DEFAULT   0xFFFF

#define SHFMT_OPT_QUICK    0x0000
#define SHFMT_OPT_FULL     0x0001
#define SHFMT_OPT_SYSONLY  0x0002

#define SHFMT_ERROR    0xFFFFFFFFL
#define SHFMT_CANCEL   0xFFFFFFFEL
#define SHFMT_NOFORMAT 0xFFFFFFFDL

extern "C"
DWORD WINAPI SHFormatDrive(HWND hwnd,
                           UINT drive,
                           UINT fmtID,
                           UINT options);
Let's take a moment to discuss the constants, then we'll go over the function declaration. First, the SHFMT_ID_DEFAULT constant is a special value that's passed for the fmtID parameter of SHFormatDrive--at this time, the only valid value that can be passed for the fmtID parameter.

The next group of constants (SHFMT_OPT_QUICK, SHFMT_OPT_FULL, and SHFMT_OPT_SYSONLY) are flags that determine which format options are selected when the Format dialog is initially displayed. If SHFMT_OPT_QUICK is specified, then the Quick Format check box on the Format dialog will be checked when the dialog is displayed. If you specify SHFMT_OPT_FULL, then the Quick Format box is cleared. However, if you specify the SHFMT_OPT_SYSONLY flag, then the Copy System Files option will be checked when the Format dialog is displayed.

Note that this option applies only to Windows 95. If you attempt to specify SHFMT_OPT_SYSONLY under Windows NT, the call to SHFormatDrive will fail. These flags only determine which options on the Format dialog are selected. The user can always change the options before formatting the disk.

You'll use the SHFMT_ERROR, SHFMT_CANCEL, and SHFMT_NOFORMAT constants to check the return value of SHFormatDrive for errors. We'll discuss the return value of SHFormatDrive in just a moment.

 

Calling SHFormatDrive

Now that you have declared the SHFormatDrive function, you can call it from your C++Builder applications. Let's take another look at the function declaration for SHFormatDrive. Here it is again (minus the extern "C" specifier):
DWORD WINAPI SHFormatDrive(HWND hwnd,
                           UINT drive,
                           UINT fmtID,
                           UINT options);
The hwnd parameter specifies the window handle of the window that should act as the parent for the Format dialog. For C++Builder VCL applications, you should use your form's Handle property here. You'd use the drive parameter to specify the drive to format. The A drive is drive 0, the B drive is drive 1, and so on. Attempting to set the drive number to an invalid drive number or to any hard drive will result in an error. The fmtID parameter isn't fully implemented and must be set to SHFMT_ID_DEFAULT. As discussed earlier, you'll use the options parameter to tell Windows which options on the Format dialog should be set when the dialog is displayed.

Let's say you wanted to format the A drive and prompt the user to do a full format. In that case, the call to SHFormatDrive would look like this:

 

SHFormatDrive(Handle, 
  0, SHFMT_ID_DEFAULT, SHFMT_OPT_FULL);
The process is really pretty simple once you've declared SHFormatDrive and its constants. This example ignores the return value since we haven't discussed the return value yet. We'll look at that next.

 

SHFormatDrive return values

The value returned from SHFormatDrive varies depending on whether the user is running Windows 95 or Windows NT. You should either detect the operating system being used or plan for the lowest common denominator should an error occur during formatting. By lowest common denominator, we mean that you should be prepared to handle any and all possible return values returned by SHFormatDrive. You might find it interesting to note that Windows 95 actually handles SHFormatDrive better than does Windows NT. Under Windows 95, the return value will be a positive number if the format was successfully carried out; or if an error occurred, the result will be one of the error constants mentioned earlier.

The error code returned is usually either SHFMT_ERROR or SHFMT_CANCEL. The SHFMT_ERROR code occurs if an error occurs when formatting a disk. Such an error might occur if the disk in the drive is bad, if the user removes the disk from the drive during the format, if the disk type is wrong (attempting to format a 720K disk in the 1.44 format), or for any number of other reasons.

SHFormatDrive returns SHFMT_CANCEL if the user cancels the format operation. The SHFMT_NOFORMAT code is returned if the disk in the drive can't be formatted. Although it's theoretically possible to get an error code of SHFMT_NOFORMAT, I've yet to see that error code in practice.

Windows NT handles SHFormatDrive differently. The return value is 0 if the function succeeded or SHFMT_ERROR if the disk wasn't formatted. Put another way, NT doesn't differentiate between the user canceling the format or an error occurring. In either case, SHFormatDrive will return SHFMT_ERROR under Windows NT.

Naturally, you should check the return value of SHFormatDrive. Then, take appropriate action if the disk in the drive remains unformatted.

 

Fortuitous floppy formatting

Formatting a floppy disk in Windows is easy once you know the secret of SHFormatDrive. Listing A contains the main unit of a program that formats a floppy disk in drive A at the click of a button. The program checks the return value of SHFormatDrive and displays a message accordingly. This code will work better on Windows 95 than on Windows NT since the error codes are more meaningful on Windows 95. Unfortunately, SHFormatDrive has no option to format the disk silently without user intervention. That inflexibility may not be all bad, though, since formatting a drive without letting the user know is probably not a good idea anyway. Knowing about SHFormatDrive will save you lots of time and hair-pulling when you must format a floppy disk from within your applications.

Listing A: FMTDRIVE.CPP

//--------------------------------------------
#include <vcl.h>
#pragma hdrstop

#define SHFMT_ID_DEFAULT   0xFFFF

#define SHFMT_OPT_QUICK    0x0000
#define SHFMT_OPT_FULL     0x0001
#define SHFMT_OPT_SYSONLY  0x0002

#define SHFMT_ERROR    0xFFFFFFFFL
#define SHFMT_CANCEL   0xFFFFFFFEL
#define SHFMT_NOFORMAT 0xFFFFFFFDL

extern "C" DWORD WINAPI 
SHFormatDrive(HWND, UINT, UINT, UINT);

#include "FormatU.h"
//--------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}
//---------------------------------------------
void __fastcall 
TForm1::Button1Click(TObject *Sender)
{
  int Res = SHFormatDrive(Handle,
    0, SHFMT_ID_DEFAULT, SHFMT_OPT_FULL);
  if (Res < 0) {
    String S;
    switch (Res) {
      case SHFMT_ERROR : {
        S = "Error formatting disk.";
        break;
      }
      case SHFMT_CANCEL : {
        S = "Format cancelled.";
        break;
      }
      case SHFMT_NOFORMAT : 
        S = "This disk cannot be formatted.";
    }
    MessageBox(Handle, S.c_str(),
      "Error", MB_ICONEXCLAMATION);
  }
  else
    MessageBox(
      Handle, "Disk formatted successfully!",
      "My Application", MB_OK);
}