^T

Articles - Zingers

Reusable Software Components - C

Introduction

In this series of articles, we explore techniques for implementing reusable software components for various computer runtimes. This article illustrates how to build dynamic-link libraries or DLL's on Microsoft Windows. Later articles will cover other environments like C++ and COM. As this article focuses on the C language, it only covers a few basic concepts. These concepts, however, will be important for understanding future articles. Throughout this series, we will implement a simple math library which exposes a single operation called Divide() that takes two integer arguments and computes their quotient. Each iteration is meant to illustrate a different technique and its tradeoffs.

This article intentionally focuses on error handling, as most software projects get error handling wrong and many software bugs are the result of not reporting or not checking error conditions. Furthermore, C is a fairly simple language with simple constructs which don't really complicate the creation of DLL's. Later articles will discuss other issues which crop up due to more complicated language and runtime environments.

Sources for each library and the associated test cases are available by clicking on the View Full Sources link at the beginning of each section. Microsoft Visual C++ 2003 was used to develop these samples. Attempting to build the samples with a different compiler may require changes to the sources and/or makefiles.

Take 1 - Exporting a Library Routine

View Full Sources

To get started, we need to define our library interface via a header file. This header file will be included in our library sources and will be used to expose the library interface to users of the library. The contents of our header file Math.h are below.

Math.h

#ifndef __RSC_C_MATH__ #define __RSC_C_MATH__ #ifdef __Building_MathDll__ # define EXPORT_SYMBOL __declspec(dllexport) #else # define EXPORT_SYMBOL __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif EXPORT_SYMBOL int __stdcall Divide(int a, int b); #ifdef __cplusplus } #endif #endif /* !__RSC_C_MATH__ */

Note the following from the code above:

  1. The header file begins and ends with a standard header guard to prevent multiple definitions for types and multiple declarations for functions.
  2. We use the __Building_MathDll__ macro to switch between import and export storage-class specifiers for our exported function. This macro should only be defined when building the math library. Defining it will tell the linker that we are providing the implementation of Divide() and that it should be exported so that it may be used by consumers of the library.
  3. Our function declaration will be wrapped in an extern "C" block if the __cplusplus macro is defined. The compiler defines this macro when compiling C++ source files. This prevents C++ name mangling from being applied to the Divide() function when it is used from a C++ program. Otherwise, there will be a symbol mismatch at link time resulting in an error.
  4. Our function is declared using the __stdcall calling convention. Explicitly providing the calling convention prevents an ABI mismatch that will occur if the library and its consumer are compiled with different default calling conventions.

Now that we have specified the interface to our library, we need to provide a C source file that implements the Divide() function. The contents of our source file Math.c are below.

Math.c

#include "Math.h" int __stdcall Divide(int a, int b) { return a / b; }

We start by including the Math.h header to pull in the declaration of the Divide() function. The Divide() function is then defined, taking care to use the same calling convention specifier as the declaration.

When building the library, we must make sure to define the __Building_MathDll__ macro so that our function is properly exported and also pass the /LD option to the compiler. The /LD compiler option tells the linker to create a DLL instead of a regular executable. In addition to the DLL, the compiler will also produce a LIB file which should be used when linking any consumer of our DLL. This LIB file contains a stub for the Divide() function which will correctly forward function calls to the appropriate routine in the DLL.

To verify that the Divide() function was properly exported, we can use dumpbin to print the exports of our DLL.

C:\Sources\RSC\C\Take1>dumpbin /exports Dll\Math.dll Microsoft (R) COFF/PE Dumper Version 7.10.6030 Copyright (C) Microsoft Corporation. All rights reserved. ... ordinal hint RVA name 1 0 00001000 _Divide@8 ...

Now that we have built our library, we can test it. Our test case is a command line program that takes two integer arguments and calls the Divide() library routine in order to compute their quotient. The resulting arithmetic expression is then printed using this result. The contents of our source file Test.c are below.

Test.c

#include <stdio.h> #include <stdlib.h> #include <Math.h> int __cdecl main(int argc, char **argv) { int a, b, c; if (argc != 3) { fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]); exit(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); c = Divide(a, b); printf("%d / %d = %d\n", a, b, c); return 0; }

Note the following about the test case sources:

  1. In addition to standard C include statements we include Math.h to declare the interface for our math library. In addition to declaring the Divide() function so that it may be called by our test program, this also tells the linker that it should be imported from a DLL.
  2. After a quick argument check, the two arguments are converted to the integers a and b. The Divide() library function is then called to compute the quotient of a and b. To detail the inputs and result, we print the entire arithmetic expression before exiting.

