Divide and Conquer

ARTICLE DATE : December 1, 1999

The size of program and data files doesn't usually matter, but if you need to move these files from one machine to another, size can become a huge obstacle. If machines aren't networked, "sneakernet" is one of your only options, which means the files must fit on removable media like floppy disks or Zip disks. Smaller files are also preferable for online distribution because of data errors and lost connections. This issue's utility, Slice32, lets you slice files into manageable pieces for easy transport and distribution. Slice32 is an update to the PC Magazine utility Slice, a DOS program first published on May 16, 1989.

The original Slice allows only removable media as destinations, does not support removable media with capacities larger than 32MB, and supports only the FAT file system and DOS short filenames. At the time of its release, these weren't considered limitations. However, as the years passed, removable media have become larger and operating systems more powerful. Slice32 updates Slice for newer file systems and large-capacity removable media. Filenames up to 255 characters are allowed. NTFS attributes and file date/time stamps can be stored and restored, and you can slice to files in a directory instead of a diskette. The source code is written in C instead of assembly language. Finally, it is a true Win32 application with a graphical user interface.

Using SLICE32
Install Slice32 by unzipping the program files to a folder on your hard disk and creating a shortcut to Slice32.exe in your shell. The other executable file distributed with the program, Splice32.exe, is the application that allows you to restore the sliced file. When you slice a file, Slice32 creates a copy of Splice32.exe in the destination location. If that copy should somehow become unusable, you can use this copy to reconstruct your file.

When you first run Slice32, the application will appear as it does in Figure 1 . Start by entering a valid path and filename in the Choose file to slice text box, or click the Browse button to select a file. NTFS supports larger files, but 4GB is the maximum allowed under both Windows 95, 98 and Windows NT. Once you've selected a file to slice, the slice method controls in the middle of the window will be activated (see Figure 2 ).

There are three slice options: slice to removable drive, slice into portions of a specified size, and slice into a specified number of portions. Slicing to a removable drive mimics the original Slice function. The source file is written out to disk after disk in the destination drive. You select the destination drive from a list box containing only the removable media drives on your system.

Slicing to portions of a specified size and slicing into a specified number of portions are features that are new to Slice32. In both cases, the source file is split into pieces and the pieces are stored in the specified destination folder. When slicing to portions of a specified size, you select one of the predefined disk sizes or enter the size, and Slice32 calculates the number of portions necessary to do the job (see Figure 3 ). When slicing to a specified number of portions, you specify how many pieces and Slice32 calculates the size of each piece (see Figure 4 ).

The number of portions is limited to 100, whether slicing to removable media or to portions in a destination folder. If the portion size you enter causes the number of portions to be greater than 100, Slice32 automatically calculates the portion size required to make 100 pieces and sets the field to this value. (In practice, you will rarely come anywhere near this limit of 100 portions.) Also, because each portion carries a header of 309 bytes, no single portion can be less than 310 bytes. If you specify a portion size less than or equal to 309 bytes, an error message will appear below the edit field.

When slicing to removable media, the Slice button is enabled as soon as the destination drive has been selected. With the other two methods, you must specify a valid destination folder before the Slice button is enabled. If you are slicing to removable media, when you click Slice you will be prompted for the first diskette. Otherwise, the source file will be sliced into the destination folder.

Reconstructing the File
Once your source file has been sliced into pieces, it must be reconstructed--or spliced--to become usable again. When you slice a file, Slice32 creates an executable file named Splice32.exe on the first diskette or in the destination folder. Splice32 is a console application. That is, it's a Win32 program, but it has a command line interface rather than a graphical user interface. To reconstruct the file, simply double-click on Splice32 from within Windows Explorer. It will reconstruct the original file from the first set of sliced files residing in its directory. If you're running Splice32 from a command prompt, you have a little more control over its behavior. The syntax for Splice32 is the following:

Splice32 [source drive | first_file_of_slice_set | destination_filename] [destination]

The first parameter is the source, and it takes three forms: the source drive, the name of the first file in the slice set, or the destination filename. The second parameter is the destination, or the path where Splice32 is to write the reconstructed file.

If you don't specify either a source or a destination, Splice32 looks for the first file of a Slice32 set in the current working directory. If it finds one, and Splice32 was not started from a removable drive, it begins to splice the original file back together in the current directory. If Splice32 was started from a removable drive, the file is spliced together in the system temp directory. If Splice32 doesn't find a Slice32 set, it displays a usage message and terminates.

