Creating a PowerBuilder extension

To build a PowerBuilder extension, follow these steps: 

Step 1: Decide on a feature to implement.

Step 2: Define the classes and functions in the extension .

Step 3: Declare native classes and global functions.

Step 4: Implement native classes and global functions.

Step 5: Export methods to create class instances.

Step 6: Build a PBX .

These steps apply whether you are building a nonvisual or a visual extension. The differences between building nonvisual and visual extensions are described in Creating and using a visual extension. This section focuses primarily on nonvisual extensions.

Required methods

All PowerBuilder nonvisual extensions must export two methods: PBX_GetDescription and PBX_CreateNonVisualObject. The use of these methods is described in Step 2: Define the classes and functions in the extension  and Step 5: Export methods to create class instances.

PowerBuilder visual extensions must export PBX_GetDescription and PBX_CreateVisualObject. See Creating and using a visual extension.

If the extension declares global functions, it must also export the PBX_InvokeGlobalFunction method.

For every native class, you must implement two PBNI methods, Invoke and Destroy, in addition to the methods the class will provide. The use of these PBNI methods is described in Step 4: Implement native classes and global functions.

Step 1: Decide on a feature to implement

The first step in building a PowerBuilder extension is to identify a problem that an extension can solve. This might be a feature that can be coded more efficiently and easily in C++ than in PowerScript, or that requires the use of callback functions or nonstandard datatypes. You might also have access to existing C++ classes that perform the tasks you want to add to a PowerBuilder application, or you might want to create a wrapper for existing standard utilities written in C++.

For possible uses of PowerBuilder extensions, see Understanding PowerBuilder extensions.

For examples of PowerBuilder extensions, see the PowerBuilder Code Samples Web site at https://www.appeon.com/developers/library/code-samples-for-pb.

Step 2: Define the classes and functions in the extension

Your C++ code must expose two standard methods that enable PowerBuilder to recognize each native class and create instances of the class. One of these methods is PBX_GetDescription.

Use PBX_GetDescription to pass the descriptions of classes and global functions in the PowerBuilder extension to PowerBuilder. Every extension must export this method. Importing the PBX or DLL file into a PBL converts the description of the extension into PowerScript and adds it to the PBL as source code. The keyword native in the source code indicates that the PowerBuilder type was defined in an extension.

All the classes or global functions in an extension module are passed in a single description. The examples that follow illustrate how you define classes and functions in a description. For the full syntax, see PBX_GetDescription.

Describing nonvisual classes

Nonvisual classes can inherit from the NonVisualObject PowerBuilder system class or any of its descendants. While a native class can inherit from a user-defined user object, Appeon recommends that you use only system classes. Each native class can provide several functions, subroutines, and events.

The following example shows how you use the PBX_GetDescription method in the C++ code for an extension that includes three nonvisual classes. ClassName1 inherits from NonVisualObject, ClassName2 inherits from Exception, and ClassName3 inherits from Transaction. All three classes must be in a single description passed by PBX_GetDescription:

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription()
{
  static const TCHAR desc[] = {
   // Description begins here
   "class ClassName1 from NonVisualObject\n"
    "function integer objectFunction(integer a[])\n"
    "subroutine objectSubroutine(integer ai_ref)\n"
    "event integer eventName(integer b)\n"
    "end class\n"

    "class ClassName2 from Exception\n"
    "function integer objectFunction(readonly 
       integer ai)\n"
    "subroutine objectSubroutine(integer arg)\n"
    "event integer eventName(integer arg)\n"
    "end class\n"

    "class ClassName3 from Transaction\n"
    "function integer objectFunction(integer arg)\n"
    "subroutine objectSubroutine(integer arg)\n"
    "event integer eventName(integer arg)\n"
    "end class\n"
    // Description ends here
  };
  return desc;
}

Describing visual classes

Visual native classes can inherit only from the UserObject PowerBuilder system class. The PowerBuilder VM considers any class that inherits from UserObject to be a visual class. All other native classes are considered to be nonvisual classes. For more information about how to describe visual classes, see Creating and using a visual extension.

Describing global functions

An extension can include global functions as well as classes. This example shows a description for two global functions:

"globalfunctions \n" \
"function int g_1(int a, int b)\n" \
"function long g_2(long a, long b)\n" \
"end globalfunctions\n"

The syntax and usage of global functions defined in an extension are the same as for global functions defined in the Function painter in PowerBuilder.

Global functions cannot be overloaded

Like global functions in PowerScript, global functions in a PowerBuilder extension cannot be overloaded.

Using forward declarations

PowerBuilder extensions can provide multiple classes. A class can reference any class that is defined earlier in the description, but if it references a class defined later in the description, you must provide a forward declaration. This example shows a description that includes forward declarations for two classes, nativeclass_1 and nativeclass_2, that reference each other. This example also demonstrates that a single description can include global functions as well as classes:

"forward\n" \
"class nativeclass_1 from nonvisualobject\n"\
"class nativeclass_2 from nonvisualobject\n"\
"end forward\n" \

"class nativeclass_1 from nonvisualobject \n" \
"function int add(nativeclass_2 a, int b)\n" \
"function int sub(int a, int b)\n" \
"end class \n" \

"class nativeclass_2 from nonvisualobject \n" \
"function int add(nativeclass_1 a, int b)\n" \
"function int sub(int a, int b)\n" \
"end class \n"

"globalfunctions \n" \
"function int g_1(int a, int b)\n" \
"function long g_2(long a, long b)\n" \
"end globalfunctions\n"

Step 3: Declare native classes and global functions

For each native class that the nonvisual extension supports, declare an ANSI C++ class that inherits from IPBX_NonVisualObject, which is the ancestor class for all nonvisual PowerBuilder native classes. 