The test case may be built just as a regular executable program making sure to include the math library's associated LIB file in the link command. The resulting executable program will import the Divide() function from Math.dll. We can see this using dumpbin.

C:\Sources\RSC\C\Take1>dumpbin /imports Test\Test.exe Microsoft (R) COFF/PE Dumper Version 7.10.6030 Copyright (C) Microsoft Corporation. All rights reserved. ... Math.dll 4060D4 Import Address Table 406EA8 Import Name Table 0 time date stamp 0 Index of first forwarder reference 0 _Divide@8 ...

Running our test case with some simple inputs shows that our math library is working and the Divide() library function is returning the correct results.

C:\Sources\RSC\C\Take1\Dll>..\Test\Test.exe 8 2 8 / 2 = 4

Take 2 - Structured Exception Handling

View Full Sources

What happens to our test program if we attempt to divide by zero? If we try doing so, Windows Error Reporting takes over and a popup window tells us that our program crashed. We really should handle errors such as these ourselves and the least intrusive (and possibly least practical) way to do this is to use structured exception handling.

Structured exception handling allows programs to raise and ultimately handle exceptional conditions outside of the normal flow of control. To use structured exception handling, code is wrapped in __try/__except blocks. If code in the __try block raises an exception and the filter expression for the __except block results in EXCEPTION_EXECUTE_HANDLER, then control will jump from the current locaiton in the __try block to the beginning of the __except block. After the __except block is executed, the process resumes normal execution after. In our case, dividing by zero results in an EXCEPTION_INT_DIVIDE_BY_ZERO exception which can be handled with appropriate __try/__except blocks.

In order to use structured exception handling, all we have to do is wrap our call to Divide() using __try/__except. This approach requires no changes to the math library itself. Below is our new version of our test case, Test.c. Lines that have been changed are in bold.

Test.c

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <Math.h> int __cdecl main(int argc, char **argv) { int a, b, c; if (argc != 3) { fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]); exit(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); __try { c = Divide(a, b); } __except(EXCEPTION_EXECUTE_HANDLER) { fprintf(stderr, "Caught exception code 0x%08x\n", GetExceptionCode()); exit(EXIT_FAILURE); } printf("%d / %d = %d\n", a, b, c); return 0; }

The first thing to note is the inclusion of windows.h which pulls in the macros and function declarations required to use structured exception handling. We then place our call to Divide() in a __try block and follow it with an __except block with a filter expression of EXCEPTION_EXECUTE_HANDLER. This overly simplistic filter expression states that the __except block should be executed for exceptions of any type. To handle an exception, we simply print a message including the exception code and exit the program.

Using our new test program, we get the following when attempting to divide by zero.

C:\Sources\RSC\C\Take2\Dll>..\Test\Test.exe 8 0 Caught exception code 0xc0000094

Standard exception codes are defined using Windows NT status codes. By looking up this exception code in ntstatus.h, we find that it is the NT status code STATUS_INTEGER_DIVIDE_BY_ZERO. Looking in WinBase.h, we also find that this NT status code is used to implement EXCEPTION_INT_DIVIDE_BY_ZERO.

In addition to handling hardware exceptions, such as a divide-by-zero exception, structured exception handling enables functions to raise their own exceptions by using the RaiseException() API. An exception code is simply a 32-bit integer and standard exception codes are defined in WinBase.h. Additionally, architects may define their own exception codes. This means that structured exception handling may be used to signal arbitrary logic errors or state errors as they are encountered by functions. However, due to the fact that exception codes do not support any sort of hierarchy, the cumbersomeness of filter expressions and the lack of support for structured exception handling on other operating systems, structured exception handling is typically reserved for specific niche applications, such as Windows operating system drivers.

We need a more practical method for reporting errors which occur in Divide() to the caller.

Take 3 - Thread-Local Error Codes

View Full Sources

Another way to pass error information to the caller is to use thread-local error codes. Thread-local error codes are set using the SetLastError() function which stores the error code in a location specific to the current thread. If a function signals to its caller that an error occurred, the caller can then call GetLastError() in order to query the latest error code associated with the current thread. Generally, GetLastError() should only be called if a function signals to its caller that an error occurred, as most API's will not clear the thread-local error code if they are successful. How a function signals an error condition is API specific, but often errors are signaled by return values of NULL, FALSE, zero or -1.

For this iteration, we need to change Divide() such that it returns a value that can be used to determine if the operation was successful. The new Math.h with this change is below.

Math.h

