User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

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.

The tutorial is presented in four parts. Part one covers External Function Declarations in PB and explores 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.

The Methodology Used to Research This Tutorial

Some of the information in this tutorial is documented in various sources such as the PowerBuilder publications published by Appeon and online Microsoft documentation (docs.microsoft.com). Several blogs and forums were helpful, particularly in regards to Windows. Additional material was discovered by extensive experimentation in PowerBuilder 2017R2 and Visual Studio Community 2017.

Using a small PowerBuilder application that utilizes the RtlMoveMemory WinAPI function, more than 100 variations in structure and nested structure configurations were examined. Structures were populated with values easily recognizable in a memory snapshot, then the RtlMoveMemory WinAPI function would be used to populate a Byte array with a snapshot of the memory occupied by the structure(s). To a lesser extent, similar work was performed in Microsoft Visual Studio. The results frequently led to more questions, which in turn led to another round of web research and hands-on experiments. Over time patterns appeared and from these patterns, insight into the rules and factors that govern structure member layout was gained.

This approach also proved useful in determining how PB manages memory in regards to variables of type Any and structures with members of type Any or members that are an array of type Any. The results of the research concerning the PB Any datatype is detailed in part three of this tutorial.

PB External Function Declarations & the Windows API

For many years and through many versions of PowerBuilder, access to API functions in Windows has been accomplished by interfacing with what used to be called the Win32 API, a documented collection of entry points in several dynamic link libraries (DLL’s) provided by Microsoft as part of the Windows operating system, such as kernel32.dll, user32.dll and gdi32.dll. The Win32 API is now called the Windows API, presumably because it is no longer limited to the 32-bit execution environment.

Note: The names of several Windows system DLL’s falsely give the impression they are for use only for one specific bitness (e.g., “user32.dll”). In 64-bit versions of Windows, there are 32-bit and 64-bit versions of all system binaries that have the same name. The versions are kept in separate subdirectories (C:\Windows\System32 and C:\Windows\SysWOW64). The name of the Windows DLL stays the same, regardless of the bitness.

External Function Declaration Fundamentals

PowerBuilder is able to interface with DLL entry points via external function declarations. This tutorial uses the acronym EFD to refer to PB external function declarations. An EFD conveys to the PB compiler all of the information it needs to create the “under-the-covers” code to invoke a specific DLL entry point, pass arguments to the DLL entry point and accept a return value from it.

Important! The EFD does not actually execute an external function, it only describes how it can be executed.

Once the entry point for a WinAPI function, its parameters and return value are declared via an EFD, that particular API function can be called in PowerScript by the name the developer assigns to it in the EFD, as if the API function is a PowerScript function.

Note: External functions can be triggered or posted, like PowerScript and object functions. However, return values are not accessible from posted functions, including external functions. External functions can also be executed using dynamic lookup (i.e., via the “DYNAMIC” keyword when the function is called), the same as PowerScript functions and object functions. Static (i.e., "normal") lookup can be explicitly requested via the “STATIC” keyword when the external function is called.

Here is an example of a simple WinAPI interface in C/C++, as it is described in the Microsoft online documentation:

BOOL FlashWindowEx(
  PFLASHWINFO pfwi
);

As its name implies, this particular API is used to “flash” a window’s title and border. It can also be used to flash the window’s icon in the Windows System Tray, either separately or together. These can be flashed once or multiple times at a requested refresh rate. This function requires one argument parameter of type PFLASHWINFO, which the Windows online documentation describes as a pointer to a FLASHWINFO structure (Pointer to FLASHWINFO = PFLASHWINFO). We’ll examine the layout of this structure shortly. The function returns a BOOL (a C/C++ type definition equivalent of a PowerBuilder Boolean) value.

The Windows online documentation notes this API function resides in the Windows system dynamic link library “user32.dll”. This website is my "go to" source for finding the name of the DLL where the API function resides. The URL will be listed in the "Tips & Techniques" section in part four of the tutorial.