If you specify a source drive but no destination, Splice32 searches the specified source drive for the first file of a Slice32 set. The destination is selected the same way as before. If Splice32 was started from a removable drive, the file is spliced into the system temp directory. Otherwise, it's restored to the current directory. If it doesn't find one, it displays a usage message and terminates.

If you specify both a source drive and a destination, Splice32 searches the specified source drive for the first file of a Slice32 set. If it finds it, it splices the original file back together in the specified destination location. If it doesn't, then a usage message is displayed and the program terminates.

If a directory contains more than one slice set, you can specify which to use by using the first file of the slice set at the source parameter. Another way to indicate which set to use is to specify the destination filename as the source parameter.

If you're splicing from removable media and you insert the wrong disk in the sequence, Splice32 will reprompt you for the correct disk. If you begin to splice and the destination file already exists, Splice32 prompts you to o verwrite it. If you want it overwritten, type y; otherwise, Splice32 will terminate.

Inside Slice32
In updating Slice for 32-bit Windows, I had to add support for long filenames, NTFS attributes and date/time stamps, and removable media larger than 32MB. Also, the original Slice had no user interface; Slice32's graphical user interface is entirely new. At the same time, I wanted the creation and distribution of SPLICE32.EXE to be as effortless in Slice32 as in the original Slice. I accomplished these goals in a ground-up rewrite. The original Splice was written in Intel assembly language; Splice32 was written in C. So Slice32 is more a "concept update" of Slice than a literal update.

The Slice32 source code is contained in four files: slice32.c (the slice function), splice32.c (the splice function and the main function for SPLICE32.EXE), slc32lib.c (library routines common to both Slice32 and Splice32), and slc32_ui.c (all of the user interface code). I wrote Slice32 in straight C, without the Microsoft Foundation Classes, as an exercise in porting legacy DOS code to Windows. In this section, I will discuss how I mapped the DOS INT 21h routines onto Win32 API functions and how the program creates and distributes Splice32 using a technique similar to that used by the original Slice but unique to the Windows environment.

How Slice32 Works


WIN32 is not DOS
The primary method used to access the DOS API was the INT 21h interface. The programmer loaded the processor registers with information such as the function number, drive number, and filename, and then issued a software interrupt 21h. DOS processed the function call and returned the data to the programmer in the processor registers. Typically, the processor carry flag was used to indicate an error. If it was clear (0) when the function returned, then all was well. If it was set (1), then some error had occurred. The Win32 API doesn't work like this.

Win32 API functions are defined like those in any major programming language. You select the function you want to use, such as CreateFile(), and call the function with the correct parameters. The operating system indicates an error by returning a special value. This value is usually FALSE if the function returns a Boolean type, NULL if it returns a pointer, and INVALID_HANDLE_VALUE if it returns a handle. To get the exact error code, you call GetLastError().

I began Slice32 by listing all of the DOS functions used in Slice. Slice used a total of 16 different functions, ranging from 09h (display message) to 4Ch (terminate with errorlevel). Some of these could be replaced with Microsoft C runtime library routines. Instead of function 09h, I used either printf() (in Splice32) or MessageBox() (in Slice32). Function 4Ch became a return statement, and the combination of functions 08h (character input without echo) and 0Ch (flush input buffer then input) were replaced with a combination of _getch and _kbhit calls. The rest of the DOS functions were replaced with true Win32 functions.

INT 21h, function 36h (get drive allocation information), was replaced with GetDiskFreeSpace(). GetDiskFreeSpace() returns TRUE if successful and FALSE if an error occurs. The first argument is the drive specification, and arguments two through five are the sectors per cluster, bytes per sector, number of free clusters, and total number of clusters, respectively. To get the total number of bytes available, you need to multiply the sectors per cluster by the bytes per sector by the number of free clusters. Because I wanted to compare the free space with the number of bytes to be written, I wrapped GetDiskFreeSpace() in a function called get_drive_free_bytes() (slc32lib.c) that takes care of all the multiplication and returns a single number. I chose not to use the newer function GetDiskFreeSpaceEx(), which supports partitions larger than 2GB, because it is not supported in the original Windows 95.