#ifndef __RSC_C_MATH__ #define __RSC_C_MATH__ #ifdef __Building_MathDll__ # define EXPORT_SYMBOL __declspec(dllexport) #else # define EXPORT_SYMBOL __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif EXPORT_SYMBOL BOOL __stdcall Divide(int a, int b, int *c); #ifdef __cplusplus } #endif #endif /* !__RSC_C_MATH__ */

The Divide() function now returns a boolean value which reports if the function call was successful. If the result is FALSE, we know to check the thread-local error code to determine the error. Also, we have added a third argument which will be used to return the result of our computation by reference.

Our library routine must be modified to match. Below is the new iteration of Math.c.

Math.c

#include <windows.h> #include "Math.h" BOOL __stdcall Divide(int a, int b, int *c) { if (b == 0) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } *c = a / b; return TRUE; }

We first include windows.h as it defines the codes and types we will be using. We then change the function signature to match the declaration in our header file. Before performing the computation, we check if the denominator is zero. If it is, we set the thread-local error code and return FALSE to tell the caller that we experienced an error. Otherwise, we perform the computation, store the result in the memory pointed to by c and return true.

The test case must be updated to use our new version of Divide(). The new iteration of Test.c is below.

Test.c

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <Math.h> int __cdecl main(int argc, char **argv) { int a, b, c; if (argc != 3) { fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]); exit(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); if (!Divide(a, b, &c)) { fprintf(stderr, "Cannot divide: %d\n", GetLastError()); exit(EXIT_FAILURE); } printf("%d / %d = %d\n", a, b, c); return 0; }

The call to Divide() is now made in an if statement which will run our error handling code if the call returns FALSE. Our error handling code calls GetLastError() to obtain the latest thread-local error code, prints an error message and then exits the test program. Note that a reference to c is now passed as the third argument to Divide() to obtain the result of the computation.

Running our test program with a bad input now results in the following output.

C:\Sources\RSC\C\Take3\Dll>..\Test\Test.exe 8 0 Cannot divide: 87

Looking up the error code in WinError.h, we see that 87 is in fact ERROR_INVALID_PARAMETER as expected. Note that we could use the FormatMessage() API to translate this error code into a human readable message.

Using this method, we can effectively return error codes to the caller of our library routine, but this approach also has portability issues. GetLastError() and SetLastError() are Microsoft Windows specific API's which are intented to be used with System Error Codes. Other platforms exclusively support thread-local error codes via the errno variable which should only be used with error codes which are defined in errno.h. Note that Windows even supports errno, but it is only used by the operating system to return errors encountered by functions in the C Standard Library. The operating system however does not provide a utility function for mapping errno codes to corresponding System Error Codes or vice versa.

To address portability issues, one may attempt to implement an abstraction on top of the thread-local error handling routines so that callers can pass error codes in a platform agnostic manner. If we choose to use errno to store thread-local error codes on all platforms, then System Error Codes should be translated to the appropriate errno codes, however there are significantly more System Error Codes than errno codes, so we may loose important information in the translation process. Another option would be to abuse errno and place System Error Codes directly in the errno, but doing so would be a disgusting hack which could result in strange bugs in error handling routines due to programmer error and side effects.

If portability is not an issue and the target library is only expected to run on Windows, then this method may be acceptable as long as exported library functions do not heavily utilize COM or COM support routines, as COM uses its own error code type, the HRESULT. There is no way to reliably map all HRESULT codes to System Error Codes such that they may be correctly used with GetLastError() and SetLastError(). However, if the library does not use HRESULT codes or the HRESULT codes that it encounters are simple enough to be mapped to System Error Codes, then this may be an option.

If the target library is only intended to run on platforms that exclusively use errno, then using errno may be an option as long as the error conditions reported by the library routines can easily be represented as errno codes supported on the target platforms. Widely supported errno codes are significantly fewer in number than System Error Codes, so it may be harder to find an errno code that sufficiently describes the error condition. For example, the System Error Code ERROR_NOT_OWNER may be used on Windows to notify the caller that an attempt was made to release a mutex not owned by the caller and the corresponding human readable string returned by FormatMessage() would make sense. On a Unix platform however, making the same mistake may result in EINVAL which simply means that an argument is invalid. Many other API's will return EINVAL if used improperly, making it significantly harder to determine what actually went wrong. Creating your own extended set of error codes and using errno to communicate them to the caller is generally a bad idea because we should be able to use the operating system provided strerror() API to obtain a human readable string for any value stored in errno.

It may also be possible to implement thread-local error handling for for the error encoding of our choice without abusing operating system provided error handling mechanisms. Many modern compilers support thread-local storage for at least the basic C types. One could implement a type-safe thread-local error handling mechanism using this feature, but this would restrict portability of the resulting library to specific platforms and compilers that support thread-local storage. Furthermore, thread-local storage has cumbersome implementation limitations on some of the supported platforms. On Windows XP for example, a library using compiler support for thread-local storage via __declspec(thread) cannot be loaded on demand via LoadLibrary(). As a workaround, the library must use TLS API's in its DllMain routine to manually manage thread local storage for each thread.

