PowerBuilder Authors: Chris Pollach, Yeshim Deniz, Jayaram Krishnaswamy, Kevin Benedict, Avi Rosenthal

Related Topics: PowerBuilder

PowerBuilder: Article

PowerBuilder Native Interface (PBNI) Part 1

PowerBuilder Native Interface (PBNI) Part 1

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.

Modeled after the Java Native Interface (JNI), PBNI provides a native vehicle that extends the functionality of PowerBuilder to that of the C++ programming language.

Since its inception, PBNI has gained remarkable popularity within Sybase. PowerBuilder 9.0 ships with many new features, three of which - EJB client, Web services client, and the PBDOM XML parser - are PBNI implementations. Those, however, are merely the tip of an iceberg; the promise of PBNI can lead PowerBuilder programmers to new frontiers such as Microsoft .NET and has the potential to establish PowerBuilder as a new component model, alongside Microsoft's COM and Borland's VCL.

It's not the intent of these articles to serve as a PBNI SDK reference, as PowerBuilder 9.0 already includes one; rather, it's a practical guide to writing PBNI extension modules (PBXs), outlining best practices to follow, as well as common programming errors to avoid. That being said, a certain amount of "dry" SDK review is occasionally necessary in order to maintain clarity and context. In places where such a review was necessary, the PBNI reference manual has been paraphrased.

Note: This article assumes that the reader is an experienced C++ programmer familiar with Windows programming concepts, particularly DLLs, the Windows API, and advanced C++ concepts like templates and traits.

What Is PBNI?
PBNI is a collection of C++ interfaces (virtual abstract classes and structures) that enable programmers to rapidly develop C++ classes for use with PowerBuilder, as well as harness the power of the PowerScript language in C++ applications. The communications between the PowerBuilder Virtual Machine (PBVM) and PBXs is managed through four core interfaces:

  • IPB_Session
  • IPB_Value
  • IPB_Arguments
  • IPB_VM
The IPB_Session interface serves as the main conduit between native code and the PowerBuilder virtual machine. It exposes methods for accessing PowerScript data variables, creating and destroying PowerBuilder objects, invoking PowerScript functions, as well as catching and throwing PowerScript exceptions. The IPB_Session interface also exposes a method for setting a marshaler object, used to convert PowerBuilder data types and formats to other languages or component models. Marshaler extensions are covered later.

The IPB_Value interface is a container for PowerBuilder values, whose type can be any of the standard PowerBuilder data types, including:

  • Integer (signed and unsigned)
  • Long (signed and unsigned)
  • Real/Double/Decimal
  • Date/Time/DateTime
  • Boolean
  • Character
  • String
  • A PowerBuilder class
This interface provides access to and information about the underlying PowerBuilder value, including its data type, access type (single or array; simple or object), reference type (by reference or by value), and a null or valid flag.

Good Programming Practice
When data integrity is of importance, use the IPB_Value data extraction methods rather than those of the IPB_Session interface because they provide "safe" access to the underlying variable through a set of reflection-like methods - for example, GetType(), GetClass(), IsObject(), IsByRef(), and so on.

The IPB_Arguments interface provides high-level access to arguments that are passed to a PowerScript function, the IPBX_UserObject::Invoke() method, or the IPBX_Marshaler interface methods. The IPB_Arguments interface is used only in the PBCallInfo structure, which will be discussed in a later article.

The IPB_VM interface is used by C++ applications that need to invoke a PowerScript method declared in a PowerBuilder class. The C++ application gets a pointer to the IPB_VM interface by loading the PBVM DLL into its memory space and calling the PB_GetVM() function, exported by the PBVM DLL. It then creates a new session by calling the IPB_VM interface's CreateSession() method. From that point, the IPB_Session interface methods are used to instantiate an object of the PowerBuilder class and invoke its methods as necessary.

The PBNI SDK is composed of seven header files and one import library. Header files and their characteristics are shown in Table 1, and library characteristics are shown in Table 2.

