ActivePatch Developer's Guide - Library Overview

ActivePatch can be used with a wide variety of programming languages and software development tools for Windows. The majority of the library functions use simple data types such as 16-bit and 32-bit integers, and null-byte terminated strings. To determine if your development language is capable of using the ActivePatch DLL, it should support all of the following features:

  1. It needs the ability to load a dynamic-link library (DLL) and call exported functions from that library. In some cases, as with C or C++, you can use an import library instead of dynamically loading the library. This allows you to link your program just as you would a standard function library. If the language does not support the use of import libraries, it must provide a mechanism for declaring a function and specifying the arguments that will be passed to it.
  2. Languages must call the ActivePatch functions using the stdcall calling convention. Parameters are pushed on to the stack in reverse order (from right to left), and the called function is responsible for clearing the stack. Note that this is different from the standard C/C++ calling convention in which the stack is cleared by the calling function, and from the Pascal calling convention in which arguments are pushed on the stack left to right.
  3. The language must support basic integer data types, including ‘short’ 16-bit integers and ‘long’ 32-bit integers. The language must also support a string data type that is represented as an array of bytes, terminated by a null-byte (a character with the ASCII value of zero). If a different native string type is used, the language must provide a means to convert between the native string format and a null-terminated byte array.
  4. The language must support passing function parameters by value and by reference. When a variable is "passed by value", a copy of its value is passed to the function on the stack. However, when a variable is "passed by reference", the memory address of the variable (typically called a pointer) is passed to the function. In most cases, the functions in the ActivePatch library expect integer data to be passed by value, while string and data structures are passed by reference.
  5. In addition to passing a variable by reference to a function, the language must provide the ability to allocate a block of memory of arbitrary size and be able to pass its address to a function. For example, in C there is the malloc() and free() functions, and in Visual Basic there is the Dim and ReDim statements.
  6. The language must support calling functions which do not return a value. For example, in C and C++, such functions are declared as void. In Visual Basic, a function which does not return a value is declared as Sub (a subroutine).
  7. Although not required, it is recommended that the language also support the ability to create user-defined data structures. For example, in C and C++, the struct keyword is used to define a data structure. In Visual Basic, the Type statement is used to create user-defined data structures.
Data Type Representations

Because various languages handle data types in different ways, the ActivePatch library has been designed to primarily use basic data types such as integers and strings. The following is a list of numeric data types that are used, along with their C and Visual Basic equivalents.

Description Size Range C / C++ Visual Basic  
Byte 1 byte 0 to 255 BYTE Byte
Boolean 4 bytes 0 is False, 1 is True BOOL Long
Handle 4 bytes 0 to 4,294,967,295 HPACKAGE   Long
Integer 4 bytes -2,147,483,648 to 2,147,483,647   int Long
Integer 4 bytes 0 to 4,294,967,295 UINT Long
Short Integer   2 bytes   -32,768 to 32,767 SHORT Integer
Short Integer 2 bytes 0 to 65,535 WORD Integer
Long Integer 4 bytes -2,147,483,648 to 2,147,483,647 LONG Long
Long Integer 4 bytes 0 to 4,294,967,295 DWORD Long
 

One problem that is frequently encountered when converting function definitions from C or C++ to other languages is the size of the integer data type. For example, default integer size for Visual Basic is 16-bits, even on 32-bit platforms. Also, some languages do not support unsigned integer types. In this case, as with Visual Basic, the signed type should be used instead.

Handles

Throughout the documentation for the ActivePatch functions, you will see references to "handles". Simply put, a handle is an unsigned integer value that is used to represent some object in memory. The actual value of the handle is not important and may refer to the memory address of a specific object, or it may be an index into a table of objects. When a handle is created, its value is unique for the life of each object created by the process. It is important that an application never make assumptions about the specific value of a handle. Handle values may be reused for objects that have been destroyed, and there is no guarantee that handle values are assigned in any particular order.

In ActivePatch, handles are used to refer to patch packages. The package handle, defined as an unsigned integer type called HPACKAGE, is returned by those functions which create a new patch package, or open an existing package. During the time that the package file is open, the package handle is used to refer to this file. When the application closes the package, the handle is destroyed.

The value of an ActivePatch handle varies between processes. The only constant is the value represented by INVALID_PACKAGE_HANDLE, which indicates that a given handle does not represent an open package file.

