Tech Articles


Interfacing PB Applications with the Windows API - Part 2


This tutorial discusses how to interface traditional, Windows-hosted PowerBuilder 32-bit and 64-bit applications with the Windows Application Programming Interface (API), also known as the WinAPI. Much of the information presented here also applies to interfacing with DLL’s created in-house or by third-party vendors, because the interface mechanism in PB is the same. The focus, however, will be on interfacing to the WinAPI.

* * * Part two content has been revised and expanded from the original version * * *

The tutorial is presented in four parts. Part one reviewed External Function Declarations (EFD’s) in PB and explored issues related to interfacing with the WinAPI. The second part examines the calling conventions in 64-bit Windows and important differences between the 32-bit and 64-bit environments. Part three looks at several factors that can affect the interfacing of PB applications with the WinAPI, such as null values, PB datatypes not supported by Windows, the PB Any datatype, unbounded arrays, nested structures and arrays of nested structures. The tutorial concludes in part four beginning with the description of an available free PB sample application and non-visual object that can dynamically determine the memory size and layout of a structure in either 32-bit or 64-bit environments. Part four also contains a list of coding tips & techniques to help you develop PB applications that interface with the WinAPI, and mapping tables to assist in the translation between many common WinAPI datatypes and the standard PB datatypes.

What Is “Boundary Alignment”?

Boundary alignment is all about improved performance. In general, the speed at which CPU’s can fetch and store a value in memory improves when the beginning and ending memory addresses for the value are “aligned”. This occurs when the starting memory address is evenly divisible by the value length. For example, boundary alignment occurs when the memory address for an eight-byte integer is evenly divisible by eight. Boundary alignment is critical to how the members of a structure are organized, or “packed” in memory.

Boundary alignment is a convention that developed over time. Historically, values of various lengths could reside in memory at any starting address. This simplistic technique is called single-byte alignment, or 1-byte packing. Today, Windows uses 8-byte alignment packing. Note this does not mean that every value begins in memory where the memory address is evenly divisible by eight! It means boundary alignment occurs up to a maximum value of eight. For example, a PB Byte variable can reside at any memory address because each variable is one byte in length. An UnsignedInteger has two-byte alignment and a Long or UnsignedLong has four-byte alignment. LongLong’s have eight-byte alignment.

Windows, and code that utilizes the WinAPI (such as PowerBuilder) utilize 8-byte alignment packing.

When a structure argument is passed by reference (via a pointer) to a WinAPI function, the rules that dictate how structure members are laid out in memory have to be understood and followed by the caller and by Windows or their ability to communicate with each other breaks down.

Note: PB by default uses the same eight-byte alignment packing conventions as the Windows O/S.

Dynamic Link Libraries created in-house or by third-party vendors are not required to use eight-byte alignment packing, so we’ll return to this topic soon.

Differences Between 32-bit & 64-bit Windows

The major differences between 32-bit applications and 64-bit applications are:

  • Address space – Memory addresses in 32-bit applications in Windows are limited to 32 bits in length and to 64-bits in 64-bit applications. Pointers (variables that are used to hold a memory address) are eight bytes long in 64-bit Windows instead of four bytes in 32-bit.

    64-bit pointers must be aligned to begin on an eight-byte boundary (the memory location of the pointer variable must be evenly divisible by eight). In 32-bit, pointers use four-byte alignment.
     
  • Handles – Handles (internal Windows identification numbers), like pointers, are four-byte integers in 32-bit and eight-byte integers in 64-bit.

As mentioned in part one of the tutorial, the names of several Windows system DLL's falsely give the impression they are for use only in a 32-bit environment (e.g., "user32.dll"). In 64-bit versions of windows, there exists both 32-bit and 64-bit versions of all system binaries and the 32-bit files are named identically to their 64-bit counterparts. The versions are kept in separate subdirectories (C:\Windows\System32 and C:\Windows\SysWOW64).

In addition to pointers and handles, the LongLong eight-byte integer datatype (and the eight-byte floating-point Double datatype, not discussed here) are each aligned on an eight-byte boundary.

