Decoupling Runtime and IDE

Why decoupling Runtime and IDE

The PowerBuilder runtime files are now separated from the PowerBuilder IDE, as an independent component "PowerBuilder Runtime".

The decoupling of Runtime and IDE makes it possible to install and upgrade IDE and Runtime separately, and one IDE can work with multiple runtime versions without needing to upgrade IDE. For example, in the previous versions, when there was a new release, especially a release with new features or bug fixes on the runtime, if the developer wants to test it, s/he will have to configure a new environment or back up the existing environment before installing the new release. And now that Runtime is separated as an independent component, multiple runtime versions can be installed on the same machine, developers can switch to the new runtime version with only one click in the IDE, and if any issue is found, s/he can switch back to the original runtime in seconds.

What changes made for the decoupling

The following changes have been made to decouple Runtime and IDE:

  • The runtime files (including .dll, .ini, .pbx, .pbd etc.) are renamed so that the version number indicator (such as "170", "190") that used to be appended to the file name is removed, for example, pbvm190.dll is renamed as pbvm.dll, pbdom190.pbd is renamed as pbdom.pbd.

    If your application uses pbwsclient.pbd/pbwsclient.pbx, pbdom.pbd/pbdom.pbx, pbsoapclient.pbd/pbsoapclient.pbx, or pbejbclient.pbd/pbejbclient.pbx, make sure to update the file name and path accordingly.

  • All runtime DLLs that used to be installed to the "Shared" folder are now separately installed to the "Runtime [version]" folder and the "IDE" folder instead, in order to physically separate Runtime files from IDE files.

    The "Runtime [version]" folder contains the libraries required at runtime. Example location: C:\Program Files (x86)\Appeon\Common\PowerBuilder\Runtime 19.2.0.2556.

    The "IDE" folder contains the support files required by IDE. Example location: C:\Program Files (x86)\Appeon\PowerBuilder 19.0\IDE.

    If any external program has called the runtime file, you may need to change the code accordingly in order for the program to find the dependent DLLs successfully (view code examples).

  • The runtime file location is no longer recorded in the system PATH environment variable; it is recorded in the system registry instead.

    The 32-bit apps in 32-bit OS search for this registry: HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\PowerBuilder Runtime

    The 32-bit apps in 64-bit OS search for this registry: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Sybase\PowerBuilder Runtime

    The 64-bit apps in 64-bit OS search for this registry: HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\PowerBuilder Runtime

  • The PowerBuilder IDE (and the application executable) can select which version of runtime files will be used, so it enables developers to easily maintain multiple projects. The IDE can compile for multiple runtime versions (both minor and major) through the System Options. See the section called “Selecting a version of PowerBuilder Runtime” in Application Techniques for detailed instructions.

Installing and switching runtime

"PowerBuilder Runtime" is always installed to %systemdrive%\Program Files (x86)\Appeon\Common\PowerBuilder\Runtime [version]. You can install multiple versions of "PowerBuilder Runtime" and select a compatible one in the PowerBuilder IDE. By default, the latest compatible PowerBuilder Runtime will be used by the PowerBuilder IDE. You can easily switch the versions of runtime files for the PowerBuilder IDE and the application executable in seconds.

To install PowerBuilder Runtime, see the section called “Installing PowerBuilder Runtime” in Application Techniques for instructions.

To select which PowerBuilder Runtime will be loaded by the PowerBuilder IDE and which one will be loaded by the native C/S application, see the section called “Selecting a version of PowerBuilder Runtime” in Application Techniques for instructions.

The PowerBuilder IDE can work with PowerBuilder Runtime at the same or earlier versions, for example:

IDE (including MRs)

Supported Runtime (including MRs)

Unsupported Runtime

2019 R3

2019 R3

2021 or later

2021

2019 R3, 2021

2022 or later

2022

2019 R3, 2021, 2022

2022 R2 or later

2022 R2

2019 R3, 2021, 2022, 2022 R2

2022 R3 or later

2022 R3

2019 R3, 2021, 2022, 2022 R2, 2022 R3

2023 or later


Related features