Note: In order to interface with any DLL entry point, the name of the DLL containing the entry point is required. At execution time, the PowerBuilder Virtual Machine (PBVM) needs to be able to locate the DLL. Determining the whereabouts of Windows system DLL’s is not an issue, because Windows “knows” where its DLL’s reside. However, you should keep in mind when utilizing in-house or third-party DLL’s that they need to be accessible to the PB application and the underlying PBVM. Here are the four locations that the Microsoft O/S Loader will search for the DLL named in an EFD:

  • The current (working) directory of the PB App EXE.
  • The Windows O/S Start-up directory.
  • All the Windows O/S Start-up subdirectories.
  • All directories listed is the Windows O/S System Path (environment variable).

Note: If the PB Application is started from a .BAT (DOS command) file, then the DOS session will start with a copy of the System Path. This path can then be augmented further using SET Commands within the .BAT file. That also includes “environment” variables.

Here is one possible PowerBuilder EFD for the FlashWindowEx API:

FUNCTION Boolean FlashWindowEx ( &
   REF s_flashwinfo pfwi &
   ) LIBRARY “user32.dll”

A brief comment about the style conventions you’ll see in this tutorial; I prefer to specify EFD keywords (such as “FUNCTION”, “REF” and “LIBRARY”) in uppercase and standard PB datatypes (such as “Boolean”) in mixed case, as I find it helps readability. Except for the name of the API function, use of upper/lower case is immaterial.

Important! The function name must always be specified exactly as the Windows documentation lists it. This also applies to the ALIAS FOR directive if used.

I also like to specify each argument parameter on its own continuation line and the LIBRARY and optional ALIAS FOR directives on its own continuation line, in order to prevent the EFD from getting too “strung out” on long lines in the PowerScript Editor.

The ALIAS FOR Directive

Here’s an example of an EFD for the same API function that uses the ALIAS FOR directive:

FUNCTION Boolean MakeTheWindowFlicker ( &
   REF s_flashwinfo pfwi &
   ) LIBRARY “user32.dll” ALIAS FOR “FlashWindowEx”

In this alternative example EFD, you can see that the name of the function as it will be referenced in PowerScript (“MakeTheWindowFlicker”) is an alias for the actual name of the WinAPI function (“FlashWindowEx”). The function name is critical. Windows may not be able to identify the DLL entry point (i.e., the function name) unless the name in the EFD exactly matches the documented name. When this happens, it can be a very difficult issue to troubleshoot, so I strongly recommend you always specify the API function name exactly as documented.

If the name of a DLL function contains non-standard characters (such as “@”, for example), you must use the ALIAS FOR directive so that the name of the external function used in PowerScript code contains only those characters acceptable to PowerBuilder:

  • Letters and numbers
  • “-” (dash/hyphen)
  • “_” (underscore)
  • “$” (dollar sign)
  • “#” (number sign)
  • “%” (percent sign)

Passing Arguments by Reference

The “REF” keyword preceding any argument parameter datatype in the EFD tells the PB compiler to pass this argument by reference instead of by value. When an argument is passed by reference, the calling method (the PowerBuilder application) can access any changes made to the argument by the called method (the DLL function). The PB compiler accomplishes this by passing the memory address (i.e. a pointer) as the argument in the call stack instead a copy of the actual argument parameter itself (this is referred to as “passing by value”).

In this particular example, it so happens that the FlashWindowEx API function does not alter the contents of the FLASHWINFO structure. Even so, the specification for this API function requires that the argument be “a pointer to a FLASHWINFO structure”. Since PowerBuilder does not provide a mechanism for obtaining memory addresses, the use of the “REF” keyword in this instance provides a way that we can satisfy the syntactical requirement of the DLL function call.

Passing structures to DLL functions by reference is not unusual, because this keeps the amount of data passed in the call stack small. Some WinAPI structures may contain several hundred bytes of data, so it is more efficient in most cases to pass a four-byte (in 32-bit processes) or eight-byte (in 64-bit processes) pointer to an argument parameter than it is to pass the entire parameter through the call stack.