Pointers (Memory Addresses)

Pointers occupy eight bytes in 64-bit applications instead of four bytes in 32-bit processes. In C/C++ and in C#, array arguments are always described as a pointer to the first element of the array. Therefore, any argument parameter that is an array is always automatically passed as a pointer, regardless of how the argument parameter is being passed. Refer to part one of the tutorial for more information.

It is easy for forget that, by convention, a C/C++ (and PowerBuilder) string is implemented as a dynamically-allocated one-dimensional array of characters which includes a null termination character at the end. Since a string is actually an array, a String argument automatically gets passed as a pointer by the PB compiler, and since the PB compiler understands this, it automatically changes a String argument parameter in an EFD from a four-byte pointer to an eight-byte pointer whenever an application is compiled for 64-bit. Part one of the tutorial explained how the “REF” keyword is not required unless the calling method needs access to any changes in the string made by the called method, but the String argument gets passed via a pointer regardless.

Even if a PB String is passed to a DLL function by value, the DLL is free to make changes to the array of characters that comprises the string. PB prevents the DLL from affecting the content of the string in the PB application by passing the address of a throwaway copy of the string to the DLL. PB also passes a copy of all other types of arrays which are passed to a DLL function by value.

Therefore, you must use the "REF" keyword when describing an argument parameter in the EFD whenever the DLL can change the parameter and PB needs access to that change.

The names of the datatypes for pointer arguments in the WinAPI typically begin with “P” (for “pointer”) or “LP” (for “long pointer”, which is an equivalent term), so a datatype that begins with “P” or “LP” is very likely to occupy eight bytes in 64-bit and four bytes in 32-bit.

When the ability to create 64-bit applications was added to PB in version 12.6, a new pseudo-datatype, Longptr (long pointer) was created. Even though it is listed as a standard PB datatype in the PB documentation and Help, the Longptr datatype is unique because is it not a true, full-fledged PB datatype. Under the covers, the compiler swaps the Longptr pseudo-datatype for the Long datatype in 32-bit compilations or for the LongLong datatype in 64-bit. In other words, a PB variable of type Longptr is four bytes long in a 32-bit application and eight bytes long in a 64-bit application.

I’m an advocate of using Hungarian notation in PowerScript code. I use the abbreviation “lptr” to signify the Longptr datatype in the name of Longptr variables and structure members, such as:

Longptr llptr_hwndparent   // Windows handle to the parent window

In 64-bit Windows, the convention is that a pointer must be aligned on an eight-byte boundary (a memory address that is evenly divisible by eight). This can impact the internal layout and size of structures, as will be seen in the upcoming example regarding Windows handles.

Handles

In Windows terminology, a “handle” is a whole number value used to identify a resource. To the uninitiated, it may seem that nearly everything in Windows is a handle, and that impression is not too far from the truth! For example, each instantiated window in an application has a handle, as does every visual control. So too, does: a font; a monitor; a device context; a brush; a bitmap; an icon; a menu; a cursor, etc. You get the idea.

Handles in 32-bit Windows are four bytes long, aligned on a four-byte boundary. Handles in 64-bit Windows are eight bytes long and convention dictates they must be aligned on an eight-byte boundary. The Longptr datatype is ideal for use with any value that is a Windows handle. The typedef’d Windows datatypes that represent handles have names that begin with “H”, such as “HWND” for a handle to a window.

Continuing the example of the FlashWindowEx WinAPI function from part one of the tutorial, recall that it requires the use of a FLASHWINFO structure, which is defined in WinAPI online documentation as the following C/C++ type definition:

typedef struct {
  UINT  cbSize;
  HWND  hwnd;
  DWORD dwFlags;
  UINT  uCount;
  DWORD dwTimeout;
} FLASHWINFO, *PFLASHWINFO;

This structure contains a Windows handle (the second member). Assuming for a moment a 32-bit execution environment, the Windows handle and the other four structure members are all four-byte, unsigned integer values (you can verify this using the WinAPI to PowerBuilder datatype conversion table in part four of this tutorial).

