Helping Developers Master PowerBuilder Classic and .NET

Yakov Werde

Subscribe to Yakov Werde: eMailAlertsEmail Alerts
Get Yakov Werde: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Enterprise Architecture, Enterprise Application Performance

Article

Building a Snap-In App Framework Using Dynamic PowerBuilder Assemblies

Part 1: Implement dynamic PBD loading and content access using readily available and mature APIs in PowerBuilder Classic

The articles in this two-part series examine and contrast PowerBuilder .NET 12.5.1's new dynamic assembly feature with corresponding dynamic library functionality in PowerBuilder Classic. The discourse is presented in the context of a simplified yet practical use case. The first article presents the use case, reviews pertinent PowerBuilder Classic dynamic APIs, and presents a Classic PBD implementation. The second article introduces PowerBuilder .NET 12.5.1's Dynamic Assembly feature, reveals relevant PowerBuilder .NET generated assembly internals, and presents a PowerBuilder .NET use case implementation.

Introduction
Say you were on a programming team designing a PowerBuilder .NET WPF based system that included this requirement:

  • The application must be extensible through third-party add-ons .

How would you go about: (1) Designing a third-party component API to support compliant add-in construction; (2) designing application plug-in interface to assure API compliance? (3) implementing a dynamic plug-in loader in your main application?

Through reading the articles in this series, you'll develop answers to these questions. Along the way you'll examine some new and old PowerBuilder APIs along with PowerBuilder and Microsoft .NET features and internals.

Related, Required Language Features
While a discussion of programming language theory and an exposé of PowerScript language capabilities is well beyond this article's scope, it's important for you to have working definitions of the concepts and APIs that underpin the discussion. Here are the necessary working definitions:

Dynamic Binding
Grady Booch defines object-oriented data typing as: ‘the enforcement of the class as an object, such that objects of different types may not be interchanged, or at the most, they may be interchanged in very restricted ways'

PowerScript was originally designed as a strongly typed object-oriented language in which predefined types were static (or early) bound at compile time. However, polymorphism, based on a set of well-known system class types was supported, as well as polymorphic Open( ) function overloads. PowerScript acquired additional dynamic characteristics in version 5 with the addition of the dynamic keyword. Runtime (or late) binding is used when a developer precedes a method call with the dynamic keyword. Late binding opened PowerScript's door to lots of there-to-fore impossible tricks. Please note, because the compiler doesn't evaluate or optimize a dynamic call, a runtime exception is thrown if the method is not present when called. Here's a dynamic call that might be placed in a menu script.

ParentWindow. Dynamic Event ue_save( this.istr_window_state)

The datatype of the system defined identifier, ParentWindow, is Window. Because Window does not have a predefined ue_save ( structure ) event, the call statement cannot compile. Placing the dynamic keyword before the method name in the call forces the statement to be runtime evaluated. As long as the actual window reference does have such an event at runtime, the statement will execute correctly.

Dynamic Polymorphic Instantiation
As mentioned above, several PowerScript functions and statements support dynamic polymorphic instantiation. In every case, you supply a reference variable of the appropriate ancestor type as the first parameter value and specify the name of the descendant type to create as the contents of a string for the second parameter. The reference variable is populated with the object address after the create.

For non-visual types (or even creating visual types non-visually):

<<PowerObject type identifier >> = CREATE USING <<String containing type to create>>

For window types:

Open( <<window type identifier>>, <<String containing type to create>>)

String contains the class name of the type to create. Type must be a Window descendant.

For graphic types:

OpenUserObject(<<DragObject type identifier>>, <<String containing type to create>>)

Reflection
Reflection is defined as the process by which a computer program can observe and modify its own structure and behavior at runtime. Modern programming languages including PowerBuilder provide a Reflection API.