In the original Slice, INT 21h, function 4Eh (find first file) was used in SPLICE.COM to find the next portion file on the source diskette, and in SLICE.COM to find the file matching the specified source name, as well as to retrieve the source file attributes for storage. In SPLICE32.EXE, I use FindFirstFile() to locate the first file in the slice set. FindFirstFile() returns a search handle. This would be used if you wanted to follow up with a call to FindNextFile(). In Splice32 I don't need FindNextFile(), so I use the returned handle to check for errors. It will be set to INVALID_HANDLE_VALUE if an error occurred.

FindFirstFile() takes two arguments: the filename to search for, and a pointer to a WIN32_FIND_DATA structure. The filename can contain wildcards; Splice32 searches for .*00. When FindFirstFile() returns, the structure argument contains the name of the found file.

In Slice32, I used GetFileAttributes() to retrieve the attributes of the source file. GetFileAttributes() is nice because it does not require you open the file in question before you call it. You pass it a filename, and it returns the attributes as a 32-bit value.

Functions 3Ch through 42h are: create file, open file, close file, read file, write file, delete file, and set file pointer, respectively. Under Win32, CreateFile() takes care of creating and opening; CloseHandle() closes; ReadFile() and WriteFile() read from, write to, and move the file pointer; and DeleteFile() deletes a file. I use the Win32 API functions GetFileAttributes() and SetFileAttributes() to retrieve and set the attributes; the C runtime library doesn't provide equivalent functionality.

Creating splice32.exe
One of the neatest features of the original Slice was its complete autonomy. To use it, you needed only SLICE.COM. When you sliced a file, it created a custom SPLICE.COM on the first destination disk. I wanted Slice32 to be just as straightforward.

Slice was a DOS COM format executable. Because a COM file is a simple executable memory image, it was relatively easy for SLICE.COM to create a customized splice.com on the destination media. Splice was actually a well-defined section of the Slice memory image. When a file was sliced, the program would initialize the splice variables and then write out that section of memory to the first destination disk. SPLICE.COM was smaller than SLICE.COM because it had only the Splice code; Slice had both the Splice and the Slice code.

Slice32 and Splice32 are 32-bit Windows applications, and because they use a different executable format, a different technique must be used. Slice32 stores a copy of SPLICE32.EXE as a user-defined resource. Some standard resource types are icons, bitmaps, and custom mouse pointers. User-defined resources can be used to store any raw binary data that an application may need. A user-defined resource can be declared as a set of immediately defined binary data, or as the contents of a specified file. In slice32.rc, a resource type of SPLICER is defined as the complete contents of SPLICE32.EXE.

A function called make_splice32() takes care of finding the SPLICER resource and writing it out to the destination. Four API functions are used: FindResource(), SizeofResource(), LoadResource(), and LockResource(). FindResource() locates a resource and returns a resource handle for it. LoadResource() takes the handle returned by FindResource(), loads the resource, and returns a global memory handle to it. LockResource() takes the memory handle returned by LoadResource() and returns a valid pointer to the resource. SizeofResource()uses the resource handle returned by FindResource() to retrieve the size, in bytes, of the resource. The pointer returned by LockResource() and the size returned by SizeofResource() are both passed to WriteFile(), which creates SPLICE32.EXE in the destination location.

The original Splice created a custom SPLICE.EXE for each split file, but Splice32 had to be written generically, so that any copy of Splice32 could recreate any file sliced with Slice32. This way, Slice32 doesn't have to perform any customization as it writes SPLICE32.EXE. While making it generic increased the size of executable file, this was a necessary change.

A COM file is a literal disk image of a program in memory. To load a COM program, the operating system simply allocates a block of memory the size of the file, copies the file into it, and starts executing it at the beginning. You can preload variable values in your source code and you can easily identify the precise offset of those values within the file. Thus you can very easily patch a COM file with specific initial values.

An EXE file is a completely different thing. The operating system reads its header and loads different parts of the file in different areas of memory. It may not even load the whole file until needed. It's very difficult to locate the offset within the EXE file that corresponds to a particular variable. So it was easy to generate a SPLICE.COM with the desired filename built right into it. It would be very difficult to do the same for SPLICE32.EXE.

Like the original Slice, Slice32 offers a way to move large data files between computers. It improves on the original with a graphical user interface, larger capacity, and support for long filenames. I hope Slice32 is as useful in your utility toolbox as the original.

Paul Trout, the author of Slice32, is a technology coordinator of the College of Applied Human Sciences at Colorado State University. Sheryl Canter is the editor of the Utilities column and a contributing editor of PC Magazine.
