From the August 2001 issue of MSDN Magazine
Fax Services: Send Any Printable File From Your Program in Windows 2000
|This article assumes you're familiar with Visual Basic|
|Level of Difficulty 1 2 3 |
|Download the code for this article: FaxServ.exe (165KB) |
|SUMMARY All versions of Windows 2000 have fax services built in, so sending faxes manually is as easy as setting fax options from the control panel. Faxes can also be sent programmatically in Windows 2000 using either COM Automation or the standard C API. The example in this article uses COM Automation with Visual Basic and MFC to programmatically manage faxing.|
The objects used for fax transmission, such as the FaxServer and FaxDoc objects, as well as their properties and methods, are explained. Because faxing of files you can't print can be problematic, this process is explained. Finally, this article implements a fax routing extension—a plug-in that exports standard functions and implements routing methods for processing received faxes.
|ith the development of the Internet, e-mail services have become more popular, replacing other means of communication including those offered by fax devices. In some cases, however, a fax device may be a reasonable solution for communicating graphical and textual information, especially when the recipient does not have an application suitable for viewing or printing e-mail attachments. Since faxes are still a popular and convenient means of communication, I'm going to take a look at the fax-handling functionality in Windows® 2000.
Faxing Prior to Windows 2000 Before Windows 2000, handling facsimiles was the domain of the Windows Messaging API (MAPI). Fax devices were treated as a special kind of MAPI transport provider that enabled the sending and receiving of faxes. In practice it meant that the fax transport provider was responsible for rendering the mail message into a suitable bitmap format (TIFF Class F) and sending it through some type of fax/modem device. Unfortunately, this solution was not very practical because the fax transport provider needed to be able to render any kind of file (attachment) to bitmap format.
Because of this limitation, most fax software vendors also developed virtual printer drivers, which enabled the user to print the file on a virtual printer, as a more natural way of handling faxes. The driver did the conversion (which was simpler in this case), and the bitmap was sent in the standard way. When the fax arrived, the software would put the received TIFF file into an e-mail message and send it to an administrator-defined mailbox.
This solution was, of course, much better than handling the faxes on paper. Unfortunately, it had some limitations. First, you needed to buy faxing software, which is usually just a link between a corporate e-mail server and a modem. Second, document-rendering output was not always as good as you would expect (especially when sending attachments). And last but not least, if you wanted to incorporate faxing into your software, you were doomed to "duct tape" integrations, using your e-mail system as a messenger between your application and the telephone network. In this scenario it is impossible to handle the transmission errors because sending the fax to the fax server does not mean sending it physically through the telephone line. Fax software will usually notify you via e-mail that the fax has been sent successfully, but this solution is of little use when trying to send a fax from a Windows NT® service application if there is no one to read the notification.
If you wanted to bypass the faxing software, you were left with the Telephony Application Programming Interface (TAPI), which apparently did not include any support for fax handling. To make things even worse, in this case you had to implement the entire G3 standard on your own. Good luck, and see you next year.
Windows 2000 Fax Services With Windows 2000 this situation has changed dramatically. First, all versions of Windows 2000 now have fax services built in, which means users can send and receive facsimiles using any fax-capable modem. Windows 2000 also includes the Fax API, which makes it possible to develop two types of applications: fax clients, which use fax services for sending faxes, and fax routing extensions, which are responsible for handling incoming fax messages. After taking a look at the Windows 2000 fax-handling features, I'll discuss both of these application types.
Fax services are installed automatically whenever you install a fax-capable modem in your system. In order to use these services, you need to configure the Fax service to use one (or more) of the installed TAPI devices. Using the control panel's Fax applet, you can perform almost all configuration tasks. First, you can define who you are so you won't need to retype this information over and over. You can also manage and create cover pages, change status display settings, and administer fax devices.
Figure 1 The Advanced Options Page
The Advanced Options page accessed from the Fax icon in Control Panel enables you to perform the most important administrative tasks (see Figure 1). Clicking on the Open Fax Service Management Console icon opens Microsoft Management Console (MMC) with the fax service snap-in loaded (see Figure 2).
Once you click on the fax device and select Properties from the context menu, the property sheet shown in Figure 3 is displayed. On the General tab page you can specify whether your device should be used for sending, receiving, or both. The Transmitting Station Identifier (TSID) and Called Station Identifier (CSID) are optional lines of information that can help track your facsimile. The TSID appears on the top of each fax page, while the CSID appears on the report printed after sending the fax by some more advanced classic fax devices. The text lines are both limited to 20 characters, so you will not be able to place anything fancier than a telephone number or your company name in any of those fields. Although these lines are optional, it is a good idea to fill them so your message recipient knows who sent the fax, even if you forget to send a cover page.
Figure 3 Fax Device Properties
The Received Faxes page of this dialog lets you specify what to do with incoming facsimiles (see Figure 4). You can print the fax, save it to a folder, or save it in a local e-mail inbox. Later in this article I will show how to extend your choices with some interesting options when I discuss fax routing extensions. As you can see in Figure 4, both the Print on and the Send to local e-mail inbox options are disabled. This happens because after installing Windows 2000, the fax service is configured to run under the Local System account, which does not have access to anything outside the local computer. If you change the logon account for the fax service to one with administrative rights, both the Print on and Send to options will be enabled.
Figure 4 Received Faxes Options
By default, the fax service is started manually, so if you want to use it on a regular basis, you will need to change its startup type (from manual to automatic) in the control panel (in Windows 2000, choose Administrative Tools | Computer Management). To send a fax you will also need to add a fax printer using the fax control panel applet. Once you've completed all the tasks successfully and your computer is connected to the telephone network, you may try to print something on the fax printer. You will be asked to provide recipient information and choose a cover page. As soon as you enter this information, your fax will be queued for sending.
Using fax services in Windows 2000 is simple. That's the good news. The bad news is that you cannot share the fax printer with other users on the network (at least in the case of Windows 2000 Professional). If you would like to know how to connect to a remote machine and use its fax services so that network users can share one machine as a fax server, please read on.
Sending a Fax Programmatically in Visual Basic To send a fax from within a program, you have to choose one of the two client APIs: COM Automation or the standard C API. Each has its advantages. For the sake of simplicity I will use COM Automation in the first example. The C version of the Fax API enables you to do a lot more. You can remotely configure the fax service and attached devices (ports), verify user access rights to a particular port, enumerate queued fax jobs, and trace job status using window messages or completion ports. You will see all of this in my MFC sample described later in this article.
To implement fax sending in Visual Basic®, create an instance of the FaxServer object. Once you have this object instantiated, call the Connect method, specifying the fax server name. This can be a local machine or one on the network. As soon as you connect, create a FaxDoc object by calling FaxServer's CreateDoc function with the file to be sent as an argument. With FaxDoc, you must at least specify recipient information (fax number and name). For the full list of FaxDoc properties, see Figure 5. If everything went OK up to this point, you can simply call the Send method and your program will now be sending faxes. When finished, call FaxServer's Disconnect method, and set your FaxDoc and FaxServer objects to Nothing.
Now you may be wondering how to convert files to the TIFF G3 format in order to send them. Well, you don't have to do the conversion because FaxServer will do it for you, provided that the file type is registered on the fax server and you are sending a file that is accessible to the fax service. You might have already figured out that the fax service will simply send a Print command to the application that is the default editor for the file being transmitted, and the file will be printed on the fax printer. That's the magic.
Figure 6 shows my sample Visual Basic procedure, which has been tested and developed using Visual Basic for Applications (VBA) in Microsoft® Word 2000. Initially I tried to develop a macro that sends the currently active document as a fax, but unfortunately whenever I called FaxDoc's Send method in the macro, the application hung. After giving this some thought, I found the reason for this behavior. The macro tried to send the document, then fax server tried to print it using Word while Word was trying to execute the macro. As a result, Word did not process any messages. Deadlock. The code works, however, in any Visual Basic-compatible environment, including Visual Basic-based programs, provided that the macro is run within an environment different than the editor for the document being sent.
Sending Faxes Programmatically using the Fax Service API Using the C version of the API you can either transmit any "printable" document (just like in Visual Basic), or get the device context to the fax printer and do the printing on your own. In both cases you can also transmit a predefined cover page, customized page, or page stored on the fax server.
The sample MFC app I developed demonstrates both of these methods. First, let's take a look at how to send a printable file. In this case, you will need to connect to the fax server (using the FaxConnectFaxServer function) to obtain a fax handle that will then be used in almost all subsequent calls. This function requires two parameters: the server name and a pointer to the fax handle that it returns on exit. The server name can be either a valid computer name (like \\myserver) or NULL. NULL indicates that it should attempt to connect to the local server.
When the function succeeds, the next step is to call the FaxCompleteJobParams function, which allocates the FAX_JOB_PARAM and FAX_COVERPAGE_INFO structures. The FaxCompleteJobParams function will also populate both of the structures with the defaults available through Control Panel's fax configuration applet. The FAX_JOB_PARAM structure contains fields that control the fax transmission and are displayed in the fax queue, either directly or through the queued document property sheet. The most important fields (and the only required fields) of this structure are SizeOfStruct and RecipientNumber.
You can use the FAX_JOB_PARAM structure to request a fax delivery (or non-delivery) report, schedule the transmission at any time in the next 24 hours, and provide the fax server with a TAPI call handle that should be used during transmission. The last option is useful only if you would like to control the dialing on your own.
If you want to be notified of the success or failure of the fax transmission, you have three choices. If you do not want a report, you can set the DeliveryReportType field of the FAX_JOB_PARAM structure to DRT_NONE. If you want to save the report directly in a local e-mail inbox, set the field to DRT_INBOX and store the name of the e-mail profile in the DeliveryReportAddress field. If you want the delivery report to be sent via e-mail, set DeliveryReportType to DRT_EMAIL and save the e-mail address in DeliveryReportAddress. This option is also a very effective way to cause an access violation within the fax service, so verify that it works properly before you use it in your software.
The FAX_COVERPAGE_INFO structure contains everything that will be printed on the cover page. You need to provide at least the name of the file for the cover page in the CoverPageName field, and this file name has to be specified without an extension or you'll get an error message stating that the file can't be found. Although the structure contains an ample number of fields for describing the recipient, only two of them (RecipientNumber and RecipientName) can be placed on the cover page using the Microsoft cover page editor.
Once you have the structures ready, call FaxSendDocument. This function requires five arguments: the fax server handle, the file name for the cover page, pointers to both the FAX_JOB_PARAM and the FAX_COVERPAGE_INFO structures, and a pointer to a DWORD that will store your fax job ID. If you don't want a cover page, simply call FaxSendDocument with a NULL pointer instead of a valid FAX_COVERPAGE_INFO structure. FaxSendDocument is an asynchronous function, so it returns almost immediately after placing your fax in the fax service's queue. As soon as it returns, you may free both allocated structures with FaxFreeBuffer, then disconnect from the server by calling the FaxClose function. Figure 7 shows my sample MFC procedure for faxing a document.
If you would like to send the same document to many different recipients, you may use the FaxSendDocumentForBroadcast function. This function takes five arguments. The first four arguments are the fax server handle, the file name for the cover page, a pointer to the DWORD for the job ID, and a pointer to a callback routine that will be called for each of the fax transmissions to provide fax job parameters. The fifth argument is a pointer to an optional argument that will be passed to your callback routine. The fax server will call your callback function until it returns 0, indicating that all transmissions have been queued. This means you avoid repetitive renderings of the document being sent.
Sending Files You Can't Print The methods I've described so far for sending faxes have one drawback: you can only send a file that is printable using an application registered in the system (such as .doc and .xls files). What if you would like to control the printing on your own, using a fax as a printer for any type of text or graphics? Well, in this case you will have to use the fax printer device context (DC). Because the fax printer is a bit different from other graphics device interface (GDI) devices (it needs additional information not provided by the GDI to print the files, such as a recipient number), you need a special function to obtain a device context to this printer without having to ask the user to provide the fax number to call.
You can obtain a fax printer device context the usual way, but if you are sending faxes from within a Windows NT service, there won't be someone to prompt for information about the recipient. The code in Figure 7 uses a fax printer device context to send text that a user has typed. The procedure obtains a device context using the FaxStartPrintJob function. This function takes the name of the fax printer (in the form \\servername\printername) and the FAX_PRINT_INFO structure as input parameters. You may set the printer name to NULL, and in this case, the API assumes the local fax printer is the output device. In return you get the fax job ID (which may be useful for tracing your fax job status) and the FAX_CONTEXT_INFO structure containing the printer device context you need.
As soon as you have the device context, you can call the standard StartPage/EndPage GDI functions and use the DC any way you want. When you are finished with your printing, call EndDoc (or AbortDoc) followed by DeleteDC to clean up resources. If you would like to print a standard cover page, call FaxPrintCoverPage with pointers to the FAX_CONTEXT_INFO and FAX_COVERPAGE_INFO structures. The fragment of the sample program in Figure 8 illustrates this technique.
Fax Routing Extensions So far I've shown techniques to send faxes. In order to receive faxes, a different approach is used. The fax service API introduces Fax Routing Extensions, which are simply plug-ins (implemented as DLLs) that export some standard functions listed in Figure 9. Each extension implements one or more extra functions called routing methods. A routing method does whatever its creators intended. The Microsoft extension outlined in Figure 10 enables you to print and save faxes (in an inbox or in some other folder). Your extensions can perform whatever tasks you want them to perform. A sample extension provided with this article stores fax information in a Microsoft Access database. But you could create an extension that performs optical character recognition (OCR), or whatever you find suitable.
Figure 10 Fax Routing Extensions
When the fax service starts, it loads all extension DLLs; as soon as the library has been loaded, the service calls GetProcAddress to obtain pointers to routing methods exported from the DLL. When the service receives a fax, each method is called in the order determined by the method priority. That's the big picture. Now let's look at the details.
Registering a Fax Service Extension Each fax service extension should have a registry key under
The key name may be the extension's vendor name, or any other form of description. This key has two values associated with it: Friendly name (REG_SZ), which is self-explanatory and ImageName (REG_EXPAND_SZ), which is a fully qualified path of the extension DLL. Under the extension key, you will find one or more subkeys, one for each of the routing methods supported by the extension DLL. There are four values associated with each method key. Function Name, as you might guess, is just a name of the function exported from the extension DLL. (The April 2000 MSDN Library incorrectly stated that this value should be named FunctionName.) The Guid value is a unique identifier of the routing method. Priority (REG_DWORD) determines the global order of the routing method calls, and finally FriendlyName (REG_SZ) describes what the method does. This value is used by the Fax Server MMC Snap-in to display all available routing methods and allows the user to change his priorities.
You have two options when registering your fax extension: update the registry manually or use a provided function to perform this task. In my first attempt to register the sample extension I used regedit, and after five minutes I had all the registry keys in place. It took me another hour to get my extension loaded by the fax service. Here is the full story: since regedit does not allow you to create values of type REG_EXPAND_SZ, I created an ImageName value of type REG_SZ. When I started the fax service it immediately reported (in the application event log) that it had a problem loading my extension. Finally I thought about the possible problem in registry value types, and recreated the ImageName value using the older brother of regedit: regedt32. As soon as I changed the type of the value, everything went smoothly. This is the price I paid for being impatient. You may think that an hour isn't too much, but I can assure you that at 1:00 am, it feels like weeks.
Another method for registering an extension (on the local server) is the FaxRegisterRoutingExtension function. You have to connect to the fax server (using FaxConnectFaxServer), then call FaxRegisterRoutingExtension with a pointer to your own callback function. The callback function will be called as long as it returns a nonzero value. The callback function will allow you to specify full information (friendly name, function name, and so on) for each of your routing methods. To be honest, I find this method a bit cumbersome, especially considering the fact that current setup utilities allow you to do whatever you want with the registry.
Implementing the Callback Function But let's get back to the extensions themselves. After the fax service loads the extension library, it calls its FaxRouteInitialize function, passing a heap handle and a pointer to the FAX_ROUTE_CALLBACKROUTINES structure. As you can imagine, the structure contains pointers to functions implemented inside the fax service, which you may want to call in order to perform your routing tasks. For a description of these routines, see Figure 11.
But what is this heap handle? You need some standard for memory allocation which both the client and server will follow if each is supposed to free memory allocated by the other. That's why the CoMemTaskAlloc and CoMemTaskFree functions exist.
Why can't I use the standard malloc/free functions? Well, the problem is that these functions are implemented inside the C runtime library, and no one can guarantee that your extension DLL will use the same runtime library as the fax service. Therefore, if you should try to free memory allocated by the server using the free function, and the libraries used by you and the Redmondtonians were different, a serious problem could arise. For this reason Microsoft decided that it is necessary to use some standard memory allocation scheme, which is the HeapAlloc/HeapFree combination. Since Windows implements both of these functions, you do not have to worry about the runtime library versions. Internally you may still use the malloc/free combination, but when exchanging memory with the fax server, you will need to use the heap handle with the HeapAlloc/HeapFree functions. If you would like to get more information on Win32® memory allocation issues, I suggest taking a closer look at the technical article "Heap: Pleasures and Pains" by Murali R. Krishnan that can be found on MSDN® Online.
Once you get the callback function pointers and the heap handle, the server calls your exported routing methods periodically whenever it receives a fax.
Fax File Lists and Fax Routing Before I delve deeper into routing methods, let me first explain the concept of the fax file list. When the fax service receives a facsimile, it saves it as a TIFF class F file in the %systemroot%\Profiles\All users\Application data\Microsoft\Windows NT\MSFax\faxreceive directory. Depending on your fax device configuration, the file will be moved to the target directory or to an inbound transmissions directory defined for the given device. Routing methods generally perform some operations on this file (converting the file to text using OCR, for example) and the results of these operations will be stored as different files with different formats. It is possible that one extension performs the OCR and saves its results in Word format, and yet another routing extension stores the Word document file in a database. To make this kind of processing possible, the fax service uses what's called a fax file list. At the beginning of the fax processing, this list contains only one file: the fax itself. Subsequent routing methods may add or delete files from this list, depending on the functionality of the routing extensions. This way you can mix and match routing extensions to get the functionality you're looking for.
There is one very important thing about fax routing that you have to keep in mind while developing fax routing extensions. Calls to your routing methods will be made on different threads each time. I have discovered (the hard way) that whenever the server receives a fax, the fax service spawns a new thread to service it (or to be perfectly precise, it spawns a thread for each of the routing methods). For this reason, when I used DAO MFC classes to store fax data in an Access database, I could only store one transmission. As it turns out, DAO is not thread-safe. With the next transmission, the server reported problems with the receipt, and although the calling party was informed that the transmission went smoothly, no trace was left on my computer (not even the TIFF file). I had to develop a standalone test program to find out that the second call (on a different thread, and not even one running concurrently) to my routing method causes an access violation in the CDaoDatabase constructor.
The FaxRouteMethod function takes three parameters. The most important is a pointer to the FAX_ROUTE structure, which details information about the inbound transmission. The structure itself does not contain the name of the received TIFF file. In order to get it you need to call the FaxRouteGetFile function. As you remember, a pointer to this function implemented by the server is provided to your extension in the FAX_ROUTE_CALLBACKROUTINES structure. The FaxRouteGetFile function takes four parameters: a job ID, the received file index in the list, a buffer for the file name, and a pointer to the size of this buffer. In order to get the file name it is necessary to call this function twice. When you call this function for the first time, you need to pass the NULL in place of the file name buffer and specify its size as zero. In this case the function will return the required buffer size. When you want to get the file name, you need to call it with a buffer with the proper size allocation using the heap handle provided in the FaxRouteInitialize function. If you need to get the original TIFF file name using this function, you will have to call it with (guess what) an index value of 1.
Other Functions Used in Routing Extensions Up until now I have discussed only two of the functions implemented by routing extensions, FaxRouteMethod and FaxRouteGetFile. You may need to use several other functions for your routing extension. First, let's take a look at the FaxRouteDeviceEnable function. This function will be called periodically to enable or disable routing of inbound fax messages received by a particular fax device. This function may also be used to query your extension, whether a particular routing method is enabled (or disabled) for a certain fax device. At present, Windows 2000 does not include any administrative tools that would enable an administrator to turn a routing method for a fax device on or off.
The C API, however, includes a method that makes development of such a tool possible (FaxEnableRoutingMethod). Your routing extension should implement this method to make sure that it does not route all inbound facsimiles, only those coming from devices for which it has been enabled.
FaxDeviceChangeNotification is a simple function that is called whenever a new fax device is added to the system or an existing device is removed. You may use this function to clean up or allocate resources on a per-device basis. If you are not interested in taking advantage of such functionality, simply return TRUE and forget about this method all together.
Full-blown routing extensions will certainly require some configuration data that will determine the way the extension works. If a hypothetical extension were to register all fax messages in a database, the configuration would include the database name, login information, and so on. Usually all this can be stored in the registry, but how do you change this information on the fly? You use the FaxRouteSetRoutingInfo and FaxRouteGetRoutingInfo functions. In order to configure a fax routing extension you will need to create some administrative tool. This tool may store and retrieve the configuration from the registry, but how do you notify the extension of configuration changes without restarting the fax service? Again, the solution is quite straightforward. Simply call the FaxSetRoutingInfo function with your routing method GUID and, like magic, the Fax server will call the FaxRouteSetRoutingInfo function and the routing extension will be immediately notified that it should change its configuration.
The sample routing extension I've provided for download (Afext.dll) implements a simple routing method that stores information about each facsimile in an Access database. The extension uses a CRecordset-derived class to store FAX_ROUTE members in a database. The extension is quite straightforward, so you may use it as a template for your own development. Figure 12 illustrates a sample routing method implemented by the Afext.dll extension.
Pitfalls to Avoid I was somewhat disappointed that fax routing extensions cannot be implemented as COM objects. I think the implementation would be much easier because fewer global variables would be necessary to store pointers to callback functions, the heap handle, configuration information, and so on.
You also have to be aware of the fax service threading behavior. Fax routing methods are called sequentially, but on different threads. This is probably because the use of COM in this case is simpler. In such a case any routing method may call CoInitialize (or CoInitializeEx) and join either a multithreaded apartment (MTA) or create its own single-threaded apartment (STA). Just as in the case of ISAPI filters, I would strongly recommend that you call CoInitialize at the beginning of the method and CoUninitialize at the end. You also have to be aware that you cannot use COM objects that do not explicitly specify the threading model in the system registry, because the fax service cannot create windows that could possibly be used to serialize function calls between the service and the object.
If you have ever developed an ISAPI extension or filter, you probably know how daunting the debugging of such an application can be. You can expect the same types of problems with routing extensions. If you plan some serious routing extension programming, I would strongly recommend developing a simple application that will load and call your extension, simulating the behavior of the fax service. This will make your life and debugging much easier.
Also remember that your extension will run within the address space of another process. In this case, one misbehaving DLL may cause serious problems when it causes leaks of memory or other resources. Pay special attention to the behavior of the fax server (especially the CPU time and memory resources it uses).
And last but not least, expect the unexpected. My modem, for example, ceases to function whenever you call it using a standard phone and hang up the receiver. You can be certain that companies that rely heavily on error-free fax communication wouldn't appreciate this sort of glitch!
Conclusion If you want to start programming the Windows 2000 fax services, you will need a recent version of the Platform SDK that includes all necessary header files and libraries. You will also need a working instance of the fax service, which is available either in Windows 2000 or BackOffice Small Business Server edition. The necessary library (winfax.dll) is available as a redistributable in BackOffice Small Business Server. If you're running any version of Windows 2000, the library is available out of the box.
| For related articles see:|
The Messaging and Collaboration/Messaging API section of the Platform SDK
| Marcin Kaluza is a senior software developer at Sage Enterprise Solutions (UK). Marcin spends most of his time developing applications with MFC, COM, and SQL Server. He can be reached at email@example.com.|