When a WinAPI function specifies that a pointer to an argument value is to be passed without changing the argument value, the typedef’d Windows datatype will typically include a prefix code letter of “C” (for “constant”) to convey this information to the developer. For example, the WinAPI datatype “LPCWSTR” is used for an argument that is “a long (32-bit) pointer to a constant, wide (i.e., Unicode) string (“L”+”P”+”C”+”W”+”STR” = “LPCWSTR”).

Note the name given to the argument in any EFD (“pfwi” in the preceding examples) is immaterial, because the EFD is not executable. Its sole purpose is to inform the PB compiler how to interface with the external DLL entry point. I prefer to keep the same argument name that appears in the online WinAPI documentation so as to provide consistency with the documentation.

Functions vs. Subroutines

Not all API functions return a value. In C/C++, the forward declaration for a function that does not return a value syntactically states that the function returns VOID.

A function that does not return a value is frequently called a subroutine, so the nomenclature used in the EFD changes correspondingly. Here’s an example of an EFD for a WinAPI subroutine that copies a requested number of bytes from the start of a FLASHWINFO structure to an unbounded byte array:

SUBROUTINE Copy2ByteArray ( &
   REF Byte         Destination[], &
   REF s_flashwinfo Source, &
   Long             Length &
   ) LIBRARY “kernel32.dll” ALIAS FOR “RtlMoveMemory”

In this example, note that the PB Byte array (the destination) must be initialized by the PB application prior to invoking this WinAPI subroutine. PB always needs to “be in charge of” or manage the memory space it uses for its variables, structures and arrays, otherwise the memory may not be managed correctly. This applies to PB strings, too, because a string is but an array of characters. It also applies to Blobs, which are equivalent to an array of bytes. One of the coding tips later on in part four mentions this and will include an example how to code for this.

Tip: When you create an EFD, save the object containing the EFD before you write code that calls the external function. PB compiles all of the scripts in an object before changes to EFD’s are saved, so code that calls an external function described by a new EFD will not successfully compile until after the object is successfully saved. If you forget, and write the code that calls the external function before saving the EFD, you have to temporarily comment out the calling code, save, then un-comment.

Scope and Access

A PowerBuilder EFD can either have global or local scope. I discourage the use of global external functions in general because (1) in my experience few external functions need to be accessible globally, and (2) a global EFD actually resides in the application object instead of the object where they are called. A locally-scoped EFD can be specified in windows, menus, and user objects (visual and non-visual). Public, protected, or private accessibility to local external functions can be explicitly specified in the EFD, if needed, in the same manner that access level is managed for instance variables. Public access is assumed if not specified. Like global PB functions, global external functions can only have public access.

Where Are EFD’s Defined?

You define local EFD’s in a PowerScript Painter using the “Declare Instance Variables” pane by changing the view (the drop-down selection located at the top-middle area of the pane) from “Instance Variables” to “Local External Functions.”

Note: Immediately above the “Local External Functions” selection in this drop-down is the “Global External Functions” selection. Even though you can view, edit or create global EFD’s in any object where local EFD’s can be created, be aware the code for all global EFD’s actually resides in the Application object. If your application is under source control, you should first perform a source control check out on the Application object, then edit it and manage global EFD’s directly in the Application object.

Character and String Encoding

Since PB Version 10, external functions interact with string and character data using Unicode encoding (two bytes per character) by default. It is possible for PowerBuilder to interface with DLL functions that utilize ANSI encoding for character and string values via an optional keyword included in the ALIAS FOR directive:

... ALIAS FOR “externalname;ansi”

If a DLL does not seem to be able to accept character/string data with Unicode encoding or if “garbage” data is being returned for character/string values, you may be encountering a Unicode/ANSI encoding conflict. If so, try including “;ansi” in the ALIAS FOR directive.

Whenever a WinAPI function interacts with string or character data, Microsoft provides two similarly-named versions of an API function; one that utilizes Unicode encoding and the other for ANSI. Unicode-specific WinAPI function names end with “W” (for “wide” characters) and ANSI-specific function names end with “A”. For example, the name WinAPI functions to obtain the path of a system folder are:

