Calling PowerBuilder objects from C++

This section presents a simple example that illustrates how to call a function on a PowerBuilder custom class user object from a C++ application:

Creating a PowerBuilder object to be called from C++

To keep the code for this example simple, create an application with one custom class user object that has one function. The function returns the product of two integers:

  1. In PowerBuilder, create a new workspace.

  2. Select the Application icon from the Target page of the New dialog box and name the application loadpbvm.

  3. Select the Custom Class icon from the PB Object page of the New dialog box.

  4. In the Function prototype window, create a function with this signature:

    f_mult ( integer arg1, integer arg2 )  returns integer
  5. Save the user object as nvo_mult and close the User Object painter.

Getting the signature of a function

To write the C++ code that invokes the f_mult function, you need to obtain its method ID. The method ID is used to initialize the PBCallInfo structure and to invoke the function. There are two IPB_Session functions that return a method ID: GetMethodID, which takes a signature, and FindMatchingFunction, which takes a comma-separated list of arguments. You use the same functions when you call PowerScript from the code in your extension; see Calling PowerScript from an extension.

If you want to use GetMethodID, you need a signature. This function is simple enough that you do not need a tool to obtain a signature -- the signature is the string III, which indicates that the function returns an integer and takes two integers as arguments.

For more complicated functions, you can get the signature from the System Tree or with the pbsig190 tool.

Getting a signature from the System Tree

To get the signature of f_mult in the System Tree, expand nvo_mult, right-click on the f_mult function, and select Properties from the pop-up menu. The signature displays in the Properties dialog box in the Signature text box:

Getting a signature using pbsig190

To get the signature of f_mult with pbsig190, type the following at a command prompt:

pbsig190 d:\pbls\loadpbvm.pbl

In the output of pbsig190, the comment on the last line contains the signature to be passed as the method ID argument to GetMethodID:

PB Object Name: loadpbvm

PB Object Name: nvo_mult   public function integer f_mult (integer arg1,
      integer arg2)
      /*  III  */

For more information about the pbsig190 tool and the format of method signatures, see pbsig190.

Creating the C++ application

To create the C++ application, follow these steps:

Load the PowerBuilder VM

In your C++ development tool, create a new console application project. The include directory for the PBNI SDK, typically PowerBuilder 2019\SDK\PBNI\include, must be in your include path. If you use any helper classes, the source file that contains them must be added to your project. For a list of files and helper classes, see the table in The PBNI SDK.

The code for the C++ application creates an IPB_VM object using the PB_GetVM function and loads the PowerBuilder VM:

#include "pbext.h"
#include "stdio.h"

typedef PBXEXPORT PBXRESULT (*P_PB_GetVM)(IPB_VM** vm);

int main(int argc, char *argv[])
{
   IPB_Session* session;
   IPB_VM* pbvm = NULL;

   //Load the PowerBuilder VM module
   HINSTANCE hinst = LoadLibrary("pbvm190.dll");
   if ( hinst== NULL) return 0;
   fprintf(stderr, "Loaded PBVM successfully\n");

Call PB_GetVM to get a pointer to the IPB_VM interface

The next step is to call the PB_GetVM function to get a pointer to the IPB_VM interface:

   P_PB_GetVM getvm = (P_PB_GetVM)GetProcAddress
      (hinst,"PB_GetVM");
   if (getvm == NULL) return 0;

   getvm(&pbvm);
   if (pbvm == NULL) return 0;

Create an IPB_Session object within IPB_VM

Next create an IPB_Session object within IPB_VM, using the PowerBuilder application's name and library list as arguments:

   // loadpbvm.pbl must contain an application object
   // named loadpbvm and it must be on the search path
   // for the executable file
   LPCTSTR LibList[] = {"loadpbvm.pbl"}; 
   if ( pbvm->CreateSession("loadpbvm", LibList, 1,
      &session) != PBX_OK )
   {
      fprintf(stderr, "Error in CreateSession\n");
      return 1;
   }
   fprintf(stderr, "Created session successfully\n");

Create an instance of the PowerBuilder object

After the session has been created, the C++ application can create PowerBuilder objects and call PowerBuilder functions in that session.

You use the FindGroup function to locate the group that contains the user object you want to use. FindGroup takes the name of the object as its first argument, and an enumerated type as its second argument. You are looking for a user object, so the second argument is pbgroup_userobject.

You pass the group returned from FindGroup to the FindClass function to get a class that you can pass to the NewObject function:

   // Create the PowerBuilder object contained 
   // in loadpbvm.pbl. 
   // First find the group that contains the 
   // user object nvo_mult
   pbgroup group = session->FindGroup("nvo_mult",
      pbgroup_userobject);
   if (group == NULL) return 0;
      // Now find the class nvo_mult in the group
   pbclass cls = session->FindClass(group,"nvo_mult");
   if (cls == NULL) return 0;
      // Create an instance of the PowerBuilder object
   pbobject pbobj = session->NewObject(cls);

Initialize the PBCallInfo structure

Next, get the method ID for the function you want to call and initialize a PBCallInfo structure. You pass the signature obtained in Getting the signature of a function to the GetMethodID function:

   // PBCallInfo contains arguments and return value
   PBCallInfo ci; 

   // To call the class member function f_mult, 
   // pass its signature as the last argument
   // to GetMethodID 
   pbmethodID mid = session->GetMethodID(cls, "f_mult",
      PBRT_FUNCTION, "III");

   // Initialize call info structure based on method ID
   session->InitCallInfo(cls, mid, &ci);

You could use FindMatchingFunction instead of GetMethodID to get the method ID. The call would look like this, because f_mult takes two integer arguments:

   pbmethodID mid = session->FindMatchingFunction(cls,
      "f_mult", PBRT_FUNCTION, "int, int");

Call the PowerBuilder function

Before you call the function, you must supply the integers to be multiplied. For the sake of simplicity, the following code sets them directly in the PBCallInfo structure.

   // Set IN arguments. The prototype of the function is
   // integer f_mult(integer arg1, integer arg2)
   ci.pArgs-> GetAt(0)->SetInt(123);
   ci.pArgs-> GetAt(1)->SetInt(45);

Finally call the function, wrapping it in a try-catch statement to handle any runtime errors:

   // Call the function
   try
   {
      session->InvokeObjectFunction(pbobj, mid, &ci);

      // Was PB exception thrown?
      if (session->HasExceptionThrown())
      {
         // Handle PB exception
         session->ClearException();
      }
   }
   catch (...)
   {
      // Handle C++ exception
   }

   // Get the return value and print it to the console
   pbint ret = ci.returnValue->GetInt();
   fprintf(stderr, "The product of 123 and 45 is %i\n",
      ret);

Write cleanup code

When you have finished with the PBCallInfo structure, call FreeCallInfo to release the memory allocated to it, then delete the structure, release the session, and free the library:

   // Release Call Info
   session->FreeCallInfo(&ci);
   delete &ci;

   // Release session
   session->Release();
   return 0;
   FreeLibrary(hinst);
}

Running the C++ application

When you run the compiled executable file at the command prompt, if the PowerBuilder VM is loaded and the session is created successfully, the following output displays in the command window:

Loaded PBVM successfully
Created session successfully
The product of 123 and 45 is 5535