Prototyping API Calls for PowerBuilder 4.0, 5.0, and 6.0
he tens of thousands of function calls in the Windows environment can be helpful to PowerBuilder users, but documenting them is nearly impossible. After hundreds of user inquiries, Powersoft Technical Support compiled these technical tips as FaxLine document 44648 to enable PowerBuilder developers to translate standard Microsoft function calls into PowerBuilder syntax, and to empower developers to use any of the external API calls within their PowerBuilder environments.
In this issue of Powerline, we've dedicated the entire space of our "Techical News" section to provide you with this important information. The FaxLine document has been annotated here so that each step in the process will be clear for PowerBuilder developers with varying degrees of experience.
The following information will help you translate any Windows SDK call to a PowerBuilder API function call. It doesn't matter whether you're using PowerBuilder 4.0, 5.0, or 6.0 but there are important differences between the 16- and 32-bit versions, as we'll discuss below.
Step 1: Converting an SDK Call to a PowerBuilder API Call.
First you need to get the syntax that will be converted. This can be obtained from either a Windows API Bible or the MSDN (Microsoft Developers Network).
Step 2: Determining Whether it is a Function or a Subroutine.
Function calls return a value; subroutines do not.
Here is an example of a Microsoft function:
BOOL GetFileVersionInfo( LPTSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData );Here is an example of a Microsoft subroutine:
VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer)Step 3: Converting the Data Types from Microsoft to PowerBuilder.Most of the data types are listed above, but some may be missing. When in doubt, it's usually safe to assume that 16-bit data types are "unit" and 32-bit are "ulong", since they are the most common.
*If the word "Callback" appears as a data type, it cannot be performed within PowerBuilder. Callback routines are functions that are called from within functions.
Step 4: Declaring a Local or Global External Function.
This is a Microsoft function:
BOOL GetFileVersionInfo( LPTSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData );The BOOL represents a boolean return code, which is translated to "Boolean" in PowerBuilder for both 16 and 32 bit. A LPTSTR means a pointer to a string (see Step 3). In PowerBuilder, simply use the word "ref" before declaring the string for either 16- or 32-bit platforms. DWORD is translated to "uint" for 16 bit and "ulong" for 32 bit. An LPVOID indicates that a structure is being used. In PowerBuilder, create a structure, assign it to an instance variable of that structure, and pass it by reference. As a result, the following function declarations can be derived:PowerBuilder 16 bit:
FUNCTION boolean GetFileVersionInfo(ref string filename, uint f_handle, uint f_length, ref lpdata lpdata2) LIBRARY "ver.dll"PowerBuilder 32 bit:FUNCTION boolean GetFileVersionInfoA(ref string filename, ulong f_handle, ulong f_length, ref lpdata lpdata2) LIBRARY "version.dll"Note: In the "gotchas" section below, you'll get a further explanation why an "A" is appended to the 32-bit function. You'll also find a handy technique to help you locate function calls within the DLLs.Step 5: Creating a Structure.
A structure, a container for one or more elements, is the mechanism used by the operating system to pass required information back from a function. The MSDN provides all information on this structure since it is a Windows function.
In the MSDN, the structure appears like this:
LPDATA = { DWORD dwSignature ; DWORD dwStrucVersion ; DWORD dwFileVersionMS ; DWORD dwFileVersionLS ; DWORD dwProductVersionMS ; DWORD dwProductVersionLS ; DWORD dwFileFlagsMask ; DWORD dwFileFlags ; DWORD dwFileOS ; DWORD dwFileType ; DWORD dwFileSubtype ; DWORD dwFileDateMS ; DWORD dwFileDateLS }In PowerBuilder, go into the Structure Painter, as shown in the above example, to convert all the elements to PowerBuilder data types. By looking at Step 3, DWORD translates to "ulong".Step 6: Scripting the Function Call.
Now that you have the function declaration, you'll need to assign it the proper arguments. The function GetFileVersionInfoA listed in Step 4 requires the script below.
First you'll need to declare the data types. Keep in mind that the variable names do not have to match the function declaration listed in Step 4.
boolean lb_rtn // Return code string ls_filename // 1st argument - filename ulong lu_hand // 2nd argument - f_handle ulong lu_len // 3rd argument - f_length lpdata lpdata2 // Last argument - assigning an instance of a structure.Step 7: Assigning Values to the Arguments.This is the hardest part; it may require use of the MSDN, API Bible, or available references that cover the function you are calling. In this particular case, the information is contained within the MSDN.
The first argument, ls_filename, should be set to the path and filename of the target file.
ls_filename = "c:\windows\calc.exe" // The calculator would be a good file to test against.The second argument, lu_hand according to the MSDN, is ignored. This is probably reserved for a future version of Windows to use. To be safe, set the argument to null.setnull(lu_hand)The third argument lu_len, contains the size of the buffer into which the information will be returned. It is critical that the buffer size not be too small because the information could overflow into another part of memory, causing a GPF or Dr. Watson error. In this particular case, since the structure contains 13 elements that are all "ulong", the number 256 should be sufficient to contain the information.lu_len = 256The last argument, lpdata2, is an instance of the structure lpdata and will be populated by the function call.The final script will appear as follows:
boolean lb_rtn long ls_filename ulong lu_hand, lu_len lpdata lpdata2 ls_filename = "c:\windows\calc.exe" // The calculator would be a good file to test against. setnull(lu_hand) lu_len = 256 lb_rtn = GetFileVersionInfoA(ls_filename, lu_hand, lu_len, lpdata2)Step 8: Viewing the Output.sle_1.text = string(lpdata2.dwSignature) sle_2.text = string(lpdata2.dwStrucVersion) sle_3.text = string(lpdata2.dwFileVersionMS) sle_4.text = string(lpdata2.dwFileVersionLS) sle_5.text = string(lpdata2.dwProductVersionMS) sle_6.text = string(lpdata2.dwProductVersionLS) sle_7.text = string(lpdata2.dwFileFlagsMask) sle_8.text = string(lpdata2.dwFileFlags) sle_9.text = string(lpdata2.dwFileOS) sle_10.text = string(lpdata2.dwFileType) sle_11.text = string(lpdata2.dwFileSubtype) sle_12.text = string(lpdata2.dwFileDateMS) sle_13.text = string(lpdata2.dwFileDateLS) Messagebox("Return Code", string(lb_rtn))Additional Information:Gotchas to Look Out For.
Make sure the DLL you are referencing is the right bit level. To ensure the referenced DLL is at the proper bit level, view the DLLs' properties. Most likely, however, you'll need a third-party product to determine this. I'd recommend Function Check from Karri Software LTD (http://ourworld.compuserve.com/homepages/KarriSL/) or QuickView, an accessory of Win95 PLUS! from Microsoft. Remember, a 16-bit application cannot make a 32-bit API call and vice versa.
Error Messages and What They Mean.Some functions are cap sensitive depending on the DLL. For example, Findwindowa fails, whereas FindWindowA works.
All handles in PowerBuilder 16 bit are "uint" and 32 bit are "ulong". Using the data types "int" or "long" may work, but if the handle points to an area in high memory, the latter two data types may not be able to support such a large number. This is more likely to happen on NT 4.0.
Make sure you have the correct function name. Under 32 bit, many of the functions had an "A" appended to the function name to make it unique from its 16-bit counterpart. This allows developers to place both 16-bit and 32-bit function calls in the same application and then make the correct function call based on what bit platform the program is being run from. Again, you'll need to use a third-party product to verify this. The MSDN does not always list the proper names for the functions. For example, GetFileVersionInfo is listed as a 32-bit function, but it is in fact GetFileVersionInfoA.
Global versus Local External Function declaration: If the function is declared globally, it can be called from anywhere in your application. If you declare the function as a Local External Function, it can only be called from that window where it's declared. Local Function uses less resources than globals, but the difference is minimal.
A Handy Trick for Finding Functions Quickly.
- Error: Error opening DLL library <filename> for external function at line <line_number> in the <event name> event of object <object_name> of <window_name>.
This error message occurs if you try calling a DLL that is not the same bit level as your application. This could also mean that the DLL is corrupted, in an incompatible format, or the DLL can't be found. As a result, PowerBuilder is trying to load the DLL into memory but is unable to. In most cases, this error message is not a PowerBuilder problem.
- Error: Error calling external function <function_name> at line <line_number> in the <event name> event of object <object_name> of <window_name>.
This is probably the result of an incorrectly spelled function name. Be sure to verify that the function name matches what is in the DLL exactly, including the case of the letters.
Error: Specified argument type differs from required argument type at runtime in DLL function <function_name>. (Invalid stack pointer on return from function call) at line <line_number> in <event_name> event of object <object_name> of <window_name>. This error usually indicates that the data types do not match what the DLL function is expecting.
PB050: Caused an Invalid Page Fault in module PBSHR050.DLL @ 0137:1111163e
This error can occur either immediately upon calling the function or when the application closes. The module and memory address may vary, but the reason for this is usually the same. If PowerBuilder is receiving a string and memory isn't allocated in advance using the SPACE( ), that string will overflow into another memory area. To allocate 12 characters to the string ls_filename, the following code would be used:
ls_filename = space(13) // You may want to give it an extra space just to be safe.
- Receiving garbage from the DLL. E.g., last name populated as: ^&Ryan
This problem is most likely the result of the byte alignment being set incorrectly. PowerBuilder expects the byte alignment to be set to one, and if you are using MSVC++ compiler, the default setting is four. To set the byte alignment to one, you'll need to do the following prior to compiling to a DLL:
Note: This is only relevant when working with structures.
- Select the desired target.
- Right mouse click and select Settings.
- Select the C/C++ tabpage.
- Select Code Generation from the Category dropdown list.
- Select desired byte alignment from the Struct Member Alignment dropdown list.
Related FaxLine Documents.
- On Win95 or NT 4.0, click on the START button and select "Find", then "Files or Folders".
- In the SLE entitled "Named" enter c:\*.dll. If this is a Windows DLL that you'll be calling, enter c:\windows\*.dll.
- Click on the "Advanced" tab and in the "Containing Text" SLE, enter the exact function name you are looking for. For example: FindWindowA
- There will usually be a lot of DLLs that contain the function you are looking for but try to use the main Windows DLLs whenever possible since they are already loaded into memory.
44596 - 16-Bit API Calls for PowerBuilder 4.0, 5.0, and 6.0
44545 - 32-Bit API Calls for PowerBuilder 4.0, 5.0, and 6.0
44588 - Dynamically Closing a Non-PowerBuilder Application
44474 - External Function Calls (PB 4.0, 5.0)
44538 - Passing a 32-bit PowerBuilder structure to a 32-bit C DLL Created in Power++PowerBuilder API Call Web site: http://www.ultranet.com/~dsalomon/api