Table 1

Table 2

Building PBNI Extensions
To develop a PBX, you need to go through the following steps:
1.  Determine what native classes, controls, or global functions you want to provide PowerBuilder developers with.
2.  Write the PowerBuilder class definitions for those classes, controls, or global functions.
3.  For each native class defined, write a corresponding C++ class.
4.  Implement the functions required by PBNI.
5.  Build the extension DLL (PBX).
6.  Generate a PBD file for the extension with the PBX2PBD90 tool.

Steps 1 and 2 will not be discussed herein because they're simple tasks for most PowerBuilder developers. The PBX2PBD90 tool mentioned in step 6 is discussed in the "PBNI Utilities" in a later article. Steps 3, 4, and 5 are discussed later.

The IPBX_UserObject interface is the ancestor of all PBNI classes. It defines two virtual functions, as follows:

virtual PBXRESULT Invoke
IPB_Session *session,
pbobject obj,
pbmethodID mid,
PBCallInfo *ci
) = 0;
virtual void Destroy() = 0;

The Invoke method must be implemented by every PBNI class. A typical implementation of this method resembles Listing 1. (Listings 1 and 2 can be downloaded from www.sys-con.com/pbdj/sourcec.cfm.)

The third parameter of this method (pbmethodID) holds the ordinal of the method that was invoked on the PowerBuilder wrapper object. The value of this ordinal is declared in the C++ class definition.

The fourth parameter is a pointer to a PBCallInfo struct that holds any parameters passed to the current method call.

The Destroy method is called by the PBVM to remove an object instance that was previously created by either the PBX_CreateNonVisualObject() or the PBX_CreateVisualObject() functions.

Consider the following PowerScript:

IF IsValid( cpp_obj ) THEN DESTROY cpp_obj

When the DESTROY statement is executed, PowerBuilder invokes the Destroy method on the PBX class. The code in the Destroy method will typically call the C++ delete statement, which in turn will invoke the class destructor of the PBNI module. For example:

void CMyClass::Destroy()
delete this;

Good Programming Practice
IPBX_UserObject is an abstract class. Your PBNI classes should never inherit directly from IPBX_UserObject; instead, use the IPBX_NonVisualObject interface, the IPBX_VisualObject interface, or their descendants.