The “cbSize” member is to be assigned the structure size (in memory) prior to calling the FlashWindowEx API function. The structure length is 20 bytes in a 32-bit execution environment, because it contains five consecutive four-byte members without any need for member alignment padding.

An example of the proper layout of the equivalent PB structure (shown in exported PB source code) is shown below:

// WinAPI FLASHWINFO structure (32-bit & 64-bit compatible)
global type s_flashwinfo from structure
  unsignedlong ul_cbsize
  longptr      lptr_hwnd
  unsignedlong ul_dwflags
  unsignedlong ul_ucount
  unsignedlong ul_dwtimeout
end type

Note the use of the Longptr datatype for the second member of the structure (the handle to the window that is to be “flashed”) instead of the Long or UnsignedLong datatype. This is going to be necessary when the structure gets compiled for a 64-bit executable because a Windows handle in a 64-bit process is an eight-byte integer value.

It is relatively easy to see from the structure’s source code that the length of the structure in 32-bit is 20 bytes since this simple structure contains five four-byte structure members, as mentioned earlier.

Pop Quiz: What do you think Windows requires the structure size to be in 64-bit?

Well, the Longptr member increases in size from four bytes to eight, so the answer is 24, correct?

Sorry, that is incorrect. The correct structure length in a 64-bit application is 32 bytes, because of member boundary alignment considerations and structure alignment conventions that have not yet been discussed. Here’s how it breaks down:

  • The structure must now begin on an eight-byte boundary instead of a four-byte boundary (we’ll discover why shortly). The first member, ul_cbsize, is a four-byte integer. No issues there.
     
  • The Longptr second member, lptr_hwnd, is now an eight-byte integer. It needs to be eight bytes long in 64-bit because it will contain a Windows handle. Recall, however, that an eight-byte integer must begin on an eight-byte boundary. Since the structure begins on an eight-byte boundary and the first member is four bytes long, Windows requires four bytes of “alignment padding” between the first and second members of this structure to ensure the second member begins on an eight-byte boundary. Therefore, the size of the structure after the first two members is 16 bytes, not 12 as you might first assume.
     
  • Three additional four-byte integers (all on four-byte boundaries) complete the structure, which brings the structure size to 28 bytes. So why is the structure size actually 32 bytes?
     
  • Even though there are no remaining structure members, there are alignment rules for both the start and end of a structure. We have not yet learned what these rules are, but they exist. One of these rules is that a structure’s ending alignment must be the same as the starting alignment, which in this example is now eight. Because the size of the structure after the fifth member is 28 bytes and the boundary alignment value is eight, an additional four bytes of “padding” are required to ensure the length of the entire structure is evenly divisible by eight. This brings the size of the structure to 32 bytes. 

The following shows how the PB structure needs to be conceptually defined for a 64-bit process (emphasis illustrating the unseen alignment padding members has been added):

// How the FLASHWINFO structure is actually laid out in 64-bit:
global type s_flashwinfo from structure
  unsignedlong ul_cbsize
  byte         byte_padding_reserved1[4]
  longptr      lptr_hwnd
  unsignedlong ul_dwflags
  unsignedlong ul_ucount
  unsignedlong ul_dwtimeout
  byte         byte_padding_reserved2[4]
end type

Do these differences imply that you have to create two structures and accordingly, two EFD’s in order to be able to invoke some Window API functions in either environment with a single code base? At a minimum, it appears you will need to be able to determine at run time in your PB application if it is executing in 32-bit or 64-bit and utilize an appropriate EFD and use the structure that is suitable for the execution environment. This can certainly be done, but that’s a messy, complex and inconvenient implementation.

However, it’s not necessary! You’ll soon learn how to avoid this complexity, use a single structure and a single EFD in both the 32-bit and 64-bit execution environments.

Eight-Byte Integers