SHGetFolderPathW (for Unicode)
SHGetFolderPathA (for ANSI)

This naming convention for different encoding versions also applies to WinAPI structures that contain string or character members, such as:

OPENFILENAMEW (for Unicode)
OPENFILENAMEA (for ANSI)

In PB 10 and later, you should use the Unicode version of WinAPI functions and structures unless there are unusual or special conditions warranting the use of the ANSI version.

Although WinAPI characters can utilize Unicode or ANSI encoding, characters in PB can only contain Unicode values. All ANSI character values passed to and from WinAPI calls are automatically converted by PowerBuilder to Unicode. PB variables (and structure members) of the Character datatype are converted to the C/C++ char type before passing.

From the Appeon PowerBuilder Application Techniques publication:

“PowerBuilder arrays of the Character datatype are converted to the equivalent C/C++ array of char variables. A PB array of the Character datatype embedded in a structure produces an embedded array in the C structure. This is different from an embedded String, which results in an embedded pointer to a string in the C structure.”

Unicode Comes in Several Flavors, but Windows & PB Utilize Only One

Unicode can be implemented by different character encodings. The Unicode standard defines UTF-8, UTF-16 and UTF-32, and several other encodings are in use (Source: Wikipedia). The Windows O/S uses UTF-16 encoding internally, as does PowerBuilder. Specifically, they use UTF-16LE (the “LE” stands for “Little Endian”) encoding.

However, even though PB uses a single Unicode encoding internally, it is able in a limited fashion to convert string data between the following four encodings via the Blob and String PowerScript functions. Note that as of the latest PB2019 release, PB does not support conversion to/from UTF-32, only:

  • ANSI
  • UTF-8
  • UTF-16LE (the default used by PowerBuilder)
  • UTF-16BE (the “BE” stands for “Big Endian”)

Interfacing with In-House and/or Third-Party DLL’s

The terms “calling convention” or “call format” are used interchangeably to identify the set of rules that define how data is placed onto and taken off of the execution call stack. If the code that calls a function and the function that is called do not use the same call format rules, argument values cannot be exchanged consistently and accurately, and therefore errors will occur.

The WinAPI uses the “__stdcall” calling convention, also known as the WINAPI format, because Visual Studio supports the WINAPI macro that codes the WINAPI/__stdcall calling convention for the target. The source code for all DLL entry points identifies the calling convention to tell the C/C++ compiler to create executable code that implements the desired calling convention rules. Because traditional PowerBuilder is a development platform for Windows applications, it uses the “__stdcall” calling convention exclusively.

In order for PB to be able to interface with DLL’s supplied by in-house developers and/or third-party vendors, the entry points in the DLL are required to be defined (the precise terminology is “exported”) using the “__stdcall” format. Some of the other calling conventions in existence are “__cdecl” (the default calling convention for the C programming language), “__clrcall”, “__thiscall”, “__fastcall” and “__vectorcall”. In older C/C++ code, you may see “_far _pascal” specified as the call format.

It’s a big world and not everyone follows the naming standards and calling convention used by Windows, so if a third-party DLL accepts or returns character or string data, you will need to determine from the vendor if that data is encoded using Unicode UTF-16LE or ANSI so that the appropriate EFD can be coded.

Not only must PowerBuilder and the Dynamic Link Libraries it interfaces with both use the same calling convention, they must agree on the rules for aligning structure members in memory based on member datatypes. Stay tuned to part two of this tutorial for details.

Summary

This portion of the tutorial focused on external function declarations (EFD’s) in PowerBuilder and how they convey to the compiler the information needed to interface with a DLL entry point such as a WinAPI function or a function in an in-house or third-party’s DLL. In part two, we’ll examine the calling conventions in 64-bit Windows processes and important differences between the 32-bit and 64-bit environments and in particular, as it pertains to structures.

Comments (1)

  1. Gary Ault

Very informative.

  Attachments
Your account does not have privileges to view attachments in the comment
 
There are no comments posted here yet