The following changes have been made in accordance to the decoupling of runtime and IDE.

  • A new property RuntimePath is added to the Environment object to get the runtime path and version used by the current application executable.

  • A system function GetInstalledRuntimes is added to get the version number of runtimes that are installed on the current computer.

  • A system option "Show prompt for full-building a target if it is opened after PowerBuilder Runtime change" is added -- Every time after you changed the version of PowerBuilder Runtime for the current IDE and restarted IDE, you will be prompted to full build the application. In most cases, it is recommended to full build your application every time after the runtime version is changed, unless your application is large and takes a long time to full build, then you can clear this check-box and manually full build the application only when there is a major version change for PowerBuilder Runtime. And once you performed a full build in the IDE, the new runtime version will be recorded in "appruntimeversion" which is an internal property used by the IDE only. (Note creating an exe from PBC, OrcaScript or IDE will not update the version number in the "appruntimeversion" property.)

  • When using OrcaScript commands, you can use the runtime_version argument to specify the version of PowerBuilder Runtime that will be used to compile the application executable, for example, OrcaScr190 /D runtime_version="19.2.0.2558" C:\test\test.bat. For more, refer to the section called “OrcaScript Commands” in Users Guide.

  • When using PowerBuilder Compiler (PBC), you can use the /rt argument (for example, /rt 19.2.0.2558) to specify the version of PowerBuilder Runtime that will be used to compile the application executable. And PowerBuilder Compiler must be installed after PowerBuilder Runtime is installed. For more, refer to the section called “Appendix B. PowerBuilder Compiler” in Users Guide.

  • The MSI/MSM file generated by the PowerBuilder Runtime Packager 2019 R3 tool has been enhanced, so that runtime files of different builds at the same major version (starting from 2019 R3 GA) can be installed and coexisting on the same computer, for example, 2019 R3 and 2019 R2 can coexist, multiple 2019 R3 MRs can coexist. And the MSI file no longer sets the runtime file path in the system PATH environment variable; therefore, the user will need to decide which build of runtime files will be loaded by the application executable file and place the application executable and the runtime files in the same folder.

Code examples

Example 1:

For example, to load the runtime DLL file PBORC190.dll from where PowerBuilder IDE is installed, you write code differently between 2019 R3 and 2019 R2.

Also note that the file name PBORC190.dll in 2019 R2 has been changed to PBORC.dll in 2019 R3.

Original code in 2019 R2:

hORCALibrary = AfxLoadLibrary("PBORC190.dll"))

Modified code in 2019 R3:

This code example hard-codes the path of IDE and runtime, if you want to read the directory from the registry, use the next example.

//First, defines the AddEnvironmentPath method
BOOL AddEnvironmentPath(LPCTSTR lpAddPath)
{
   TCHAR szSysPath[1024 * 8] = { 0 };
   TCHAR szNewPath[1024 * 8] = { 0 };

   if(0 == GetEnvironmentVariable(_T("path"), szSysPath, 1024 * 8))
      return FALSE;

   _tcscpy_s(szNewPath, lpAddPath);
   _tcscat_s(szNewPath, _T(";"));
   _tcscat_s(szNewPath, szSysPath);

   if(0 == SetEnvironmentVariable(_T("path"), szNewPath))
      return FALSE;

   return TRUE;
}

//Adds the path of runtime file before loading the runtime DLL
//The path needs to be updated when PB is upgraded
AddEnvironmentPath(_T("C:\\Program Files (x86)\\Appeon\\Common\\PowerBuilder\\Runtime 19.2.0.2566"));
AddEnvironmentPath(_T("C:\\Program Files (x86)\\Appeon\\PowerBuilder 19.0\\IDE\\"));
(hORCALibrary = AfxLoadLibrary("PBORC.dll")

Or you can write code like this in 2019 R3 which reads the path of IDE and runtime from the registry instead of the hard-coded path:

//First, defines the AddEnvironmentPath method
//AddEnvironmentPath(_T("19.2.0.1080"), _T("19"));
//AddEnvironmentPath(_T("19.2.0.1080"), NULL);
BOOL AddEnvironmentPath(LPCTSTR lpRuntimeVersion, LPCTSTR lpIDEShortVersion)
{
    LONG lRet = 0;
    HKEY hkReg = NULL;
    TCHAR   szIDEPath[MAX_PATH] = { 0 };
    TCHAR   szRTPath[MAX_PATH] = { 0 };
    TCHAR   szIDEName[MAX_PATH] = { 0 };
    TCHAR   szTmpRTPath[MAX_PATH * 2] = { 0 };
    TCHAR   szTmpIDEPath[MAX_PATH * 2] = { 0 };
    TCHAR   szSysPath[1024 * 8] = { 0 };
    TCHAR   szNewPath[1024 * 8] = { 0 };
    TCHAR   szRegPath[MAX_PATH] = { 0 };
    DWORD   cbLen = MAX_PATH * sizeof(TCHAR);
    DWORD   dwType = REG_SZ;

    //Reads runtime path from registry
    if (NULL == lpRuntimeVersion)
        return FALSE;
    
    wsprintf(szRegPath, _T("%s\\%s"), _T("Software\\Sybase\\PowerBuilder Runtime"), lpRuntimeVersion);
    lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegPath, 0, KEY_QUERY_VALUE, &hkReg);
    if ((ERROR_SUCCESS != lRet) || (NULL == hkReg))
        return FALSE;
    
    lRet = RegQueryValueEx(hkReg, _T("Location"), 0, &dwType, (LPBYTE)szRTPath, &cbLen);
    RegCloseKey(hkReg);

    if (_tcslen(szRTPath) <= 0)
        return FALSE;
    
    wsprintf(szTmpRTPath, _T("%s;%s\\X64;"), szRTPath, szRTPath);

    //Reads IDE path from registry
    if (lpIDEShortVersion && _tcslen(lpIDEShortVersion) > 0)
    {
        hkReg = 0;
        wsprintf(szRegPath, _T("%s\\%s.0"), _T("Software\\Sybase\\PowerBuilder"), lpIDEShortVersion);
        lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegPath, 0, KEY_QUERY_VALUE, &hkReg);
        if ((ERROR_SUCCESS != lRet) || (NULL == hkReg))
            return FALSE;
        
        cbLen = MAX_PATH * sizeof(TCHAR);
        lRet = RegQueryValueEx(hkReg, _T("Location"), 0, &dwType, (LPBYTE)szIDEPath, &cbLen);
        cbLen = MAX_PATH * sizeof(TCHAR);
        lRet = RegQueryValueEx(hkReg, _T("IPS Name"), 0, &dwType, (LPBYTE)szIDEName, &cbLen);
        RegCloseKey(hkReg);

        if (_tcslen(szIDEPath) <= 0 || _tcslen(szIDEName) <= 0)
            return FALSE;
        
        wsprintf(szTmpIDEPath, _T("%s\\%s\\IDE;%s\\%s\\IDE\\X64;"), szIDEPath, szIDEName, szIDEPath, szIDEName);
    }

    if (0 == GetEnvironmentVariable(_T("path"), szSysPath, 1024 * 8))
        return FALSE;

    _tcscpy_s(szNewPath, szTmpRTPath);
    _tcscat_s(szNewPath, szTmpIDEPath);
    _tcscat_s(szNewPath, szSysPath);

    if (0 == SetEnvironmentVariable(_T("path"), szNewPath))
        return FALSE;

    return TRUE;
}