It is important to note that the handles used by functions in the ActivePatch library are not Windows handles. In other words, you cannot use functions like DuplicateHandle and CloseHandle with package handles. More importantly, they are not file handles and cannot be used with any of the file-related Windows API functions.

Boolean Data

Boolean parameters present a special problem for two reasons. Firstly, the data types used to represent boolean values frequently vary between languages. Secondly, different languages represent the values ‘true’ and ‘false’ differently. Boolean parameters (declared as BOOL in the function prototypes) should always be passed as 32-bit signed integers.

If you are passing a boolean parameter to a function, then ‘false’ should be represented by a value of zero and ‘true’ as a non-zero value (typically a value of one). When writing code that checks a boolean flag, or tests a boolean return value from a function, it is recommended that you test against a value of zero. For example, consider the following code in Visual Basic:

Dim bResult As Long
Dim pfInfo As PATCH_FILE_INFORMATION

bResult = GetPatchFileInformation(strPatchFile, strPassword, _
                                  PATCH_INFO_REFERENCE_FILE, _
                                  pfInfo)

If bResult = True Then
    ' The patch file and password is valid
Else
    ' The patch file or password is not valid, check
    ' the value of Err.LastDLLError for the error code
End If

In this example, even if the function GetPatchFileInformation was successful, the program would always believe that it failed because of the explicit test against the value True. This is because the function returns a value of 1 to indicate success, but Visual Basic defines True as –1. Instead, the code should be written as:

Dim bResult As Long
Dim pfInfo As PATCH_FILE_INFORMATION

bResult = GetPatchFileInformation(strPatchFile, strPassword, _
                                  PATCH_INFO_REFERENCE_FILE, _
                                  pfInfo)

If bResult <> 0 Then
    ' The patch file and password is valid
Else
    ' The patch file or password is not valid, check
    ' the value of Err.LastDLLError for the error code
  End If

In summary, the rule of thumb for dealing with boolean parameters is that they should always be 32-bit integer values, and you should always compare the boolean against a value of zero, never against a predefined constant.

String Data

String parameters can also present a problem when calling functions from languages other than C and C++. Different languages tend to have different internal representations of how string data is stored. The convention used by the ActivePatch library is that a string is an array of characters, terminated by a null-byte (a character with an ASCII value of zero). The length of the string is not stored in the string data itself, and by definition, a string cannot contain embedded nulls.

To use functions which require string parameters, the language must be capable of converting between its native string data type and the null-terminated character array expected by the ActivePatch functions. For example, Object Pascal provides the StrPCopy function. Note that Visual Basic provides implicit conversion between its native string type and null-terminated strings when a string is passed by value (ByVal) instead of by reference.

Another important consideration when passing string parameters is how the string is being used by the function. In most cases, the string is provided as input to the function (for example, as the hostname or address of a server to establish a connection with). However, in some cases the string is passed to the function as an output buffer, which the function copies data into. For example, the GetPackageProperty function stores the value of a package property into a string parameter. A Visual Basic programmer may write code that looks like this:

Dim strProductName As String
Dim nLength As Long

nLength = GetPackageProperty(hPackage, _
                             PACKAGE_PROPERTY_PRODUCT_NAME, _
                             strProductName, _
                             255)

Although this code looks correct, it will invariably result in a general protection fault or some other unpredictable error. The problem is that although the strProductName variable has been defined, no memory has been allocated for it. There are two ways that this can be done in Visual Basic. One is to declare the string as fixed-length, such as:

Dim strProductName As String * 256
Dim nLength As Long

nLength = GetPackageProperty(hPackage, _
                             PACKAGE_PROPERTY_PRODUCT_NAME, _
                             strProductName, _
                             255)

The other is to dynamically allocate memory for the string using the String function, such as:

Dim strProductName As String
Dim nLength As Long

strProductName = String(256, 0)
nLength = GetPackageProperty(hPackage, _
                             PACKAGE_PROPERTY_PRODUCT_NAME, _
                             strProductName, _
                             255)

One final consideration is that string data that is returned by the function will be null-terminated. For those languages that do not terminate strings with null-bytes, it may be necessary to remove the trailing null-byte. The complete example would be written in Visual Basic as:

Dim strProductName As String
Dim nLength As Long

strProductName = String(256, 0)
nLength = GetPackageProperty(hPackage, _
                             PACKAGE_PROPERTY_PRODUCT_NAME, _
                             strProductName, _
                             255)

' Trim string up to the terminating null-byte
strProductName = Left(strProductName, _
                      InStr(strProductName, Chr(0)) – 1)