Generally speaking, aside from pointers and Windows handles there are few designated eight-byte integer (PB datatype LongLong) values passed into or out of WinAPI functions. However, the eight-byte boundary alignment concerns in a 64-bit execution environment are the same for eight-byte integers as for pointers and Windows handles.

Structure Alignment and Length

What determines the boundary alignment for a structure? It would be nice if the rule was as simple as “All structures must begin and end on an eight-byte boundary” …but unfortunately, that is not the case. Here’s the rule:

The boundary alignment value of the beginning and end of a structure equals the largest alignment value of its members.

Let’s review the FLASHWINFO structure in light of this rule:

  • In 32-bit, all five structure members are four-byte integers, each aligned on a four-byte boundary. Therefore, the FLASHWINFO structure must begin and end on a four-byte boundary. No alignment padding prior to the end of the structure is needed, so the structure size is 20 bytes.
     
  • In 64-bit, the second structure member (defined as type Longptr because it is a Windows handle) is now an eight-byte integer aligned (after padding) on an eight-byte boundary. The FLASHWINFO structure must now begin and end on an eight-byte boundary instead of four, so four bytes of padding are required after the last structure member. 

Nested Structures and Their Impact on Structure Alignment

The preceding example showed how structure alignment conventions can impact PB code that interacts with the WinAPI, depending upon the process bitness. Another way it can be affected is when structures are nested.

A nested structure is simply a structure that is a member of another (“parent”) structure. In order to determine the alignment needs of the parent structure, the alignment value of all nested structures contained in the parent structure and other parent structure members has to be determined and the largest value is used.

Boundary alignment of a nested structure implies that alignment padding may be required in front of a nested structure in addition to potential alignment padding after the last member in the nested structure. We’ll revisit this topic in part three of this tutorial.

Progma_Pack( 1 ): Single-Byte Alignment Packing

We’ve seen how PB and Windows utilize eight-byte alignment packing to organize how variables, including structure members and nested structures, are laid out in memory. What if your PB application needs to interface with a DLL that does not use eight-byte packing, such as a DLL from a third-party vendor?

Single-byte, also known as 1-byte packing can be specified in an EFD via the PROGMA_PACK( 1 ) directive, as illustrated in the following example (emphasis added, and upper/lower case is immaterial except for the function name):

FUNCTION UnsignedLong UnlockTreasure ( &
   s_TreasureChest   tc, &
   REF s_combination pcomb &
   ) LIBRARY “HidItWell.dll” PROGMA_PACK( 1 )

When the progma_pack( 1 ) directive is included in the EFD, PowerBuilder will, at runtime:

  • Dynamically create, if needed, a temporary version of all structures used as argument parameters (including nested structures) that follow the single-byte alignment rules.
     
  • Copy the data values in the eight-byte aligned structures to the single-byte version(s).
     
  • Call the external function in the DLL.
     
  • Upon return, if any structure arguments were passed by reference, the data values in the single-byte aligned version(s) are copied back into the original, “normal” eight-byte aligned version of the structure(s). 

Progma_Pack( 8 ): Business as Usual

For completeness, I’ll mention that PowerBuilder also supports the progma_pack( 8 ) directive in an EFD to explicitly request the eight-byte alignment packing that occurs by default. The only reason to specify progma_pack( 8 ) I can think of would be where progma_pack( 1 ) is also being used; In this scenario I'd be inclined to specify progma_pack( n ) in all EFD's to clearly draw the distinction between the two options.

Note: You may have noticed that every reference to the progma_pack directive in this tutorial include a blank on either side of the "1" or "8". Syntactically, there's no reason to do this, and normally, I wouldn't do this, but I've been coerced into doing so because the HMTL-based editor used by the Appeon Community web site insists on translating an "eight, close parenthesis" character sequence into an Emoticon. I added these spaces to prevent that from happening.

Determining Structure Size (Length)

What about setting the structure size, or length, in a member of the structure itself, if this value may vary based on the bitness of the execution process? As seen in the example of the FlashWindowEx API function and the FLASHWINFO structure, the structure size member (ul_cbsize) needs to be set to 20 in a 32-bit environment or to 32 in a 64-bit environment.

