Welcome!

PowerBuilder Authors: Chris Pollach, Avi Rosenthal, Yakov Fain, RealWire News Distribution, Al Soucy

Related Topics: PowerBuilder

PowerBuilder: Article

PowerBuilder Native Interface (PBNI) Part 2 - Extending functionality

PowerBuilder Native Interface (PBNI) Part 2 - Extending functionality

The PowerBuilder Native Interface (PBNI) is a standard C++ programming interface that allows developers to extend the PowerScript language with C++ classes and to call PowerScript functions from C++ applications.

In Part 2, we'll create your first PBNI class and work with PBNI objects.

In the following example, we'll build a very simple PBX that returns a string containing the text "Hello World".

Tip
Don't be discouraged by the simplicity of this example - we'll use it to ensure that all core PBNI components are in place. Subsequent examples will rapidly increase in complexity and satisfy your appetite for "real-world" code.

Create the PBNI DLL
The following steps will generate the file PBNIHello.DLL:
1.  Copy and save Listings 1-4 as individual files using the filenames provided at the top of each listing ( Listings 3-6 can be downloaded from www.sys-con.com/pbdj/sourcec.cfm.).
2.  Follow the directions given in Part 1 (Vol. 10, issue 10) in the section, "Building a PBNI Extension DLL," to point your C++ IDE to the location of the PBNI SDK files.
3.  Compile and link the files.

Create the PBD
The following steps will generate the file PBNIHello.PBD:
1.  Create a new directory to hold the PowerBuilder client application files; for example, C:Hello.
2.  Copy the PBNIHello.DLL file to the newly created C:Hello directory.
3.  Open a new command-line (DOS) window and type the following command:

PBX2PBD90 C:HelloPBNIHello.PBD C:HelloPBNIHello.DLL

The output of this command should look like the following:

