Using External Functions and Other Processing Extensions

About this chapter

This chapter describes how to use external functions and other processing extensions in PowerBuilder.

Using external functions

External functions are functions that are written in languages other than PowerScript and stored in dynamic libraries. External functions are stored in dynamic link libraries (DLLs).

You can use external functions written in any language that supports the standard calling sequence for 32-bit platforms.

If you are calling functions in libraries that you have written yourself, remember that you need to export the functions. Depending on your compiler, you can do this in the function prototype or in a linker definition (DEF) file.

Use _stdcall convention

C and C++ compilers typically support several calling conventions, including _cdecl (the default calling convention for C programs), _stdcall (the standard convention for Windows API calls), _fastcall, and thiscall. PowerBuilder, like many other Windows development tools, requires external functions to be exported using the WINAPI (_stdcall) format. Attempting to use a different calling convention can cause an application crash.

When you create your own C or C++ DLLs containing functions to be used in PowerBuilder, make sure that they use the standard convention for Windows API calls. For example, if you are using a DEF file to export function definitions, you can declare the function like this:

LONG WINAPI myFunc()
{
...
};

Using PBNI

You can also call external functions in PowerBuilder extensions. PowerBuilder extensions are built using the PowerBuilder Native Interface (PBNI). For more information about building PowerBuilder extensions, see the PowerBuilder Native Interface Programmers Guide and Reference. For more information about using PowerBuilder extensions, see PowerBuilder Extension Reference.

Declaring external functions

Before you can use an external function in a script, you must declare it.

Two types

You can declare two types of external functions:

  • Global external functions, which are available anywhere in the application

  • Local external functions, which are defined for a particular type of window, menu, or user object

    These functions are part of the object's definition and can always be used in scripts for the object itself. You can also choose to make these functions accessible to other scripts as well.

Datatypes for external function arguments

When you declare an external function, the datatypes of the arguments must correspond with the datatypes as declared in the function's source definition.

For a comparison of datatypes in external functions and datatypes in PowerBuilder, see the section called “Declaring external functions” in PowerScript Reference.

To declare an external function:

  1. If you are declaring a local external function, open the object for which you want to declare it.

  2. In the Script view, select Declare in the first drop-down list and either Global External Functions or Local External Functions from the second list.

  3. Enter the function declaration in the Script view.

    For the syntax to use, see the section called “Declaring external functions” in PowerScript Reference or the examples below.

  4. Save the object.

    PowerBuilder compiles the declaration. If there are syntax errors, an error window opens, and you must correct the errors before PowerBuilder can save the declaration.

Modifying existing functions

You can also modify existing external function declarations in the Script view.

Sample declarations

Suppose you have created a C dynamic library, SIMPLE.DLL, that contains a function called SimpleFunc that accepts two parameters: a character string and a structure. The following statement declares the function in PowerBuilder, passing the arguments by reference:

FUNCTION int SimpleFunc(REF string lastname, &
   REF my_str pbstr) LIBRARY "simple.dll"

By default, PowerBuilder handles string arguments and return values as if they have Unicode encoding. If SimpleFunc passes ANSI strings as arguments, you must use this syntax to declare it:

FUNCTION int SimpleFunc(REF string lastname, &
   REF my_str pbstr) LIBRARY "simple.dll" &
   ALIAS FOR "SimpleFunc;ansi"

Declaring Windows API functions

The Windows API includes over a thousand functions that you can call from PowerBuilder. The following examples show sample declarations for functions in the 32-bit Windows API libraries KERNEL32.DLL, GDI32.DLL, and USER32.DLL.

Windows API calls

Some 32-bit function names end with A (for ANSI) or W (for wide). Use wide function names in PowerBuilder.

For a complete list of Windows API functions, see the Microsoft Windows SDK documentation.

The following statements declare a function that gets the handle of any window that is called by name, and a function that releases the open object handle:

FUNCTION longptr FindWindowW(ulong classname, &
      string windowname) LIBRARY "User32.dll"
FUNCTION boolean CloseHandle(longptr w_handle) &
      LIBRARY "Kernel32.dll"

The following statement declares a function that draws a pie chart based on the coordinates received:

