Calling PowerBuilder from C++

About this chapter

A third-party application or server written in C++ can load the PowerBuilder VM, use PowerBuilder nonvisual objects, and use PowerBuilder visual controls. This chapter uses some simple examples to illustrate the process.

About calling PowerScript from C++ applications

If you have a PowerBuilder custom class user object that performs intensive programming that would be useful to an application that you need to write in C++, you can access the object directly from the C++ application using PBNI. You do not need to make the user object into a COM component or automation server.

To call functions on a PowerBuilder object, you can embed the PBVM in the C++ application. The C++ application must load the PBVM by loading pbvm.dll with the Windows LoadLibrary function, get a pointer to the IPB_VM interface by calling the PB_GetVM function exported by pbvm.dll, and create a session by calling the IPB_VM CreateSession function.

The application can then create an instance of the PowerBuilder class and invoke its functions through the IPB_Session interface.

The following figure illustrates the relationship between the C++ application and the interfaces provided by the PBVM.

Figure: Embedding the PBVM in a C++ application

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 pbsig210 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 pbsig210

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

pbsig210 d:\pbls\loadpbvm.pbl

In the output of pbsig210, 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 pbsig210 tool and the format of method signatures, see pbsig210.

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("pbvm.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

Accessing result sets

You can use the IPB_ResultSetAccessor interface to access result sets in PowerBuilder. Use the IPB_Session GetResultSetAccessor method to create an instance of the interface using a result set returned from PowerBuilder as the method's argument. You can then use the IPB_ResultSetAccessor's getColumnCount, GetRowCount, GetItemData, and GetColumnMetaData methods to obtain information from the result set.

GetItemData uses the IPB_RSItemData interface to handle the data in each cell in the result set. If the data has a date, time, or datetime datatype, it is stored in a PB_DateData, PB_TimeData, or PB_DateTimeData structure.

To create a result set that can be passed to PowerBuilder, use the IPB_Session CreateResultSet method. See CreateResultSet for an example.

Processing PowerBuilder messages in C++

You can open a PowerBuilder window from a C++ application or from an extension, but to make sure that events triggered in the window or control are processed, you need to make sure that the C++ application processes PowerBuilder messages. The IPB_Session ProcessPBMessage function lets you do this.

Each time the ProcessPBMessage function is called, it attempts to retrieve a message from the PowerBuilder message queue and process it. The function is similar to the PowerBuilder Yield function, which yields control to other graphic objects and pulls messages from PowerBuilder objects and other graphic objects from the queue. However, ProcessPBMessage processes only one message at a time, and it processes only PowerBuilder messages.

Messages are added to the PowerBuilder message queue when you call the PostEvent function.

ProcessPBMessage must be called repeatedly

You need to make sure that the ProcessPBMessage function is called repeatedly. For most C++ applications, you can provide a message loop in the main function and insert the IPB_Session ProcessPBMessage function in the message loop. This is shown in the example that follows.

If you use Microsoft Foundation Classes (MFC), you cannot modify the built-in message loop. To ensure that the ProcessPBMessage function is called repeatedly, you can overload the CWnd::WindowProc function and insert ProcessPBMessage into the overloaded function:

LRESULT CCallPBVCtrl::WindowProc(UINT message, 
   WPARAM wParam, LPARAM lParam)
{
   d_session->ProcessPBMessage();
   return CDialog::WindowProc(message, wParam, lParam);
}

Examples

The following code fragments are from a C++ program that opens a window. The window has a menu item that invokes the Open event of a PowerBuilder application.

Calling ProcessPBMessage

The call to ProcessPBMessage is in a loop in the WinMain function:

int __stdcall WinMain(HINSTANCE hInstance, 
                      HINSTANCE hPrevInstance,
                      LPSTR lpCmdLine,
                      int nCmdShow)
{
   MSG msg;

   WNDCLASSEX wcex;

// initialization code omitted
   ...
   RegisterClassEx(&wcex);

   HWND hWnd = CreateWindow(szWndClsName,
      "OpenPBWindow", WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL,
      hInstance, NULL);

   if (!hWnd)
   {
     return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   try
   {
       while (GetMessage(&msg, NULL, 0, 0)) 
       {
          TranslateMessage(&msg);
          DispatchMessage(&msg);

          // Call to ProcessPBMessage
          if (session)
             session->ProcessPBMessage();
      }
   }
   catch(...)
      {
          MessageBox(NULL, "Exception occurs",
            "Exception", MB_OK);
      }
   return msg.wParam;
}

Loading the PBVM and triggering an event

In the WndProc function, when the WM_CREATE message is passed, the PBVM is loaded and the library list, containing openwin.pbl, is passed to CreateSession. When the user selects the menu item that opens the PowerBuilder window, the FindGroup, FindClass, and GetMethodID functions obtain the information needed to create a new application object, initialize the PBCallInfo structure, and trigger the application object's Open event:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
   WPARAM wParam, LPARAM lParam)
{
  int wmId, wmEvent;
  PAINTSTRUCT ps;
  HDC hdc;

  switch (message) 
  {
    case WM_CREATE:
      {
       // Load the PBVM
       hPBVMInst = ::LoadLibrary("pbvm.dll");
       P_PB_GetVM getvm = (P_PB_GetVM)
           GetProcAddress(hPBVMInst,"PB_GetVM");
       IPB_VM* vm = NULL;
       getvm(&vm);

       // Define the library list and create the session
       static const char *liblist[] = {"openwin.pbl"};
       vm-> CreateSession("openwin", liblist, 1,
          &session);
       break;
      }

    case WM_COMMAND:
       wmId    = LOWORD(wParam); 
       wmEvent = HIWORD(wParam); 
       // Parse the menu selections:
       switch (wmId)
       {
         case ID_PB_VISUAL:
         {
           // Initialize PBCallInfo and trigger the
           // application open event
           try
           { 
               pbgroup group = session->FindGroup
                  ("openwin", pbgroup_application);
               pbclass cls = session->FindClass(group,
                   "openwin");
               pbmethodID mid = session->GetMethodID
                   (cls, "open", PBRT_EVENT, "QS");
               pbobject obj = session->NewObject(cls);

               PBCallInfo ci;
               session->InitCallInfo(cls, mid, &ci);
               session->TriggerEvent(obj, mid, &ci);
               session->FreeCallInfo(&ci);
           }
           catch(...)
           {
              MessageBox(NULL, "Exception occurs",
                 "Exception", MB_OK);
           }
          break;
         }
         default:
            return DefWindowProc(hWnd, message, wParam,
               lParam);
         }
         break;
      case WM_PAINT:
         hdc = BeginPaint(hWnd, &ps);
         RECT rt;
         GetClientRect(hWnd, &rt);
         EndPaint(hWnd, &ps);
         break;
      case WM_DESTROY:
         session->Release();
         session = NULL;
         FreeLibrary(hPBVMInst);
         PostQuitMessage(0);
         break;
      default:
         return DefWindowProc(hWnd, message, wParam,
            lParam);
   }
   return 0;
}

Testing ProcessPBMessage

You can test the ProcessPBMessage function with a simple PowerBuilder application like this one:

  1. Create a PowerBuilder application called openwin in openwin.pbl.

  2. Create a main window, w_main, with three buttons.

  3. Insert a window-level function, of_setcolor, that takes three integers as arguments and has this script:

    this.backcolor = rgb(red,green,blue)
  4. Insert a window-level user event, ue_test, with this script:

    MessageBox("ue_test", "This is a user event")
  5. Provide the following scripts for the clicked events of the buttons:

    //cb_1:
    MessageBox("Button 1", "Clicked")
    parent.of_setcolor(255, 255, 0)
    
    //cb_2:
    MessageBox("Button 2", "Clicked")
    parent.PostEvent("ue_event") 
    // not fired
    parent.of_setcolor(255, 0, 0)
    
    //cb_3:
    MessageBox("Button 3", "Clicked")
    cb_1.PostEvent(Clicked!) // not fired
  6. Script the application's Open event:

    open (w_main)

    When the ProcessPBMessage function is included in the C++ application, the application runs from C++ as it does in PowerBuilder. The posted events in cb_2 and cb_3 are processed.

    Now try commenting out these lines in the C++ application, recompiling, and running the application again:

    if (session)
    session->ProcessPBMessage();

    The message boxes still display (response windows have their own message loop) and the of_setcolor function is called, but the posted events do not fire.

More PBNI possibilities

The ability to create visual, nonvisual, and marshaler extensions, and to call PowerBuilder objects from external C++ applications, opens up numerous opportunities to combine these capabilities to develop more complex applications.

Writing an extension that loads the PBVM

Most of the examples in this book and on the PowerBuilder Code Samples website at https://www.appeon.com/developers/library/code-samples-for-pb show you how to create an extension in C++ and use it in PowerBuilder, or how to write a C++ application that loads the PowerBuilder VM.

You could also write an extension that loads the PowerBuilder VM and uses a custom class user object, using the techniques described in this chapter. The following figure depicts the interaction between the PBVM and an external application that uses an extension.

Figure: Interaction between PBNI, the PBVM, and external applications

Calling PowerBuilder from Java

You can combine the ability to call PowerBuilder classes from C++, as described in this chapter, with the ability to create marshaler extensions, as described in Creating Marshaler Extensions to call PowerBuilder from Java.

One way to do this is to create a Java proxy class that declares static native methods that can be called to load the PBVM, create PowerBuilder sessions, create PowerBuilder objects, and invoke PowerScript functions. These native methods can call into the PBVM through PBNI. Additional Java classes that represent the PBVM, PowerBuilder sessions, and PowerBuilder objects can be based on the proxy class.

The Java classes call the Java native methods through JNI, whereas the Java native methods call PowerBuilder through PBNI.

There is a sample that illustrates these techniques on the PowerBuilder Code Samples website at https://www.appeon.com/developers/library/code-samples-for-pb.