In summary, you need to write code in two steps in version 2019 R3:

Step 1: Define a method that can add a path to the PATH environment variable.

Step 2: Call the method to add the PowerBuilder Runtime and PowerBuilder IDE directories to the PATH environment variable and then load the DLL file.

Note that if the path of IDE or runtime is hard-coded in the scripts, make sure to update the path accordingly if PowerBuilder Runtime and IDE are upgraded to a new version.

Example 2:

If you have source code like below which determines the PowerBuilder versions according to the class name, you will need to change the code accordingly.

Original code in 2019 R2:

Environment le_env

GetEnvironment(le_env)

//Determines position of window within microhelp bar
ii_width = st_time.x + st_time.width + 25
this.width = ii_width

//Sets object class name
choose case le_env.PBMajorRevision
       case 10, 11, 12
              choose case le_env.PBMinorRevision
                     case 5
                           is_classname = "FNHELP" + &
                                  String(le_env.PBMajorRevision) + "5"
                     case 6
                           is_classname = "FNHELP" + &
                                  String(le_env.PBMajorRevision) + "6"
                     case else
                           is_classname = "FNHELP" + String(le_env.PBMajorRevision * 10)
              end choose
       case else
              is_classname = "FNHELP" + String(le_env.PBMajorRevision * 10)
end choose

//Sets parenthood
this.wf_setparent()

Modified code in 2019 R3:

choose case le_env.PBMajorRevision
         case 10, 11, 12
                   choose case le_env.PBMinorRevision
                            case 5
                                     is_classname = "FNHELP" + &
                                               String(le_env.PBMajorRevision) + "5"
                            case 6
                                     is_classname = "FNHELP" + &
                                               String(le_env.PBMajorRevision) + "6"
                            case else
                                     is_classname = "FNHELP" + String(le_env.PBMajorRevision * 10)
                   end choose
         case 17
                   is_classname = "FNHELP" + String(le_env.PBMajorRevision * 10)
         case 19
                   if le_env.PBMinorRevision > 1 then
                            // pb2019 R3 or later:
                            is_classname = "FNHELP"
                   else //pb2019 R2 or earlier:
                            is_classname = "FNHELP" + String(le_env.PBMajorRevision * 10)
                   end if
         case IS > 19
                            is_classname = "FNHELP"                 
end choose
Function long GetClassName ( &
       longptr hWnd, &
       Ref string lpClassName, &
       long nMaxCount &
       ) Library "user32.dll" Alias For "GetClassNameW"
public subroutine wf_setparent ()
String ls_name
LongPtr ll_hWnd
Integer li_rc

//Gets the microhelp handle
ll_hWnd = GetWindow(Handle(gw_frame), 5)
DO UNTIL ll_hWnd = 0
       ls_name = Space(25)
       li_rc = GetClassName(ll_hWnd, ls_name, Len(ls_name))
       If ls_name = is_classname Then
              ll_hWnd = SetParent(Handle(this), ll_hWnd)
              ll_hWnd = 0
       Else
              ll_hWnd = GetWindow(ll_hWnd, 2)
       End If
LOOP