FUNCTION boolean Pie(longptr hwnd,long x1,long y1, &
      long x2,long y2,long x3,long y3,long x4, &
      long y4) LIBRARY "Gdi32.dll"

The following statement declares an external C function named IsZoomed:

FUNCTION boolean IsZoomed(longptr handle)  &
      LIBRARY "User32.DLL"

A script that uses IsZoomed is included as an example in Using utility functions to manage information.

For more information about these functions, see the Microsoft documentation in the MSDN Library at http://msdn.microsoft.com/en-us/library/ms674884(VS.85).aspx.

Passing arguments

In PowerBuilder, you can define external functions that expect arguments to be passed by reference or by value. When you pass an argument by reference, the external function receives a pointer to the argument and can change the contents of the argument and return the changed contents to PowerBuilder. When you pass the argument by value, the external function receives a copy of the argument and can change the contents of the copy of the argument. The changes affect only the local copy; the contents of the original argument are unchanged.

The syntax for an argument that is passed by reference is:

REF datatype arg

The syntax for an argument that is passed by value is:

datatype arg
Passing numeric datatypes

The following statement declares the external function TEMP in PowerBuilder. This function returns an integer and expects an integer argument to be passed by reference:

FUNCTION int TEMP(ref int degree)    LIBRARY "LibName.DLL"

The same statement in C would be:

int _stdcall TEMP(int * degree)

Since the argument is passed by reference, the function can change the contents of the argument, and changes made to the argument within the function will directly affect the value of the original variable in PowerBuilder. For example, the C statement *degree = 75 would change the argument named degree to 75 and return 75 to PowerBuilder.

The following statement declares the external function TEMP2 in PowerBuilder. This function returns an Integer and expects an Integer argument to be passed by value:

FUNCTION int TEMP2(int degree) LIBRARY "LibName.DLL"

The same statement in C would be:

int _stdcall TEMP2(int degree)

Since the argument is passed by value, the function can change the contents of the argument. All changes are made to the local copy of the argument; the variable in PowerBuilder is not affected.

Passing strings

PowerBuilder assumes all string arguments and returned values use Unicode encoding. If a function uses strings with ANSI encoding, you need to add an ALIAS FOR clause to the function declaration and add a semicolon followed by the ansi keyword. For example:

FUNCTION string NAME(string CODE)    LIBRARY "LibName.DLL" ALIAS FOR "NAME;ansi"

Passing by value

The following statement declares the external C function NAME in PowerBuilder. This function expects a String argument with Unicode encoding to be passed by value:

FUNCTION string NAME(string CODE)    LIBRARY "LibName.DLL"

The same statement in C would point to a buffer containing the String:

char * _stdcall NAME(char  * CODE)

Since the String is passed by value, the C function can change the contents of its local copy of CODE, but the original variable in PowerBuilder is not affected.

Passing by reference

PowerBuilder has access only to its own memory. Therefore, an external function cannot return to PowerBuilder a pointer to a string. (It cannot return a memory address.)

When you pass a string to an external function, either by value or by reference, PowerBuilder passes a pointer to the string. If you pass by value, any changes the function makes to the string are not accessible to PowerBuilder. If you pass by reference, they are.

The following statement declares the external C function NAME2 in PowerBuilder. This function returns a String and expects a String argument to be passed by reference:

FUNCTION string NAME2(ref string CODE)  &
      LIBRARY "LibName.DLL"

In C, the statement would be the same as when the argument is passed by value, shown above:

char * _stdcall NAME2(char * CODE)

The String argument is passed by reference, and the C function can change the contents of the argument and the original variable in PowerBuilder. For example, Strcpy(CODE,STUMP) would change the contents of CODE to STUMP and change the variable in the calling PowerBuilder script to the contents of variable STUMP.

If the function NAME2 in the preceding example takes a user ID and replaces it with the user's name, the PowerScript string variable CODE must be long enough to hold the returned value. To ensure that this is true, declare the String and then use the Space function to fill the String with blanks equal to the maximum number of characters you expect the function to return.

If the maximum number of characters allowed for a user's name is 40 and the ID is always five characters, you would fill the String CODE with 35 blanks before calling the external function:

String CODE
CODE = ID + Space(35)
. . .
NAME2(CODE)