PowerBuilder provides classes that implement a read-only reflection API. API members include ClassDefinition, ScriptDefinition, and VariableDefinition. The grand ancestor of all PowerScript classes, the PowerObject, has a ClassDefinition property that contains a reference to an object's ClassDefinition type object. Code in any PowerObject descendant need make only a single assignment to a reference variable of type ClassDefinition to reflect on a class.

ClassDefinition lcd_1
Lcd_1 = this.ClassDefinition

Figure 1 shows the Object Browser's view of ClassDefinition's properties.

Figure 1: ClassDefinition properties

Library API
PowerBuilder Classic provides functions that allow a developer to (1) manipulate the library list at runtime; (2) create and delete PBL repository files; and (3) get a detailed listing of the contents of a runtime (PBD) or development time (PBL) repository. Table 1 lists all of Classic's library functions.

Table 1: Library Functions

AddToLibraryList

Adds files to the library search path of the application

GetLibraryList

Gets the files in the library search path of the application

LibraryCreate

Create a library and associates comments to the new library

LibraryDelete

Delete a library

LibraryDirectory

Return a list containing all the objects of a specified type

LibraryDirectoryEx

Return a list containing all the objects in a library

LibraryExport

Export objects from a specified library

LibraryImport

Import objects into a specified library

SetLibraryList

Change the files in the library search path of the application

PowerBuilder Classic offers three functions that allow you to manipulate the library list (PBD) at runtime. They are: AddToLibraryList( ), SetLibraryList( ), and GetLibraryList( ). As their names suggest, AddTo appends PBDs to the existing library list. Set replaces the current list and Get returns a string with the current library list. Adding a PBD to the library list makes its contents available to the application. Your code can dynamically instantiate objects and create DataWindow objects using definitions stored in the PBD. However, because types are unknown at compile time, it's not possible to open them using their class name as types as references. You will have to use a dynamic open call as illustrated above. Code objects referenced inside dynamically loaded types, including DataWindows, will be automatically loaded.

When the contents of a loaded PBD are unknown, you can call the LibraryDirectoryEx function to get a full or partial listing of a PBD's contents. This function returns the directory list as a tab delimited string. You can create an external datasource DataWindow Object whose buffer structure maps onto the elements in the string LibraryDirectoryEx returns. Then Import( ) the string into the DataWindow to interact with its contents.

Putting It All Together
Figure 3 shows our little POC application's UI. Clicking the Load button allows you to select a dynamic library and load a directory of its contents into a DataWindow. Figure 4 shows what happens when you select an object and click the Create button. As expected it polymorphically instantiates an object using the corresponding PowerScript function. Because the LibraryDirectory function reports the selected object's type, it's not necessary to reflect on the selected object's class definition to determine which Open function to call.

Figure 3: POC application UI

Figure 4: Select an object and click the Create button

Listing 1 shows the code in the Load button's clicked event.

Listing 1: Load button clicked event script
integer rc
string lfile, lpath, lobjects
//get the name of the assembly to add to the list
rc = GetFileOpenName ('PB Dynamic Libraries', lpath, lfile, "PBD", "PB Dynamic Libraries (*.pbd),*.pbd")
if rc <> 1 then return
rc = AddToLibraryList(lpath)                         //add to app reference list
choose case rc
case -1
MessageBox( 'Wake Up!!', "Can't test from inside the IDE")
return
case -2
MessageBox( 'Something went wrong@!', &
'The new library list or existing library list is empty, or another internal error has occurred')
return
end choose
//load directory into dwo
lobjects = librarydirectoryex( lfile, dirall!)
dw_1.importstring(lobjects)
dw_1.sort( )
dw_1.setrow(1)
dw_1.setfocus( )

Listing 2 shows the code in the Create button's clicked event.

Listing 2: Create button clicked event script
string ls_objectname, ls_type
window lw
userobject luo
ls_objectname=dw_1.getitemstring(dw_1.getrow( ), 'object_name')
ls_type = dw_1.getitemstring(dw_1.getrow( ), 'object_type')
choose case lower(ls_type)
case 'window'
open(lw, ls_objectname)
case 'userobject'
openuserobject(luo, ls_objectname, 10, 770)
end choose