Adding file:

  • C:HelloPBNIHello.dll into PBD: C:HelloPBNIHello.PBD ... Succeeded!

    Create the PB Client
    Create a simple PB app and add the PBNIHello.PBD to the library list of the new application. Figure 1 shows the contents of the PBD in the PowerBuilder System Tree pane.

    Next, add the following code snippet to the application's Open() event:

    n_cpp_hello pbniobj
    TRY
    pbniobj = CREATE n_cpp_hello
    MessageBox( "Hello from PBNI", pbniobj.of_hello() )
    CATCH ( PBXRuntimeError pbre )
    MessageBox( "PBNI Exception", pbre.GetMessage() )
    FINALLY
    IF IsValid( pbniobj ) THEN DESTROY pbniobj
    END TRY

    Save your application and run it. Figure 2 shows the output produced by the application.

    Congratulations! You have successfully implemented your first PBX.

    Tip
    Now that you know what the necessary components for a PBX are, you can use shortcuts that will make you more productive. In the %SYBASE%PowerBuilder 9.0SDKPBNIwizards folder, you'll find a file called pbext.awx; this is an Application Wizard for Microsoft Visual C++ 6.0. This wizard generates a skeleton C++ source and header file for a specified PBNI class. Copy this file to your Microsoft Visual StudioCommonmsdev98Template directory to make it available as a new project type in Visual C++ 6.0. The SDN CodeXchange Web site (http://codexchange.sybase.com) also contains an Application Wizard for use with Microsoft Visual C++ .NET.

    Note that the wizard will generate files with the extension .pbx instead of .dll. This is done with future development in mind (for example, a Windows Explorer extension for PBXs), and has no effect on the DLL.

    Example Review
    Figure 3 illustrates what happens behind the scenes during the creation of the PBNI Hello example and when the client PowerBuilder application is executed.

    1.  When the PBX2PBD90 utility is run, the PBX_GetDescription() function is called, which returns the class description as defined in Listing 1.
    2.  When creating a new instance of the n_cpp_hello class in PowerScript, the PBVM calls the PBX's PBX_CreateNonVisualObject() function, which in turn creates an instance of the PBX_Hello C++ class.
    3.  When calling the of_hello() method on the n_cpp_hello object created in step 2, the PBVM calls the Invoke() method on the PBX_Hello object.
    4.  The Invoke() method calls the Hello() C++ method, which sets the return value of the of_hello() method to the string "Hello World!".

    Working with PBNI Objects
    A PBNI C++ class is wrapped in an independent PBD that shields the user from having to code external function declarations in the application. PBNI classes can inherit from the following PowerBuilder constructs:

  • NonVisualObject
  • UserObject
  • Any descendant of the previous (for example, Exception, Transaction, and so on)

    PBNI classes can expose functions and subroutines, as well as events. A PBX can also expose global functions.

    Exchanging Data with PowerBuilder
    A PBX often needs to exchange data with PowerBuilder. It might need to access value parameters; modify reference parameters; access global, shared, or instance variables in a PowerBuilder class; or set a return value for an extension method.

    PBNI offers two interfaces for accessing and handling PowerBuilder data. They are:

    • IPB_Session
    • IPB_Value
    As mentioned at the beginning of Part 1, the IPB_Session interface offers low-level access to PowerBuilder data. It has more than 200 methods to:
  • Create, access, and manipulate variables of all native PowerBuilder types, including strings, blobs, and arrays.
  • Access global, shared, and instance variables on any PowerBuilder object.
  • Pass arguments to PowerScript functions and events.
  • Implement memory allocation and deallocation methods for PowerBuilder objects and PBVM sessions.

    The IPB_Value interface, on the other hand, offers a higher-level and type-safe access to PowerBuilder data. The logical equivalent to PowerBuilder's any data type, it offers a set of get and set methods to obtain and assign values of native PowerBuilder types.

    In addition, the PBCallInfo class and IPB_Arguments interface provide high-level access to functions, events, and their respective parameters as they are passed between PowerBuilder and the PBX.

    The IPB_Arguments interface exposes GetAt() and SetAt() methods that encapsulate data type conversions between C++ and PowerScript via the IPB_Value interface.

    Listing 5 demonstrates the use of all four constructs mentioned in this section (IPB_Session, IPB_Value, PBCallInfo, and IPB_Arguments).

    Calling PowerScript Object Functions
    PowerBuilder applications typically use the services of PBXs; however, it's not uncommon that a PBX would need to invoke a PowerScript method on a PowerBuilder object. PowerScript object functions can be invoked using the IPB_Session interface InvokeObjectFunction() method.

    A common scenario is callback functionality. To demonstrate this process, we'll examine a PBX that enumerates all top-level windows on the desktop and loads them into a PowerBuilder TreeView control, mimicking the user interface of the Microsoft Spy++ utility, shown in Figure 4.

    To accomplish this, our PBX will call the EnumWindows() Win32 API. This API takes a callback function as its first parameter; our implementation of this callback will, in turn, invoke a method on a PowerBuilder TreeView user object.

    Note: The contents of this PBNI class header file and the standard PBNI DLL entry point file were omitted for brevity. The complete source code for this example (both PowerBuilder and C++) is available from the SDN CodeXchange (http://codexchange.sybase.com) Web site.

    To invoke a PowerScript object function from PBNI, we need to know its signature. To find a method's signature, use the PowerBuilder system tree to locate the object that defines that method. Right-click the class definition and bring up its properties dialog box, as shown in Figure 5.

    Note: The PBSIG90 command-line program can be used for this purpose as well. PBSIG90 is discussed later.

    The last output line contains a sequence of four letters - LSSS - that encodes the signature, as follows:

    • L: Long return type
    • S: String parameter
    • S: String parameter
    • S: String parameter
    The PowerScript code of this method is shown in Listing 5.

    When the EnumWindows API is invoked, the callback function in Listing 6 is passed to it as the first parameter. For each window enumerated, EnumWindows invokes the callback method, which in turn calls the OnNewWindow PowerScript method (see Listing 5). Figure 6 shows the results in the PowerBuilder client application.

    PBNI Callback Example Review
    Figure 7 illustrates what happens behind the scenes when the PBNI Callback example is executed.

    1.  When a new instance of the n_cpp_enumwindows class is created in PowerScript, the PBVM calls the PBX's PBX_CreateNonVisualObject() function, which creates an instance of the CEnumWindows C++ class.
    2.  The of_EnumWindows() method is then invoked on the n_cpp_enumwindows object created in step 1, passing the TreeView control as a parameter. The PBVM then calls the Invoke() method on the CEnumWindows object.
    3.  The Invoke() method calls the EnumWindows() method, which calls the Win32 EnumWindows() API, passing the PBNIEnumWindowProc() function as the callback function.
    4.  The PBNIEnumWindowProc() callback function calls the OnNewWindow() function on the TreeView control for every window enumerated, adding a TreeView item with a label that consists of the window handle, classname, and title concatenated.

    Note
    Common Programming Error: Often you'll find yourself changing the data type of a reference parameter in a PBX's PowerScript method using the PBX_GetDescription() function; however, if you do not adjust the data type of the value assigned to the respective IPB_Value, a PB runtime exception will be thrown.

    Good Programming Practice: To guard your PBX from situations like that, wrap the Set[datatype] call in a try-catch; for example:

    // assign a long to a string by reference
    pbstring pbs = ci->pArgs->GetAt(0)->GetString();
    try {
    ci->pArgs->GetAt(0)->SetLong( 1234 );
    } catch ( PBXTypeMismatchException tme ) { // oops!
    pbxr = PBX_E_MISMATCHED_DATA_TYPE;
    }
  • More Stories By Roy Kiesler

    Roy Kiesler, a senior lead consultant with Percussion Software in Massachusetts, has worked with PowerBuilder for the past seven years. He now spends most of his time with XML and Java. A winner of the 2001 TeamSybase MVP award, Roy has been a member of TeamSybase since 1999.

    Comments (0)

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.