For information about the Space function, see the section called “Space” in PowerScript Reference.

Passing characters

Passing chars to WinAPI

WinApi characters can have ANSI or Unicode values, while PowerBuilder characters have only Unicode values. ANSI Char values passed to and from WinAPI calls are automatically converted by PowerBuilder. Therefore, when defining character array length, you must always use the PowerBuilder character length (two bytes per character).

Passing chars to C functions

Char variables passed to external C functions are converted to the C char type before passing. Arrays of Char variables are converted to the equivalent C array of char variables.

An array of Char variables 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.

Recommendation

Whenever possible, pass String variables back to PowerBuilder as a return value from the function.

Using utility functions to manage information

The utility functions provide a way to obtain and pass Windows information to external functions and can be used as arguments in the PowerScript Send function. The following table describes the PowerScript utility functions.

Five utility functions

Function

Return value

Purpose

Handle

UnsignedInt

Returns the handle to a specified object.

IntHigh

UnsignedInt

Returns the high word of the specified Long value.

IntHigh is used to decode Windows values returned by external functions or the LongParm attribute of the Message object.

IntLow

UnsignedInt

Returns the low word of the specified Long value.

IntLow is used to decode Windows values returned by external functions or the LongParm attribute of the Message object.

Long

Long

Combines the low word and high word into a Long.

The Long function is used to pass values to external functions.

LongLong

LongLong

Combines the low word and high word into a LongLong.

The LongLong function is used to pass values to external functions.


Examples

This script uses the external function IsZoomed to test whether the current window is maximized. It uses the Handle function to pass a window handle to IsZoomed. It then displays the result in a SingleLineEdit named sle_output:

boolean Maxed
Maxed = IsZoomed(Handle(parent))
if Maxed then sle_output.Text = "Is maxed"
if not Maxed then sle_output.Text = "Is normal"

This script passes the handle of a window object to the external function FlashWindow to change the title bar of a window to inactive and then active:

// Declare loop counter and handle to window object
int       nLoop
uint      hWnd
// Get the handle to the PowerBuilder window.
hWnd = handle(This)
// Make the title bar inactive.
FlashWindow (hWnd, TRUE)
//Wait ...
For nLoop = 1 to 300
Next
// Return the title bar to its active color.
FlashWindow (hWnd, FALSE)

Sending Windows messages

To send Windows messages to a window that you created in PowerBuilder or to an external window (such as a window you created using an external function), use the Post or Send function. To trigger a PowerBuilder event, use the EVENT syntax or the TriggerEvent or PostEvent function.

Using Post and Send

You usually use the Post and Send functions to trigger Windows events that are not PowerBuilder-defined events. You can include these functions in a script for the window in which the event will be triggered or in any script in the application.

Post is asynchronous: the message is posted to the message queue for the window or control. Send is synchronous: the window or control receives the message immediately.

As of PowerBuilder 6.0, all events posted by PowerBuilder are processed by a separate queue from the Windows system queue. PowerBuilder posted messages are processed before Windows posted messages.

Obtaining the window's handle

To obtain the handle of the window, use the Handle function. To combine two integers to form the Long value of the message, use the Long function. Handle and Long are utility functions, which are discussed later in this chapter.

Triggering PowerBuilder events

To trigger a PowerBuilder event, you can use the techniques listed in the following table.

Technique

Description

TriggerEvent function

A synchronous function that triggers the event immediately in the window or control

PostEvent function

An asynchronous function: the event is posted to the event queue for the window or control

Event call syntax

A method of calling events directly for a control using dot notation


All three methods bypass the messaging queue and are easier to code than the Send and Post functions.

Example

All three statements shown below click the CommandButton cb_OK and are in scripts for the window that contains cb_OK.

The Send function uses the Handle utility function to obtain the handle of the window that contains cb_OK, then uses the Long function to combine the handle of cb_OK with 0 (BN_CLICK) to form a Long that identifies the object and the event:

Send(Handle(Parent),273,0,Long(Handle(cb_OK),0))
cb_OK.TriggerEvent(Clicked!)
cb_OK.EVENT Clicked()

The TriggerEvent function identifies the object in which the event will be triggered and then uses the enumerated datatype Clicked! to specify the clicked event.