Thread-local error handling has another serious drawback for architects designing kernel space libraries. Operating system support for thread-local error handling and thread-local storage is not available in kernel mode. These features are only available in user mode. Thus, this approach is not even an option for libraries that run in the kernel.

What we need is a more practical and portable method of communicating errors to the caller.

Take 4 - Returning Error Codes

View Full Sources

Probably the simplest way for a C library routine to communicate an error condition to its caller is for it to just return an error code. That error code should then be checked to determine if an error actually occurred. If an error is encountered, then the caller can run an appropriate error handling routine.

For this iteration, we need to change the function signature for Divide() such that it returns an error code. Below are the new contents of Math.h.

Math.h

#ifndef __RSC_C_MATH__ #define __RSC_C_MATH__ #ifdef __Building_MathDll__ # define EXPORT_SYMBOL __declspec(dllexport) #else # define EXPORT_SYMBOL __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { #endif EXPORT_SYMBOL DWORD __stdcall Divide(int a, int b, int *c); #ifdef __cplusplus } #endif #endif /* !__RSC_C_MATH__ */

The function Divide() now returns a DWORD which represents an error code. This error code should be checked by the caller to determine if the Divide() operation was successful.

Below is our new iteration of Math.c.

Math.c

#include <windows.h> #include "Math.h" DWORD __stdcall Divide(int a, int b, int *c) { if (b == 0) { return ERROR_INVALID_PARAMETER; } *c = a / b; return ERROR_SUCCESS; }

The return type of Divide() has been changed to reflect that we are now returning an error code. When the function checks the denominator, it now returns ERROR_INVALID_PARAMETER if the argument is zero. If the denominator is non-zero, the computation is performed as before and ERROR_SUCCESS is returned to the caller.

Our updated test case is below.