A PBNI DLL must export the following functions in order to be callable from a PowerBuilder application:

  • PBX_GetDescription(): All extensions
  • PBX_CreateNonVisualObject(): For DLLs that provide nonvisual extensions
  • PBX_CreateVisualObject(): For DLLs that provide visual controls
  • PBX_InvokeGlobalFunction(): For PBXs that expose global functions

    The PBX_GetDescription function returns the PowerBuilder class description( s) contained in the PBNI module. For example:

    static const TCHAR classDesc[] = {
    "class n_cpp_base64 from
    NonVisualObject "
    " function string encode( blob data) "
    " function blob decode( string data
    ) "
    "end class"

    The signature for this function is as follows:


    A PBNI class must inherit from one of two system classes:

    • UserObject: For visual extensions
    • NonVisualObject: For nonvisual extensions
    A PBNI class can also inherit from any descendants of the previous two classes, such as Transaction or Exception.

    The description can contain more than one class definition. An example is shown in Listing 2.

    A class definition can reference any class definition that appears before it in the description. The description returned from PBX_GetDescription is used by the PBX2PBD90 tool to generate a PBD wrapper for the PBNI class.

    The PBX_CreateNonVisualObject() function is called by PowerBuilder to create a new instance of a C++ class provided in the PBX DLL. The C++ class is wrapped in a PowerBuilder nonvisual object.

    The signature for this function is as follows:

    IPB_Session * session,
    pbobject obj,
    LPCSTR xtraName,
    IPBX_NonVisualObject ** nvobj

    Consider the following bit of PowerScript:

    n_cpp_base64 base64 base64 = CREATE n_cpp_base64

    When the CREATE statement is executed, PowerBuilder invokes the PBX_CreateNonVisualObject method in the PBNI module, passing the bitwise object as the second parameter and "n_cpp_base64" (all lowercase) as the third parameter.

    The fourth parameter is a pointer to the C++ abstract class that will be used to create the new instance of the PBNI class. For example:

    if ( strcmp( xtraName, "n_cpp_base64" ) == 0 )
    *nvobj = new PBX_Base64( session );

    The PBX_CreateVisualObject() function is called by PowerBuilder to create a new instance of the C++ class provided in the PBX DLL. The C++ class is wrapped in a PowerBuilder user object. The signature for this function is:

    IPB_Session * session,
    pbobject obj,
    LPCSTR xtraName,
    IPBX_NonVisualObject ** nvobj

    Consider the following bit of PowerScript:

    u_cpp_xpbutton xpbutton OpenUserObject( xpbutton, "u_cpp_xpbutton", 10, 10 )

    When the OpenUserObject PowerScript function is called, PowerBuilder invokes the PBX_CreateVisualObject method in the PBNI module, passing the xpbutton object as the second parameter and "u_cpp_xpbutton" (all lowercase) as the third parameter.

    The fourth parameter is a pointer to the C++ abstract class that will be used to create the new instance of the PBNI class. For example:

    if ( strcmp( xtraName, "u_cpp_xpbutton" ) == 0 )
    *nvobj = new PBXV_XPButton( session );

    The constructor of the PBXV_XPButton class (inherited from IPBX_VisualObject) calls the IPBX_ VisualObject GetWindowClassName() method, followed by the CreateControl() method. The latter is responsible for the creation, initialization, and display of the visual control.

    The PBX_InvokeGlobalFunction() function is called by PowerBuilder when a global PBNI function is invoked. Your PBNI DLL needs to export this function if it exposes any global functions. The signature for this function is as follows:

    IPB_Session* pbsession,
    LPCTSTR functionName,
    PBCallInfo* ci

    Good Programming Practice
    Your PowerBuilder classname does not need to match the C++ classname. Most PowerBuilder programmers are familiar with the "n_cst" prefix, representing nonvisual custom classes, and the "u_" prefix for visual user object. Along the same lines, I suggest using "n_cpp" to denote PBNI nonvisual classes, written in C++ (hence the "cpp"). Similarly, visual user objects can use the prefix "u_cpp". The C++ implementation classes follow a similar naming convention, where the name of nonvisual classes (including those used for global functions) is prefixed with "PBX_"; for example, "PBX_Base64". Visual classnames are prefixed with "PBXV_"; for example, "PBXV_XPButton", and so on.

    To ensure the successful compilation and build of a PBX, the following requirements must be met:
    1.  The location of the PBNI header files needs to be known to the C++ compiler.
    2.  The location of the PBNI library file needs to be known to the C++ linker.

    Most C++ compilers and linkers have specific command switches that specify the aforementioned file locations. Depending on your choice of a C++ development environment, a convenient user interface might exist to modify these settings. Figures 1 and 2 illustrate such a user interface provided by Microsoft Visual C++ 7.0, an integral part of the Microsoft VisualStudio.NET development environment.

    Fig 1

    fig 2

    Note: All code examples in this article were compiled using both Microsoft Visual C++ .NET and Borland C++ Builder 6.0. Versions 5.0 and 6.0 of Visual C++ can also be used. At the time of writing, the latest beta of the open-source Watcom C++ 11.0c failed to compile the pbtraits.h file because it uses template specialization syntax that is not yet supported. Other compilers, such as CodeWarrior and GNU C++, might work, but they were not tested.

    This article is based on PowerBuilder 9 Client/Server Development by various authors (ISBN 0672325004), published by Sams Publishing. Also look for PowerBuilder 9 Internet and Distributed Application Development.
  • 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.