The dot notation uses the EVENT keyword to trigger the Clicked event. TRIGGER is the default when you call an event. If you were posting the clicked event, you would use the POST keyword:

Cb_OK.EVENT POST Clicked()

The Message object

The Message object is a predefined PowerBuilder global object (like the default Transaction object SQLCA and the Error object) that is used in scripts to process Microsoft Windows events that are not PowerBuilder-defined events.

When a Microsoft Windows event occurs that is not a PowerBuilder-defined event, PowerBuilder populates the Message object with information about the event.

Other uses of the Message object

The Message object is also used:

Customizing the Message object

You can customize the global Message object used in your application by defining a standard class user object inherited from the built-in Message object. In the user object, you can add additional properties (instance variables) and functions. You then populate the user-defined properties and call the functions as needed in your application.

For more information about defining standard class user objects, see the section called “Building a standard class user object” in Users Guide.

Message object properties

The first four properties of the Message object correspond to the first four properties of the Microsoft Windows message structure.

Property

Datatype

Use

Handle

Integer

The handle of the window or control.

Number

Integer

The number that identifies the event (this number comes from Windows).

WordParm

UnsignedInt

The word parameter for the event (this parameter comes from Windows). The parameter's value and meaning are determined by the event.

LongParm

Long

The long parameter for the event (this number comes from Windows). The parameter's value and meaning are determined by the event.

DoubleParm

Double

A numeric or numeric variable.

StringParm

String

A string or string variable.

PowerObjectParm

PowerObject

Any PowerBuilder object type including structures.

Processed

Boolean

A boolean value set in the script for the user-defined event:

  • TRUE -- The script processed the event. Do not call the default window Proc (DefWindowProc) after the event has been processed.

  • FALSE -- (Default) Call DefWindowProc after the event has been processed.

ReturnValue

Long

The value you want returned to Windows when Message.Processed is TRUE.

When Message.Processed is FALSE, this attribute is ignored.


Use the values in the Message object in the event script that caused the Message object to be populated. For example, suppose the FileExists event contains the following script. OpenWithParm displays a response window that asks the user if it is OK to overwrite the file. The return value from FileExists determines whether the file is saved:

OpenWithParm( w_question, &
   "The specified file already exists. " + &
   "Do you want to overwrite it?" )
IF Message.StringParm = "Yes" THEN
   RETURN 0  // File is saved
ELSE
   RETURN -1 // Saving is canceled
END IF

For information on Microsoft message numbers and parameters, see the Microsoft Software Developer's Kit (SDK) documentation.

Context information

The PowerBuilder context feature allows applications to access certain host (non-PowerBuilder) services. This is a PowerBuilder implementation of functionality similar to the COM QueryInterface. PowerBuilder provides access to the following host services:

PowerBuilder creates service objects appropriate for the current execution context (native PowerBuilder or transaction server). This allows your application to take full advantage of the execution environment.

The context feature uses seven PowerBuilder service objects: ContextInformation, ContextKeyword, CORBACurrent, ErrorLogging, Inet, SSLServiceProvider, and TransactionServer; it also uses the InternetResult object. (The context feature is sometimes called the Context object, but it is not a PowerBuilder system object.)

For more information about these objects, see Objects and Controls or the section called “Browsing the class hierarchy” in Users Guide.

Enabling a service

Before you use a service, you instantiate it by calling the GetContextService function. When you call this function, PowerBuilder returns a reference to the instantiated service. Use this reference in dot notation when calling the service's functions.

To enable a service:

  1. Establish an instance variable of the appropriate type:

    ContextInformation icxinfo_base
    ContextKeyword     icxk_base
    CORBACurrent       corbcurr_base
    ErrorLogging       erl_base
    Inet               iinet_base
    SSLServiceProvider sslsp_base
    TransactionServer  ts_base
  2. Instantiate the instance variable by calling the GetContextService function:

    this.GetContextService("ContextInformation",  &
       icxinfo_base)
    this.GetContextService("ContextKeyword", icxk_base)
    // Use Keyword instead of ContextKeyword 
    this.GetContextService("Keyword", icxk_base) 
    this.GetContextService("CORBACurrent", &
       corbcurr_base)
    this.GetContextService("ErrorLogging", erl_base)
    this.GetContextService("Internet", iinet_base)
    this.GetContextService("SSLServiceProvider", &
       sslsp_base)
    this.GetContextService("TransactionServer",ts_base)

