There are all kinds of pies, chicken pot pie, shepherd's pie, cherry pie and of course good ol' American apple pie. Every host or hostess knows that pies are perfect circles. There are infinite ways to divide and serve them. They can be cut in halves, thirds, quarters, eighths, sixteenths or any combination thereof. If a pie is small, you might even choose to serve each guest a whole pie. It's up to you to determine which size pieces are appropriate for your guests.
In a sense, configuring a .NET application for deployment is a lot like dividing a pie. You can deploy the entire application as a whole pie (a single EXE) or you can carve it up into a set of smaller related assemblies. How you divide the application into separate deployment pieces is really your choice.
In this article, I'll take a look the new project painter feature included in the March 21.1 EBF release that gives you control over how you architect your deployment. I'll give you the lowdown on why this feature is important to you and show you how to work with the configuration tools to achieve your desired result.
We all know that many PowerBuilder line-of-business applications are large. In fact, some are outright humongous. Source code for an application can range into hundreds of megabytes. Size notwithstanding, in Classic Win32, a developer can make a small change, click the Runner icon, and immediately launch and test the application without incurring a compilation phase time penalty. This development methodology is one of the great features listed on PowerBuilder's glorious calling card. This methodology is supported by a PBL structure that contains a compiled version of each code object in addition to a source code version. Testing is always quick and easy.
Additionally, as a Classic developer, you have your choice of deployment architectures. You can build all your code into a single EXE or choose a combination of EXE with a set of related PBDs or DLLs. In addition to minimizing build times, choosing a PBD/DLL scheme gives you flexibility in how you deployed your changes.
The .NET Difference
.NET is a different platform. Instant Run functionality does not exist in .NET. Binary code is no longer stored along with source code in the PBLX. Every change, no matter how small, requires a compilation phase before testing. Even though you can (and will) kick off an incremental compile to minimize compile time, by default all code is built into a single large EXE. With a large application, the compilation phase can be a time-consuming process that can severely impact developer productivity. (How about, change a single line of code, start the compile, drive to the coffee shop and back before it's done?)
Another potentially fatal impact of compiling into a single EXE occurs when compiling very large applications. The compiler can run out of memory and ABEND! Being a good 32-bit citizen PowerBuilder WPF .NET and its friend, the MicroSoft C# compiler, natively only support 2 gigs of virtual RAM. Even after making the .NET Framework executable large address aware, it is possible for the compiler to run out of memory when processing a large application. (See PowerBuilder 12.0 Doc section > PowerBuilder .NET Features Guide > About PowerBuilder .NET > "Memory Tuning for Large Applications". For more background, search the Internet for ".NET Framework large address aware".) The only available option for these large applications with 12.0 GA and 12.1 was to manually carve them up into separately deployable DLLs. However, manual partitioning is a painstaking, time-consuming process; one I presume is not a palatable approach for most shops.
The March 2011 EBF is really a base functionality release. It enhances core functionality in three major areas. One of these areas is application deployment. In the balance of this article I'll focus on this functionality. You will discover how to use the tool and technology to overcome deployment issues that were impeding .NET migration and perhaps even be able to restore RAD development to its rightful position in PowerBuilder.NET deployment.
Assembly Configuration Tools
In this section I'll describe some new tools that you'll use to configure your deployment architecture:
- The code dependency checker: Its job is to build an assembly reference tree. It checks for and reports on circular references. In Figure 1 you can see that this tool is accessible from the Design menu when the Project Painter is open, and in Figure 2 you can see it on the Project object context menu in the Solution Explorer.
Figure 1: Check Dependencies Menu Item
Figure 2: Project Object Context Menu
- The assembly mapping view: Use the edit fields on this Project Painter view (shown in Figure 3) to designate your PBLx to assembly mapping choices. The view was added as a tab only within the project painter. Your choices are stored in the project file (srj) file in XML format. (shown in Figure 4).
Figure 3: Assembly Mapping View
Figure 4: Mappings in the srj Project file
- Dependencies Display Tab: A separate tab (shown in Figure 5) within the Project Painter hosts a treeview that is populated after dependencies have been checked. Assemblies are listed in compile dependency order.
Figure 5: Dependencies Display Tab
- Dependency View: Available anytime from the Viewà Other Windows menu. Shown in Figure 6, it hosts treeview listing assembly dependencies in Library List order. However, it will only be populated after either you open the project painter (even if you subsequently closed it), or you checked dependencies from the project painter object context menu. Note that the Dependency Checker caches its results in a file called object_dependency.xml in the .out folder. If the file does not exist and you start a build, Check Dependencies is automatically invoked by the build process.
Figure 6: Assembly Dependencies View
What It Takes to Implement It
Configuring assemblies is not an automatic process. You have to determine inter-assembly dependencies, determine if any circular dependencies exist, reorganize your code object storage scheme to eliminate them, and determine where you will store your global types.
What you have to know before assigning PBLs to assemblies:
- Your application code. You need to know what is where, what depends on what, what inherits from what and what references what. You may need to relocate code objects into new or different PBLs and you'll have to do that intelligently. Don't fret if you are not sure about dependencies. There is a tool that will help you discover them. But, the more you know upfront, the quicker the configuration process will be.
- The global data your application uses. For sure, the five global system service types - transaction (SQLCA), dynamicstagingarea (SQLSA), dynamicdescriptionarea (SQLDA), message and error - are involved. Have you changed the type of any of the global services to a custom type? If so, where is that type located? Do you have other specialized application global types that are defined in your PBLs?
- Which of your code objects are most likely to be modified? To minimize incremental build time during maintenance activities, you'll want to isolate code objects under change into separate assemblies.
Oh Those Dependencies!
A dependency between assemblies is formed by a reference. Both inheritance relationships (w_sheet FROM w_anc) and variable type declarations (w_anc Lw_anc) create dependencies. You must arrange your code objects to avoid circular dependencies between assemblies. Table 1 explains which references are legal and which are illegal.
Table 1: Legal & Illegal References
What About Those Globals?
If you choose to create an assembly from your global variables, it will be located at the end of the assembly tree. This allows every assembly and the EXE assembly to be dependent on it. However, there can't be circular dependencies between the globals assembly and the assemblies that depend on it, i.e., a global type can't be in its own dll if it inherits from something that is defined in another assembly. To solve this, specify that your globals be put into the assembly they are dependent on. For example, n_tr in pfc is a global type that depends on
pfc_n_tr. Put the globals into the same PBL as the PFC (see below for a complete PFC solution)
If you don't choose to create an assembly from your globals, then each assembly will have its own set of globals, i.e., each assembly will need its own DB Connection and other global resources. Not an intended outcome, for sure.
What About Resources?
There are two categories of application resources: internal and external. DataWindow Objects (and Query objects) are termed internal resources. Images (and other items) whose build action is set to embedded resource on their property sheet (see figure 7) are termed external resources.
Figure 7: Image Property Sheet
Figure 8: Create resource-only assembly for executable
Checking the "Create resource-only assembly for executable" check box on the project painter assemblies tab (see Figure 8) will cause all internal resources stored in PBLs slated to go into the EXE (i.e., DataWindow Objects) to be built into a separate .resource DLL. In the 12.0 time frame when the only build option was single EXE, this option was an important time and resource saver. Unchecking this check box will cause the internal resources to be included inside the EXE.
External resources are handled differently. Items with BuildAction=EmbeddedResource are built to every output assembly - including the exe, but not into the resource-only assembly. There is no mapping tool you can use to direct a specific item into a single specific assembly.
Figure 9 shows the difference in size for a small app with a single DWO and a couple of embedded resource images when the resource-only assembly is enabled and disabled.
Figure 9: Size difference with/without resource assembly
Understanding the Dependency Display
The dependency display window (shown in Figure 10) contains a help key that explains how to read the dependency chart.
Figure 10: Dependency Display Window
Figure 11 shows the full IDE with views explaining what it means.
Figure 11: Full IDE View
The WIP_RAD.exe assembly depends on the Base.dll assembly. The Base.dll assembly contains the Base PBL. The Base PBL contains the
w_window, which is referenced by the application object
WIP_RAD open event.
Base.DLL depends on globals.DLL. Globals.DLL contains the
WIP_RAD PBL (yes it is in two places, the global part in one assembly and the code part in another assembly). Lines of code in the constructor event of
n_customnonvisual reference objects in globals.dll. In this case they are assignment statements to properties of SQLCA.