The declaration of the class can be placed in a header file, and it must include Invoke and Destroy methods. This is a simple prototype for a nonvisual class:

#include "pbext.h"

class CMyClass : public IPBX_NonVisualObject
{
enum MethodIDs
{
   mFunca = 0,
   mFuncb = 1
};
public:
   // constructor, destructor
   CMyClass()
   virtual ~CMyClass()
   // member methods   PBXRESULT Invoke(
      IPB_Session   *session, 
      pbobject   obj, 
      pbmethodID   mid,
      PBCallInfo   *ci
   );
   void Destroy();

private:
   void funcA(IPB_Session* session, pbobject obj,
       PBCallInfo* ci);
   void funcB(IPB_Session* session, pbobject obj,
       PBCallInfo* ci);
};

If you declare global functions in your extension, the extension must export the PBX_InvokeGlobalFunction method. The following PBX_GetDescription call declares three global functions: bitAnd, bitOr, and bitXor:

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription()
{
   static const TCHAR desc[] = {
      "globalfunctions\n"
      "function int bitAnd(int a, int b)\n"
      "function int bitOr(int a, int b)\n"
      "function int bitXor(int a, int b)\n"
      "end globalfunctions\n"
   };

  return desc;
}

Step 4: Implement native classes and global functions

The implementation of each class must include the implementation of the Invoke and Destroy methods, as well as all the methods declared for the class. Invoke and Destroy are methods of the IPBX_UserObject interface. 

When the PowerBuilder application calls a method on the native class, the PBVM calls the Invoke method, which dispatches the call based on the method ID or method name. The method name is used when the method is called dynamically.

The Invoke method must be coded to invoke each method in the class. The example that follows shows a switch-case statement that invokes either funcA or funcB, depending on the value of the method ID. When the PowerBuilder application has finished using an instance of a native class, the PBVM calls the Destroy method.

This example does not show the implementation of the methods of the class itself:

PBXRESULT MyClass::Invoke(IPB_Session *session, pbobject obj, pbmethodID mid, PBCallInfo *ci)
{
   PBXRESULT result = PBX_OK;

   switch (mid)
   {
   case mFunca:
      result = funcA(session, obj, ci);
      break;

   case mFuncb:
      result = funcB(session, obj, ci);
      break;

   default:
      result = PBX_E_INVOKE_FAILURE;
      break;
   }

   return result;
}
// Implementation of funcA and funcB not shown
void Destroy()
   {
      delete this;
}

The following PBX_InvokeGlobalFunction contains the implementation of the three global functions included in the description shown in Step 3: Declare native classes and global functions:

PBXEXPORT PBXRESULT PBXCALL PBX_InvokeGlobalFunction
(
   IPB_Session*   pbsession,
   LPCTSTR        functionName,
   PBCallInfo*    ci   )
{

  PBXRESULT pbrResult = PBX_OK;

  int arg1 = ci->pArgs->GetAt(0)->GetInt();
  int arg2 = ci->pArgs->GetAt(1)->GetInt();

  if (stricmp(functionName, "bitand") == 0)
  {
     ci->returnValue->SetInt(arg1 & arg2);
  }else if (strcmp(functionName, "bitor") == 0)
  {
     ci->returnValue->SetInt(arg1 | arg2);
  }else if (strcmp(functionName, "bitxor") == 0)
  {
     ci->returnValue->SetInt(arg1 ^ arg2);
  }else
  {
     return PBX_FAIL;
  }

  return pbrResult;
}

Step 5: Export methods to create class instances

PowerBuilder creates nonvisual and visual class instances differently:

  • For visual classes, the instance is created when the window or visual control in which the class is used is opened. See Creating visual class instances.

  • For nonvisual classes, the instance is created when the PowerBuilder CREATE statement is used. This is described next.

When the PowerBuilder application creates an instance of a nonvisual class using the PowerScript CREATE statement, the PBVM calls the PBX_CreateNonVisualObject method in the extension. Every extension that contains nonvisual native classes must export this method.

In the same way that multiple classes are included in a single description passed by PBX_GetDescription, PBX_CreateNonVisualObject can be used to create multiple classes.

In this example, the extension has three classes. An IF statement compares the name of the class passed in from the PowerBuilder CREATE statement to the name of each of the classes in the extension in turn and creates an instance of the first class with a matching name. You could also use a CASE statement. The class name in the string comparison must be all lowercase:

PBXEXPORT PBXRESULT PBXCALL PBX_CreateNonVisualObject(
   IPB_Session * session, 
   pbobject obj, 
   LPCSTR className, 
   IPBX_NonVisualObject **nvobj
)
{
   PBXRESULT result = PBX_OK;
   // The class name must not contain uppercase
   if ( strcmp( className, "classone" ) == 0 )
      *nvobj = new ClassOne;
   else if ( strcmp( className, "classtwo" ) == 0 )
      *nvobj = new ClassTwo( session );
   else if ( strcmp( className, "classthree" ) == 0 )
      *nvobj = new ClassThree;
   else   {
      *nvobj = NULL;
      result = PBX_E_NO_SUCH_CLASS;
   }
   return PBX_OK;
};

Step 6: Build a PBX

Using your C++ development tool or the command line, build a PBX from your C++ classes.

When you compile and link the C++ code, verify the following:

  • The include directory for the PBNI SDK, typically PowerBuilder 17.0\SDK\PBNI\include, must be in your include path.

  • If you use any helper classes, make sure the source file that contains them is added to your project. For a list of classes, see the table in The PBNI SDK.

Now you are ready to use the extension in a PowerBuilder application.