Test.c

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <Math.h> int __cdecl main(int argc, char **argv) { int a, b, c; DWORD error; if (argc != 3) { fprintf(stderr, "Usage: %s <a> <b>n", argv[0]); exit(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); error = Divide(a, b, &c); if (error) { fprintf(stderr, "Cannot divide: %d\n", error); exit(EXIT_FAILURE); } printf("%d / %d = %d\n", a, b, c); return 0; }

A new local variable, error, is introduced to store the error code of our library function. When we call Divide(), we store the resulting error code in error and pass a reference to c as the third argument. If our library call resulted in an error, we print an error message and exit the program.

An attempt to divide by zero now results in the following.

C:\Sources\RSC\C\Take3\Dll>..\Test\Test.exe 8 0 Cannot divide: 87

As expected, this is exactly the output of the previous test case.

While this approach is both portable and simple in theory, it changes the way that functions return results. Take for example a memory allocator. The malloc() routine returns a pointer to a freshly allocated block of memory. When an error occurs, it returns NULL and stores the appropriate error code in errno. If a developer chooses not to check for errors and next function calls, the result of a call to malloc() may be passed to another function without the use of any intermediate variables. This can result in simpler code at the expense of error handling. When directly returning error codes, an implementation of malloc() must instead return pointers via an additional parameter which is passed by reference. Developers may still neglect to perform proper error handling when using such API's, but they will not be able to nest function calls. Later, when proper error handling is implemented, the task should be easier as nested function calls will not have to be decomposed.

While this approach removes the dependency on thread-local error codes, there is another layer of complexity which applies to all methods of communicating errors to the caller. In order to allow users of our library to consistently handle errors across all platforms, we must choose a standardized error encoding and use it for all of our library interfaces that can encounter an error. When our library encounters an error internally, it should be translated to one of our standardized error codes before it is passed to the caller. The trick is to retain enough information in the translated error code so that the user of your library can understand what went wrong.

Even using this technique, there is still significant responsibility placed on the architect to implement a portable, reusable software component. The next section explores the use of a standardized error encoding.

Take 5 - Portable Error Handling

View Full Sources

By using a standardized encoding for errors, the error handling for our library can be completely platform agnostic. Error handling is usually one of the more complicated aspects of writing portable software as many platform specific operations can be hidden via abstractions, but detailed error codes should allow the caller to determine exactly why a function failed. Detailed error codes are important so that users of a library can reliably handle expected errors and to make debugging easier when things go horribly wrong.

In this iteration, we need to define our new error type and error codes in Math.h and update the declaration of Divide() accordingly.

Math.h

#ifndef __RSC_C_MATH__ #define __RSC_C_MATH__ #ifdef __Building_MathDll__ # define EXPORT_SYMBOL __declspec(dllexport) #else # define EXPORT_SYMBOL __declspec(dllimport) #endif typedef int MathError_t; #define MATH_ERROR_SUCCESS 0 #define MATH_ERROR_DIVIDE_BY_ZERO 1 #ifdef __cplusplus extern "C" { #endif EXPORT_SYMBOL MathError_t __stdcall Divide(int a, int b, int *c); EXPORT_SYMBOL const char * __stdcall GetMathErrorStr(MathError_t error); #ifdef __cplusplus } #endif #endif /* !__RSC_C_MATH__ */

Above, we have defined a new type, MathError_t, which will be used to represent error codes used by our library. Two library specific error codes are also defined: one representing success and the other representing a divide-by-zero error. The declaration of Divide() is changed so that it now returns an error of type MathError_t. Also, a new function called GetMathErrorStr() is defined which returns a human readable error string for a given error code.

Below is our new version of Math.c.

Math.c

#include <windows.h> #include "Math.h" MathError_t __stdcall Divide(int a, int b, int *c) { if (b == 0) { return MATH_ERROR_DIVIDE_BY_ZERO; } *c = a / b; return MATH_ERROR_SUCCESS; } const char * __stdcall GetMathErrorStr(MathError_t error) { if (error == MATH_ERROR_SUCCESS) { return "No error"; } else if (error == MATH_ERROR_DIVIDE_BY_ZERO) { return "Divide by zero"; } else { return "Unknown error"; } }

Our implementation of Divide() has been changed to return library specific error codes. Furthermore, we have implemented GetMathErrorStr() using if/else if/else. As our library grows and our list of error codes gets larger, we may consider using a different method to store and lookup strings such as an array.

Corresponding changes to our test case are below.

Test.c

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <Math.h> int __cdecl main(int argc, char **argv) { int a, b, c; MathError_t error; if (argc != 3) { fprintf(stderr, "Usage: %s <a> <b>\n", argv[0]); exit(EXIT_FAILURE); } a = atoi(argv[1]); b = atoi(argv[2]); error = Divide(a, b, &c); if (error) { fprintf(stderr, "Cannot divide: %s\n", GetMathErrorStr(error)); exit(EXIT_FAILURE); } printf("%d / %d = %d\n", a, b, c); return 0; }

The type of our error variable is now MathError_t so that it correctly represents errors from our library. GetMathErrorStr() is now used to translate the library specific error code into a human readable string.

Running the test case now results in the following output.

C:\Sources\RSC\C\Take5\Dll>..\Test\Test.exe 8 0 Cannot divide: Divide by zero

This output is both library specific and human readable.

Conclusions

In this article, we showed how to implement a reusable software component in C on the Microsoft Windows platform. Over five different iterations, we produced a DLL and a test case which illustrate different methods for communicating error conditions to the caller.

If software portability is not a problem, then utilizing thread-local error codes may be an option. On Windows, GetLastError() and SetLastError() may be used for user mode libraries as long as the library does not internally use COM or any COM support routines which return HRESULT codes which cannot be easily mapped to System Error Codes. On other platforms, errno may be used as long as the library does not need to define custom error codes.

When portability or proper error handling are major concerns, library routines should directly return error codes. Designing interfaces this way also helps prevent nested function calls which must be decomposed to implement robust error handling. Care must be taken to define a standardized error encoding for the library, return standard error codes from each library routine that can encounter an error and map native error codes to standard error codes before passing them to the caller. To aide usability and debugging, a library should provide a mechanism to translate its own standardized error codes into human readable strings.

There are a couple of techniques that are occasionally seen in C libraries that were not discussed in this article. One of these techniques stores the latest error code in a context or state structure passed to library function calls. This pattern is usually seen in libraries that implement general purpose file I/O where files are opened using a path name, I/O is performed and finally the file is closed. The context structure used to store file state can also store the latest error encountered while performing file operations. When any of the file operations signal an error condition to the caller, the context may be querried for the latest error code. This approach however encourages practices like nesting functions which discourages developers from checking for errors.

Designing a truly portable library interface is hard. Care must be taken not only to intelligently expose the correct interfaces to the user, but also to reliably report error conditions to the caller. The caller must be able to discriminate between different errors in order to perform robust error handling. Finally the caller must be able to map errors to human readable strings to aide usability and debugging.

About the Author

David P. Reese, Jr. is a Principal Software Engineer in the Office of the CTO at McAfee, Inc.