The simplest and most straight-forward solution is to check the process bitness in the application and set the appropriate hard-coded value. Here’s an example on how to accomplish this:

// How to check process bitness at execution time
// and set structure length member accordingly.
s_flashwinfo lstr_flash
Environment  lenv

// Obtain PB environment object and check bitness property.
// then set structure length member accordingly.

GetEnvironment(lenv)
If lenv.ProcessBitness = 64 Then
   lstr_flash.ul_cbsize = 32
Else
   lstr_flash.ul_cbsize = 20
End If

Aside from determining the correct PB datatype(s) to use, perhaps the most challenging part of interfacing to the WinAPI is determining structure sizes, especially when the size of the structure changes based on the bitness of the execution process.

The FLASHWINFO structure is pretty simple. But, as we’ve seen, there are subtle factors at play that can affect the determination of the correct structure size. The WinAPI utilizes a lot of structures, and many are much larger than the FLASHWINFO structure. Manually and accurately ascertaining structure size can be a daunting challenge as a structure grows in complexity. In part three of the tutorial, we’ll look at several additional conditions that can affect structure layout and size.

Later, in part four of the tutorial, we’ll learn about a free utility to dynamically calculate structure size that takes the bitness of the execution process into account.

Summary

We examined the calling conventions unique to 64-bit Windows processes and how they differ from 32-bit processes, with particular emphasis on structures. We learned how using the Longptr datatype can simplify or eliminate differences in calling WinAPI functions between 32-bit and 64-bit. We also showed how to recognize process bitness in a PB application in order to set the proper structure size when it is required, but this technique requires the structure size be manually determined and the size value(s) be hard-coded. In the next portion of the tutorial, we’ll discuss several additional factors that can affect the interfacing of PB applications with the WinAPI, such as null values, PB datatypes that are not supported by Windows, the PB Any datatype, unbounded arrays, nested structures and arrays of nested structures.

Revisions

The following changes have been made to the original version of Part Two:

  • Mention of how the 32-bit and 64-bit versions of Windows system DLL's are identically named has been included.

  • The explanation of how arrays are passed as pointers to DLL functions has been expanded 

  • Several minor word omissions, grammatical errors and other miscellaneous corrections have been made.
Comments (0)
There are no comments posted here yet

Find Articles by Tag

Excel TortoiseGit Source Control DataType OLE Git JSON Charts DLL JSONGenerator PowerServer Mobile Performance Transaction Mobile DataWindow Linux OS Text Outlook File OAuth Event Handling PowerServer Web Application WinAPI Model Event PostgreSQL ODBC driver InfoMaker BLOB Design Database Connection Stored Procedure Graph Windows OS .NET Std Framework Automated Testing Database Profile Import Open Source UI License PostgreSQL CrypterObject Window SOAP Elevate Conference RESTClient Jenkins Error Oracle Validation Authentication DevOps Web API iOS DataWindow JSON Migration Import JSON Expression Encryption RichTextEdit Control C# SDK Filter Resize Interface Testing PowerBuilder Compiler SqlModelMapper JSONParser SQL SQL Server Configuration RibbonBar NativePDF PowerBuilder Windows 10 Icons Debug Bug TLS/SSL Database OAuth 2.0 Messagging IDE Web Service Proxy Branch & Merge Azure PowerBuilder (Appeon) Trial Database Table Schema ODBC Encoding SqlExecutor .NET DataStore PFC Array Deployment Data GhostScript PBDOM COM Database Object Script SnapObjects Event Handler Service TreeView 32-bit PDFlib ActiveX Source Code UI Modernization Authorization Repository 64-bit WebBrowser REST XML External Functions OrcaScript Export Debugger Database Table Data PowerScript (PS) Menu PDF MessageBox Database Painter Database Table Android Debugging SnapDevelop CoderObject Platform RibbonBar Builder UI Themes Class Export JSON HTTPClient Sort API Installation Syntax TFS CI/CD DragDrop SVN Icon .NET Assembly Variable