Implementing the Use Case Spec
We can now extend our basic dynamic loader to implement the use case spec. Figure 5 shows the complete architecture of my POC. You can see that the application provider has common abstract "framework' ancestors that implementation classes inherit from. These ancestors form a common, mandatory API that the application's loader framework will call to dynamically interact with loaded classes. The application provider supplies these ancestors in PBD format to the snap-in developer. The snap-in developer adds the PBD to the library list of their Add-In application and inherits from the ancestors to build their snap-in implementation classes. Obviously, the Provider's application has the same ancestors in one of its libraries. The ancestors provide a unique runtime ‘fingerprint' that the Provider can verify before loading the objects. Figure 5 shows both application targets in the System Tree view: the dynamic loader and the Snap-In (or Add-in). Listing 3 shows the Create button implementation including runtime identification and dynamic loading.

Figure 5: System Tree view

Listing 3: Create button implementation
string ls_objectname, ls_type
PowerObject PO                     //these are for reflection
ClassDefinition CD                // "        "    "
w_ancestor lw_a  //common abstract ancestor for load-able windows
u_cvuo_anc luo                       //common abstract ancestor for load-able CVUOs
ls_objectname=dw_1.getitemstring(dw_1.getrow( ), 'object_name') //get selected class system type & class name
ls_type = dw_1.getitemstring(dw_1.getrow( ), 'object_type')
if lower(ls_type) <> 'datawindow' then         //reflect to determine ancestor
po = Create using ls_objectname
cd = po.classdefinition
cd = cd.ancestor ; end if
choose case lower(ls_type)
case 'window' //is the window from expected ancestor?
if cd.name = 'w_ancestor' then
open(lw_a, ls_objectname) ;else; messagebox("Can't Open Window. Wrong Ancestor", cd.name) ; end if
case 'userobject'
if cd.name = 'u_cvuo_anc' then
openuserobject(luo, ls_objectname, 10, 770) ; else ; messagebox("Can't Open CVUO. Wrong Ancestor", cd.name) ; end if
case 'datawindow'
dw_1.dataobject = ls_objectname
dw_1.settransobject( sqlca)
dw_1.retrieve( )
end choose
destroy CD     //clean up
destroy PO

Conclusion
In this article you read how to implement dynamic PBD loading and content access using readily available and mature APIs in PowerBuilder Classic. You also saw how to implement a basic snap-in interface and dynamic loader that you can use to allow third-party extensions to your application

In the next article I'll introduce you to the new PowerBuilder 12.5.1 .NET Dynamic Library API and the .NET Reflection API. You'll learn to use them together with some of the Classic APIs you explored in this article to implement a PowerBuilder .NET snap-in interface for your application.

References

  1. http://www.devx.com/dotnet/Article/31578/1954
  2. http://www.programmersheaven.com/2/Dot-Net-Reflection-Part-1

More Stories By Yakov Werde

Yakov Werde, a 25 year IT industry veteran, is a member of TeamSybase and the newly formed Sybase Customer Evangelist Team. Yakov is a recognized author, speaker and trainer who has been designing and delivering PowerBuilder, .NET, EaServer, Web App Development, and Java training for over 14 years to corporate, military and government developers. Prior to discovering his aptitude as an educator, Yakov worked as an architect, project manager and application coder in the trenches of application software development. Yakov holds a Masters in Education with a specialty in instructional design for online learning from Capella University and a BS in math and computer science from Florida International University. Yakov, managing partner of eLearnIT LLC (www.elearnitonline.com), authors and delivers workshops and web based eLearning tutorials to guide professional developers toward PowerBuilder Classic and .NET mastery. Follow Yakov on Twitter as @eLearnPB

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.