Print
Category: PowerBuilder
Hits: 761

User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

This tutorial has discussed 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. In the first three parts, the groundwork was laid to make you more knowledgeable and comfortable coding External Function Declarations (EFD's) and understanding the nuances of how information is exchanged between PB and Windows API functions. Part four contains a list of coding tips and techniques and mapping tables to help you with the translation between many common WinAPI datatypes and the standard PB datatypes. It also describes a free PB sample application and non-visual user object that can dynamically determine the memory size and layout of a structure in either 32-bit or 64-bit environments.

Structure Size and Layout Sample Application & Non-Visual User Object

PowerBuilder does not currently have a “SizeOf” function for returning the amount of memory required by a standard datatype or a structure. The ability to dynamically determine the size of a structure would be very helpful in setting the “size” member required in some WinAPI structures, particularly if it could take into account structure member boundary-alignment padding and other conventions for both the 32-bit and 64-bit environments. Review part two of the tutorial for a refresher on structure member boundary-alignment padding.

Roland Smith provides a free PB code sample (named “SizeOf”) on his TopWizProgramming.com website for calculating the size of a structure. The calculation logic is contained within a single non-visual user object that was originally authored many years ago by Rui Cruz. Roland enhanced the original object to handle the datatypes added to PowerBuilder in version 12.6. With Roland’s permission, I’ve made several additional enhancements to the object:

With this newly-enhanced object it is now possible to determine the size of PowerBuilder structures at execution time, even if PB creates a temporary, behind-the-scenes version of the structure to satisfy the progma_pack( 1 ) directive specified in an EFD.

There are, however, a few caveats:

With Roland’s permission, I have posted in the CodeXchange section of the Appeon Community this enhanced version of his SizeOf utility and the accompanying Structure Layout sample application.

https://community.appeon.com/index.php/codeexchange/powerbuilder/260-enhanced-sizeof-utility-with-sample-application-and-winapi-tutorial-demo-tests#280

The sample app demonstrates how the structure size calculator works by determining the size of approximately thirty structures (primarily WinAPI structures) for both the 32-bit and 64-bit environments. Some of these WinAPI structures are relatively large and some are small. Some grow in size in a 64-bit environment while others do not. Here’s a screenshot of the application’s main window:

Demo Application - Main Window

The Structure Layout sample application was developed in PB 2017 R2 and can be compiled into either a 32-bit or 64-bit executable. It has been successfully tested in PB 2019 R2.

The Structure Layout Report

A structure layout report can be displayed for any of the listed structures by clicking on the “Layout” button in either the 32-bit or 64-bit columns. Here’s a snapshot of the structure layout report for the 64-bit version of the FLASHWINFO structure:

Demo Application - Report Window

Tutorial-Related Test Suites

The sample application can perform four test suites related to topics covered in this tutorial:

  1. Demo Flash Window API

    Calls the FlashWindowEx Windows API function using coding techniques described in the tutorial that work properly in both the 32-bit and 64-bit environments without relying on bitness-specific coding or hard-coded structure size values.
     
  2. Test Progma_Pack

    Calls the FlashWindowEx Windows API to illustrate the impact of specifying the progma_pack( 8 ) or progma_pack( 1 ) directive in an EFD and what happens when the progma_pack directive is omitted from an EFD. The progma_pack( 1 ) test fails (gracefully, thanks to a Try-Catch block) in a 64-bit environment, as expected, because Windows expects to find structure member boundary alignment padding and structure size padding to exist but it gets omitted when progma_pack( 1 ) is specified.
     
  3. Test Any’s in Memory

    Shows how variables of type Any are actually represented in memory, how a structure member of type Any is represented in memory and what can happen when a variable of type Any is passed as an argument parameter to an external function.
     
  4. Test Nested Structures

    Populates two-element arrays of a nested hierarchy of small, medium and large-sized structures that contain values that be recognized when memory is examined. The test takes a memory snapshot, then extracts the structure member data values from the snapshot byte-by-byte to illustrate that the boundary alignment and padding rules for structure members and structures described in this tutorial are correct.
     

Coding Tips and Techniques For 32-Bit and 64-Bit Applications

The following compendium of tips and techniques (many of which have been discussed in the tutorial) can help you interface 32-bit and 64-bit Windows-hosted PowerBuilder applications with Windows dynamic link libraries:

  1. Use the “REF” keyword in EFD’s whenever the WinAPI function requires a pointer to a value or structure, even if the called API function will not make any change to the argument parameter.
     
  2. Because a string is an array (of characters), strings are automatically passed as a pointer, so the “REF” keyword is not required with a parameter of type String unless the called API function will be changing the value of the string and the calling PB application needs access to the changed value.
     
  3. Whenever the definition of an argument parameter in a Windows API function includes "*" (as in "long * pLength", for example), the API is specifying that a pointer is to be passed, so the "REF" keyword will be needed in the EFD... except for strings (see #2 above).
     
  4. It is not uncommon for a C/C++ type definition to equate to a pointer. For example, "LPDWORD" is a pointer to a DWORD (a 32-bit unsigned integer). Therefore, you should include the "REF" keyword in the EFD to ensure a pointer is passed as the argument value.
     
  5. Strings that are going to be given a value by the called API function must first be initialized to its maximum possible length before the API function is invoked, Typically, this is accomplished via the Space PowerScript function.
     
    Tip: For extra memory overflow safety, always use a “+1” in the Space PowerScript function:
     
      ls_FilePath = Space(li_MaxPathLength+1)
     
    Example:
            The following code invokes the WinAPI GetComputerNameW function to obtain the (Unicode)
            name of the computer that is executing the application.
     
    The Windows API definition:
     
       BOOL GetComputerNameW(
          LPWSTR lpBuffer,
          LPDWORD nSize
       );
     
    The PB EFD:
       FUNCTION Boolean GetComputerName ( &
          REF String       lpBuffer, &
          REF UnsignedLong nSize &
          ) LIBRARY "kernel32.dll" ALIAS FOR "GetComputerNameW"
     
    Example PB Code:
       Constant UnsignedLong MAX_COMPUTERNAME_LENGTH = 31

       Boolean      lb_rc
       UnsignedLong lul_size = MAX_COMPUTERNAME_LENGTH
       String       ls_name
      
       ls_name = Space(lul_size+1) // Pre-allocate string (w/1 extra char)
       lul_size++                  // Add 1 char for the terminating null
      
       lb_rc = GetComputerName(ls_name,lul_size)
      
       if not lb_rc then ls_name = ''
       Return ls_name
     
  6. The names given to WinAPI functions involving string and/or character information end in either “W” (for “Wide character”, i.e., Unicode) or “A” (for “ANSI”). The EFD for any API function that works with ANSI-encoded data must include the ALIAS FOR directive with “;ansi” appended to the name of the actual API function because PowerBuilder v10 and upward assume Unicode by default.
     
  7. It is good practice to use the ALIAS FOR directive in an EFD to normally hide the distinction between Unicode and ANSI versions of an API function, as shown below and in Tip number 5 above:

      FUNCTION Boolean GetOpenFileName ( &
          REF s_openfilenamew Arg1 &
          ) LIBRARY “comdlg32.dll” ALIAS FOR “GetOpenFileNameW”

    I suggest if you do this, do it only for the Unicode version of API functions because use of Unicode is the norm within PB – I prefer to leave the trailing “A” in the EFD for any ANSI functions to clearly delineate any cases where ANSI encoding is required, as illustrated in the example below:

       // Note: ANSI-encoding version
       // (The ALIAS FOR directive is needed only to specify the ";ansi" keyword)

       FUNCTION Boolean GetOpenFileNameA ( &
          REF s_openfilenamea Arg1 &
          ) LIBRARY “comdlg32.dll” ALIAS FOR "GetOpenFileNameA;ansi"
     
  8. Specify the name of the actual WinAPI function using the same capitalization as in the online Microsoft WinAPI documentation to avoid run-time errors that are difficult to diagnose.
     
  9. The runtime bitness of a PB application can be obtained by checking the ProcessBitness property of the PB Environment object. This integer value will be either 32 or 64. Use the PowerScript GetEnvironment function to obtain an instance of the PB Environment object.
     
  10. If you need to use the progma_pack( 1 ) directive in an EFD, include a comment that explains your intent or requirement.
     
  11. Named constants in WinAPI functions typically defined in a #define declaration in a C/C++ header (.h) file. For example, the sample code that invokes the FlashWindowEx API function sets a structure member to FLASHW_ALL, which has the value 3.

    Where can you look up the value of named constants in the WinAPI?

    Sometimes the normal online Windows API documentation will list these values and explain what each supported value means. Unfortunately, the documentation doesn’t always include this information. When this happens, look in the C/C++ “header” files in the Windows System Development Kit (SDK). Header files have a file extension of “.h”, such as “winbase.h”. Keep in mind there are hundreds of header files in the WinAPI, so it can be a challenge to find the definition in source code.

    Even if you do not write code in Visual Studio, I suggest you install it (Community editions can be downloaded from Microsoft and installed for free) so you can locate and search the contents of the folder where the source header files reside. For example, in Visual Studio 2017 Community, I find a total of 1,508 header files in:

           C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um

    You’ll also likely need use of a tool that can search the contents of files. I use an excellent, free utility named Notepad++. A technique I’ve found that works well is to use Notepad++ to search for the #define statement containing the named constant I'm looking for. For example, to find the FLASHW_ALL constant, I search for (without the double-quotes, of course):

            “#define FLASHW_ALL “      <- Note the trailing blank
                            -or-
            “ FLASHW_ALL “                 <- Note the leading AND trailing blanks

    There are nearly always multiple, related constants having names that begin the same. In this example, the FLASHW_ALL constant is defined as FLASHW_CAPTION | FLASHW_TRAY (two other constants combined via the C/C++ bitwise OR operator), so I will often search for all of the related constants, as in (without the double-quotes):

            “#define FLASHW_”            <- Note NO trailing blank
                            -or-
            “ FLASHW_”                       <- Leading blank only

    In this case, we see that FLASHW_CAPTION = 1 (0x00000001) and FLASHW_TRAY = 2 (0x00000002), so after a bitwise OR operation, FLASHW_ALL = 3 (0x00000003).

    I prefer to include these constants in PowerBuilder code as constant instance variables (hmmm... isn’t a “constant variable” an oxymoron?) using the same names as defined in the Windows header files. For example, in PowerBuilder:

       Constant UnsignedLong FLASHW_CAPTION = 1  // 0x00000001
       Constant UnsignedLong FLASHW_TRAY    = 2  // 0x00000002
       Constant UnsignedLong FLASHW_ALL     = FLASHW_CAPTION + FLASHW_TRAY // 0x00000003
     
  12. Don’t use PB Any’s when interfacing to the WinAPI or third-party DLL’s. Just don’t. Even if you believe you have a really good reason to do so. Review part three of the tutorial to remember why. Use only the standard PB datatypes that Windows recognizes (refer to the first table in the next section).
     
  13. Unbounded arrays are OK to use if the API function expects it. Typically, the WinAPI function definition or a structure passed to the WinAPI function will include a parameter/member stating how many elements are currently contained in the unbounded array so that Windows can determine what to look for.
     
  14. Avoid empty unbounded arrays. Windows generally doesn’t know what to do with them. When unbounded arrays are used in an external function call, they are not actually empty! When I have encountered an unbounded array in a WinAPI function (refer to the SendInput WinAPI function, for example), you usually have to also supply a parameter that contains the number of elements contained in the unbounded array.
     
  15. Do not attempt to pass a null PB variable or structure member to an external DLL function. Chaos (or errors, at the very least) will likely ensue. Instead, assign a value of the expected datatype where all bits are zero, as this is typically how Windows defines NULL.
     
  16. When interfacing to an in-house or third-party DLL that accepts and/or returns character data (including strings) determine if the DLL expects characters to be encoded in Unicode or in ANSI, and code the EFD accordingly.
     
  17. When interfacing to an in-house or third-party DLL, verify that the DLL functions have been exported using the __stdcall calling convention. PowerBuilder will not be able to successfully interface with the DLL unless the DLL has been created using this calling convention.
     

Resources

I have a top-level bookmark menu in my browser named “Win API”. Here are the URL’s I’ve found to be most helpful:

Appeon’s PowerBuilder documentation (which you can access online) contains helpful information about how to define and use external functions. Here are two documents and the drill-down to the pertinent information:

https://docs.appeon.com/appeon_online_help/pb2019r2/application_techniques/ch24s01.html
 

https://docs.appeon.com/appeon_online_help/pb2019r2/powerscript_reference/ch03s04.html
 

The Memory Size of Standard PB Datatypes

What is the memory size of a Boolean in PB? A Double? It would be nice if PB provided a SizeOf operator similar to C/C++, particularly for use with structures.

Over on the Windows side of the aisle, what is the memory size of a WORD in the WinAPI? An LPARAM (Long message parameter)?

Type definitions have run amuck in the WinAPI. A primary reason for using type definitions is to convey context and categorization to data values (to which I applaud) and if I coded in the WinAPI extensively I likely would not have trouble remembering the underlying datatype of a DWORD, for example. But I do have trouble remembering, and you might struggle with this, also, so the tables in this section and in the next section can help.

The following table lists the memory size (in bytes) of the standard PB datatypes. Any PB datatype that does not have a standard WinAPI datatype equivalent is noted.

Standard PB Datatype

Size (Bytes)

Equivalent WinAPI Datatype

Note

Any (as a variable)

8 or 12

n/a

An internal PB structure. Its size depends on the target compilation bitness. When used in a structure, an Any assumes the size and characteristics of the argument parameter it represents. No WinAPI equivalent.

Blob

4 or 8

BYTE array

An array of type BYTE (i.e., a pointer), so the size depends on the target compilation bitness.

Boolean

2

BOOL

BOOL is a 4-byte integer in WinAPI, but a PB Boolean is 2 bytes. PB automatically promotes/demotes when passing as an argument.

Byte

1

BYTE

 

Char or Character

2

WCHAR

Unicode character. "W" stands for "wide", i.e., Unicode.

Date

4 or 8

-none-

A pointer to an internal PB structure, so the size depends on the target compilation bitness. No WinAPI equivalent.

Datetime

4 or 8

-none-

A pointer to an internal PB structure, so the size depends on the target compilation bitness. No WinAPI equivalent.

Decimal

8

-none-

No WinAPI equivalent.

Double

8

double (C/C++)

Internal bit layout may differ. No WinAPI equivalent.

Integer / Int

2

SHORT

 

Long

4

INT or LONG

 

LongLong

8

LONGLONG

 

Longptr

4 or 8

LONG_PTR

Signed 32-bit or 64-bit integer. Its size depends on the target compilation bitness.

Real

4

FLOAT

Internal bit layout may differ. Rarely used in Windows.

String

4 or 8

LPWSTR

An array of Unicode characters (i.e., a pointer), so the size depends on the target compilation bitness.

Time

4 or 8

-none-

A pointer to an internal PB structure, so the size depends on the target compilation bitness. No WinAPI equivalent.

UnsignedInteger or UnsignedInt or UInt

2

USHORT or WORD

 

UnsignedLong or ULong

4

DWORD

 

 Common Windows API to PowerBuilder Datatype Conversions

The online documentation that describes the various type definition datatypes used in the WinAPI is the authoritative source for this information. Even though I listed the URL above, for convenience I included it here:

https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types

The following table lists the PowerBuilder (version 12.6 and later) equivalent for the some of the most common WinAPI datatypes:

Windows API Datatype

Equivalent PB Datatype

Description

BOOL

Boolean

Boolean (True/False) value. A 4-byte integer in Windows. A PB Boolean is 2 bytes.

BOOLEAN

Byte

Boolean (True/False) value. An 8-bit, unsigned integer. **NOTE** - Not the same datatype as BOOL, but also used for True/False values.

BYTE

Byte

An 8-bit, unsigned integer.

BYTE Array (block of memory)

Blob

A block of memory is typically referenced by a pointer in Windows.

CONST **

Constant **

C/C++ directive that denotes a variable whose value is to remain constant during execution.
(** = NOT a datatype)

double (C/C++)

Double

Basic C/C++ language datatype – Not a WinAPI-defined type definition.

DWORD

UnsignedLong (ULong)

A 32-bit unsigned integer.

FLOAT

Real

Internal bit-pattern may differ. Rarely used in Windows.

HANDLE

Longptr

A generic handle to an undetermined object.

HBITMAP

Longptr

A handle to a bitmap.

HBRUSH

Longptr

A handle to a brush.

HDC

Longptr

A handle to a device context.

HFILE

Longptr

A handle to a file.

HFONT

Longptr

A handle to a font.

HICON

Longptr

A handle to an icon.

HINSTANCE

Longptr

A handle to an instance (same as HMODULE today).

HMENU

Longptr

A handle to a menu.

HMODULE

Longptr

A handle to a module (same as HINSTANCE today).

HMONITOR

Longptr

A handle to a display monitor.

HRESULT

Long

A return code used by COM interfaces (this is not a Windows handle, despite how it is named).

HWND

Longptr

A handle to a "window". Keep in mind all visual controls in WIndows are actually implemented as sub-classed windows.

INT

Long

A signed 32-bit integer.

INT16

Integer (Int)

A signed 16-bit integer.

INT32

Long

A signed 32-bit integer.

INT64

LongLong

A signed 64-bit integer.

INT8

-none-

A signed 8-bit integer. Note: The PB Byte data type is an unsigned 8-bit integer.

INT_PTR

Longptr

A signed integer (32-bit or 64-bit, depending on process bitness). Used for pointer arithmetic.

LANGID

Integer (Int)

A language identifier.

LONG

Long

A signed 32-bit integer.

LONGLONG

LongLong

A signed 64-bit integer.

LONG_PTR

Longptr

A signed integer (32-bit or 64-bit, depending on process bitness). Used in Windows for pointer arithmetic.

LONG32

Long

A signed 32-bit integer.

LONG64

LongLong

A signed 64-bit integer.

LPARAM

Longptr

A Windows event message parameter.

LPCSTR

Longptr

A pointer to a constant, null-terminated string of ANSI characters.

LPCVOID

Longptr

A pointer to a constant of undetermined type.

LPCWSTR

Longptr

A pointer to a constant, null-terminated string of Unicode (“Wide”) characters.

LPVOID

Longptr

A pointer to a value of undetermined type.

LPDWORD

Longptr

A pointer to a DWORD (32-bit unsigned integer)

LPWORD

Longptr

A pointer to a WORD (16-bit unsigned integer).

LPWSTR

Longptr

A pointer to a null-terminated string of Unicode (“Wide”) characters.

LRESULT

Longptr

A signed result of message processing.

PVOID

Longptr

A pointer to a value of undetermined type.

QWORD

-none-

An unsigned 64-bit integer. Note: The PB LongLong data type is a 64-bit signed integer.

SHORT

Integer (Int)

A signed 16-bit integer.

UINT

UnsignedLong (ULong)

An unsigned 32-bit integer.

ULONG

UnsignedLong (ULong)

An unsigned 32-bit integer.

ULONGLONG

-none-

An unsigned 64-bit integer. Note: The PB LongLong data type is a 64-bit signed integer.

USHORT

UnsignedInt (UInt)

An unsigned 16-bit integer.

VOID

Any

A value of undetermined type. Means “returns nothing” when describing a function’s return value.

WCHAR

Character (Char)

A Unicode (“Wide”) character.

WORD

Integer (Int)

A 16-bit unsigned integer.

WPARAM

UnsignedLong (ULong)

A Windows event message parameter.

Tutorial Summary

We've learned how to code External Function Declarations (EFD’s) in PowerBuilder, as these are necessary in order to call exported entry points in “external” (to PowerBuilder) dynamic link libraries such as the WinAPI functions and third-party DLL’s. The means for invoking external functions using either Unicode or ANSI character/string encoding was described. ANSI encoding was included because DLL’s from third-party vendors may not support Unicode. We saw that passing argument values by reference to WinAPI functions is common practice, particularly when an argument is a structure, because a passing a pointer (memory address) can be much more efficient than passing the structure data (by value) through the call stack.

In part two the topic of boundary alignment was introduced; the reason it exists and the 8-byte alignment convention used by Windows and by PowerBuilder. Fundamental differences between Windows’ 32-bit and 64-bit environments and how the Longptr datatype in PB helps to mitigate those differences were covered. The effect of boundary alignment and 32/64-bit differences on structure layout and size were examined because some WinAPI functions require the caller to accurately specify structure length. Single-byte (or 1-byte) alignment was described as an alternative that some non-Windows DLL’s utilize. The optional progma_pack( 1 ) directive in an EFD allows PB to interface with DLL’s that uses 1-byte structure packing alignment. WinAPI functions all use progma_pack( 8 ), or eight-byte boundary alignment, which is what PB uses by default.

The third installment of the tutorial focused on several nuances of the use of structures in the interface between PB and Windows and in particular, how PB null variables and the PB Any datatype can adversely affect this interface. Although the Any datatype can be used in WinAPI function calls in a limited manner and with considerable care, we examined the potential dangers of using Any’s and described the circumstances where using Any’s simply does not work.

Finally, several coding tips and techniques were listed and mapping tables for translating between many of the common WinAPI datatypes and standard PowerBuilder datatypes were presented.

Acknowledgements

I’m very grateful to Armeen Mazda and everyone at Appeon for rescuing and resuscitating a languishing yet unparalleled product named PowerBuilder. You are the reason the future for RAD looks bright!

Roland Smith does many great things for the PowerBuilder community. He graciously gave me permission to post the source code for the free sample application that accompanies this tutorial, since it is based on his “SizeOf” code sample he makes available to all on his TopWizProgramming.com web site. Many thanks, Roland!

I’m especially indebted to Chris Pollach, who used some of his valuable time and energy to review this tutorial and improve it with excellent corrections and suggestions. Thank you, Chris!

 

 

We use cookies which are necessary for the proper functioning of our websites. We also use cookies to analyze our traffic, improve your experience and provide social media features. If you continue to use this site, you consent to our use of cookies.