If you are unsure of how your language handles null-terminated strings, we recommend that you review the language’s technical reference for information on how to call native Windows API functions. Since the Windows API also uses null-terminated strings, that same information can be used to determine how to call functions in the ActivePatch library.

Unicode Support

Unicode is a multi-language character set designed to encompass virtually all of the characters used with computers today. Unicode characters are represented by a 16-bit value, and differs from other character sets in two important ways. First, unlike the traditional single-byte (ANSI) character sets, Unicode is capable of representing significantly more characters in a variety of languages. Second, unlike multi-byte character sets (where some characters may be one byte in length, while others may be two bytes), the characters are fixed-width, which makes them easier to work with. Windows support for Unicode varies according to the platform. There is no Unicode support for 16-bit versions of Windows, or for 16-bit applications running on 32-bit platforms. On 32-bit platforms, Unicode is fully supported under Windows NT. However, support is extremely limited under Windows 95 and Windows 98.

The ActivePatch library supports both the single-byte and Unicode character sets under Windows NT/2000/XP, and Windows 95, 98 and Windows ME. This is done by having two versions of each function that either expects a string as an argument (including those functions which pass structures that contain strings) or returns the address of a string. The version of the function that expects a single-byte character set has a suffix of "A" (ANSI), while the function which expects the Unicode character set has a suffix of "W" (wide). Note that no suffix is used with those functions which only expect numeric parameters and return numeric values.

For example, consider the GetPackageProperty function mentioned in the previous section. If you looked at the list of exported functions in the library, you would see two functions exported, GetPackagePropertyA and GetPackagePropertyW. In C and C++, which function is called actually depends on how the application is being built. That is, if your application is built to use Unicode (in other words, the UNICODE macro is defined and you are linking with Unicode versions of the standard libraries), then the GetPackagePropertyW function will be used instead of the GetPackagePropertyA. In other languages, you may have to explicitly declare which version of the function you wish to use. In Visual Basic, for example, the Alias keyword must be used with the function declaration to specify the correct name.

When developing software for the 32-bit Windows platforms, whether or not to use Unicode is an important consideration. If you are developing an application specifically for Windows NT/2000/XP, then Unicode support is an option. However, if your application must run on Windows 95/98 in addition to NT, then it is recommended that you use the ANSI character set. Another alternative is to build two versions of the software, one that uses Unicode and is specific to Windows NT, and another that uses ANSI.

One final consideration when using Unicode is that, regardless of the character set, some of the ActivePatch functions use byte arrays, not character strings. This can create problems when reading and writing Unicode string data. For example, consider the StorePackageResource function which is used to store application specific metadata in a patch package. Because character strings and byte arrays are essentially identical when using the ANSI character set, a C/C++ programmer may try to write code such as this:

LPTSTR lpszData;
BOOL bResult;
int cchData;

lpszData = _T("This is a test, this is only a test");
cchData = lstrlen(lpszData);

bResult = StorePackageResource(hPackage, uID,
                               PACKAGE_RESOURCE_STRING,
                               lpszData, cchData);

This would work as expected until the programmer tries to compile for the Unicode character set. The problem is that the Unicode string is no longer an array of bytes, but is now an array of 16-bit (short) integers. The string must be converted from Unicode to a byte array before passing it to the StorePackageResource function. To do this, the WideCharToMultiByte function can be used as follows:

LPTSTR lpszData;
LPBYTE lpBuffer;
BOOL bResult;
int cchData;

lpszData = _T("This is a test, this is only a test");
cchData = lstrlen(lpszData);

#ifdef UNICODE
lpBuffer = (LPBYTE)_alloca((cchData + 1) * 2);
if (!lpBuffer) {
    // Unable to allocate memory
    return;
}

WideCharToMultiByte(CP_ACP, 0,
                    (LPCWSTR)lpszData, cchData,
                    (LPSTR)lpBuffer, ((cchData + 1) * 2),
                     NULL, NULL);
#else
lpBuffer = (LPBYTE)lpszData;
#endif

bResult = StorePackageResource(hPackage, uID,
                               PACKAGE_RESOURCE_STRING,
                               lpBuffer, cchData);

Note that the type of characters that are being converted may also present a problem to the developer. In this example, the string is easily converted because it is composed only of characters that are part of the basic ASCII character set. However, when converting a string that contains international characters (such as accented vowels) the conversion may result in unprintable characters. For additional information, check your programming language's technical reference for issues with regards to localization and the use of Unicode.