Using a CREATE statement

You can instantiate a service object with a PowerScript CREATE statement. However, this always creates an object for the default context (native PowerBuilder execution environment), regardless of where the application is running.

Context information service

You use the context information service to obtain information about an application's execution context. The service provides current version information, as well as whether the application is running in the PowerBuilder execution environment.

Accessing context information

Using the context information service, you can access the information in the following table.

Item

Use this function

Comment

Full context name

GetName

Value returned depends on the context:

  • Default: PowerBuilder Runtime

Abbreviated context name

GetShortName

Value returned depends on the context:

  • Default: PBRUN

Company name

GetCompanyName

Returns Appeon.

Version

GetVersionName

Returns the full version number (for example, 2019.0.1)

Major version

GetMajorVersion

Returns the major version number (for example, 2019)

Minor version

GetMinorVersion

Returns the minor version number (for example, 0)

Fix version

GetFixesVersion

Returns the fix version number (for example, 1)


Using the ClassName function for context information

You can also use the ClassName function to determine the context of the object.

You can use this information to verify that the context supports the current version. For example, if your application requires features or fixes from Version 2019.0.1, you can use the context information service to check the version in the current execution context.

To access context information:

  1. Declare an instance or global variable of type ContextInformation:

    ContextInformation icxinfo_base
  2. Create the context information service by calling the GetContextService function:

    this.GetContextService("ContextInformation", &
       icxinfo_base)
  3. Call context information service functions as necessary.

    This example calls the GetShortName function to determine the current context and the GetVersionName function to determine the current version:

    String  ls_name
    String  ls_version
    Constant String ls_currver = "12.5.0.1"
    icxinfo_base.GetShortName(ls_name)
    IF ls_name <> "PBRun" THEN
       cb_close.visible = FALSE
    END IF
    icxinfo_base.GetVersionName(ls_version)
    IF ls_version <> ls_currver THEN
       MessageBox("Error",  &
          "Must be at Version " + ls_currver)
    END IF

Context keyword service

Use the context keyword service to access environment information for the current context. In the default environment, this service returns host workstation environment variables.

Accessing environment variables

When running in the PowerBuilder execution environment (the default context), you use this service to return environment variables.

To access environment variables:

  1. Declare an instance or global variable of type ContextKeyword. Also declare an unbounded array of type String to contain returned values:

    ContextKeyword  icxk_base
    String   is_values[]
  2. Create the context information service by calling the GetContextService function:

    this.GetContextService("Keyword", icxk_base)
  3. Call the GetContextKeywords function to access the environment variable you want. This example calls the GetContextKeywords function to determine the current application Path:

    icxk_base.GetContextKeywords("Path", is_values)
  4. Extract values from the returned array as necessary. When accessing environment variables, the array should always have a single element:

    MessageBox("Path", "Path is: " + is_values[1])

CORBACurrent service (obsolete)

Obsolete service

CORBACurrent service is obsolete because EAServer is no longer supported since PowerBuilder 2017.

Client applications and EAServer components marked as OTS style can create, control, and obtain information about EAServer transactions using functions of the CORBACurrent context service object. The CORBACurrent object provides most of the methods defined for the CORBA Current interface.

Error logging service

To record errors generated by PowerBuilder objects running in a transaction server to a log file, create an instance of the ErrorLogging service object and invoke its log method. For example:

ErrorLogging erlinfo_base
this.GetContextService("ErrorLogging",  &
   erlinfo_base)
erlinfo_base.log("Write this string to log")

The errors are recorded in the Windows system application log if the component is running in COM+.

Internet service

Note

Inet object is considered to be obsolete. Obsolete features are still available to use, but are no longer eligible for technical support and will no longer be enhanced. You can replace it by using the WebBrowser control or the HTTPClient object.

Use the Internet service to:

  • Display a Web page in the default browser (HyperLinkToURL function, which starts the default browser with the specified URL)

  • Access the HTML for a specified page (GetURL function, which performs an HTTP Get)

  • Send data to a CGI, ISAPI, or NSAPI program (PostURL function, which performs an HTTP Post)

