| By Roy Kiesler | Article Rating: |
|
| November 12, 2003 11:00 AM EST | Reads: |
19,608 |
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.
IPB_VALUE
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
|
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. |
IPB_ARGUMENTS
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.
IPB_VM
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
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.
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.
IMPLEMENTING PBNI CLASSES
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. |
PBNI REQUIRED FUNCTIONS
A PBNI DLL must export the following
functions in order to be callable
from a PowerBuilder application:
PBX_GETDESCRIPTION
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:
PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription();
A PBNI class must inherit from one of two system classes:
- UserObject: For visual extensions
- NonVisualObject: For nonvisual extensions
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.
PBX_CREATENONVISUALOBJECT
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:
PBXEXPORT PBXRESULT PBXCALL
PBX_CreateNonVisualObject
(
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 );
PBX_CREATEVISUALOBJECT
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:
PBXEXPORT PBXRESULT PBXCALL
PBX_CreateVisualObject
(
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.
PBX_INVOKEGLOBALFUNCTION
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:
PBXEXPORT PBXRESULT PBXCALL
PBX_InvokeGlobalFunction
(
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. |
BUILDING A PBNI EXTENSION DLL
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.
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. |
Published November 12, 2003 Reads 19,608
Copyright © 2003 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
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.