Hyperlinking to a URL

You call the Internet service's HyperLinkToURL function to start the default browser with a specified URL.

To hyperlink to a URL:

  1. Declare an instance or global variable of type Inet:

    Inet  iinet_base
  2. Create the Internet service by calling the GetContextService function:

    THIS.GetContextService("Inet", iinet_base)
  3. Call the HyperLinkToURL function, passing the URL of the page to display when the browser starts:

    iinet_base.HyperlinkToURL  &
       ("http://www.appeon.com")

Getting a URL

You call the Internet service's GetURL function to perform an HTTP Get, returning raw HTML for a specified URL. This function returns the raw HTML using the InternetResult object.

To perform an HTTP Get:

  1. Declare an instance or global variable of type Inet. Also declare an instance or global variable using the descendant InternetResult object as the datatype (n_ir_msgbox in this example):

    Inet  iinet_base
    n_ir_msgbox  iir_msgbox
  2. Create the Internet service by calling the GetContextService function:

    THIS.GetContextService("Internet", iinet_base)
  3. Create an instance of the descendant InternetResult object:

    iir_msgbox = CREATE n_ir_msgbox
  4. Call the GetURL function, passing the URL of the page to be returned and a reference to the instance of the descendant InternetResult object:

    iinet_base.GetURL  &
       ("http://www.appeon.com", iir_msgbox)

    When the GetURL function completes, it calls the InternetData function defined in the descendant InternetResult object, passing the HTML for the specified URL.

Posting to a URL

You call the Internet service's PostURL function to perform an HTTP Post, sending data to a CGI, ISAPI, or NSAPI program. This function returns the raw HTML using the InternetResult object.

To perform an HTTP Post:

  1. Declare an instance or global variable of type Inet. Also declare an instance or global variable using the descendant InternetResult object as the datatype (n_ir_msgbox in this example):

    Inet  iinet_base
    n_ir_msgbox  iir_msgbox
  2. Create the Internet service by calling the GetContextService function:

    THIS.GetContextService("Internet", iinet_base)
  3. Create an instance of the descendant InternetResult object:

    iir_msgbox = CREATE n_ir_msgbox
  4. Establish the arguments to the PostURL function:

    Blob    lblb_args
    String  ls_headers
    String  ls_url
    Long    ll_length
    ls_url = "http://coltrane.appeon.com/"
    ls_url += "cgi-bin/pbcgi80.exe/"
    ls_url += "myapp/n_cst_html/f_test?"
    lblb_args = Blob("")
    ll_length = Len(lblb_args)
    ls_headers = "Content-Length: "  &
       + String(ll_length) + "~n~n"
  5. Call the PostURL function, passing the URL of the routine to be executed, the arguments, the header, an optional server port specification, and a reference to the instance of the descendant InternetResult object:

    iinet_base.PostURL  &
       (ls_url, lblb_args, ls_headers, 8080, iir_msgbox)

    When the PostURL function completes, it calls the InternetData function defined in the descendant InternetResult object, passing the HTML returned by the specified routine.

Using the InternetResult object

The GetURL and PostURL functions both receive data in an InternetResult object. This object acts as a buffer, receiving and caching the asynchronous data as it is returned by means of the Internet. When all data is received, the InternetResult object calls its InternetData function, which you override to process the data as appropriate.

Implement in descendants of InternetResult

You implement this feature by creating standard class user objects of type InternetResult. In each of these descendant user objects, define an InternetData function to process the passed HTML as appropriate.

To implement a descendant InternetResult object:

  1. Create a standard class user object of type InternetResult.

  2. Declare a new user object function as follows:

    • Name

      InternetData

    • Access

      Public

    • Returns

      Integer

    • Argument name

      Data, passed by value

    • Argument datatype

      Blob

  3. Add code to the InternetData function that processes the returned HTML as appropriate. This example simply displays the HTML in a MessageBox:

    MessageBox("Returned HTML", &
       String(data, EncodingANSI!))
    Return 1

Transaction server service

Use the transaction server service to access information about the context of an object running in a transaction server. You can use the TransactionServer object to influence transaction behavior programmatically, and to access the methods of another component on the transaction server.