Part of the allure of the .NET platform is that all code, no matter what language or grammar it's written in, compiles down to Common Intermediate Language (CIL) that is managed and executed by the Common Language Runtime (CLR). PowerScript code goes through a two-step process before becoming CIL. First it's run through PBCS.exe, which converts PowerScript to the equivalent C#. Then the emitted CS code is compiled by the C# compiler, CS.EXE, into standard CIL.
One of the tools that ships with the .NET SDK is a CIL disassembler ILDASM.exe. The tool allows you to explore the emitted assemblies and examine their CIL instructions and metadata.
I decided to build a simple C# application into an assembly and explore its CIL and reproduce it in PowerScript to compare compiler outputs and see what I could learn.
Listing 1 shows the source code for the simple C# calculator application. It's emitted as an assembly named calc.exe. Listing 2 shows the CIL for the
add( ) method. Note the two int32 arguments and that the generated code size is 9 lines long. (The listings for this article can be downloaded here.)
The PowerBuilder code is all built into the application object. The Open event calls the add method that sums the value of the two parameters. Note that I passed the values as integers.
Figure 1 shows the PowerScript add method inside the application object, and Listing 3 shows the CIL generated from it. There are a couple of eye-openers here. First, look at the size this one line of code generates: a whopping 23 lines of code; the routine is more than 2.5 times bigger than its C# equivalent. Second, look at the parameter and return types: they are not int32 - they're Sybase PowerBuilder.PBInt value objects.
If you look carefully at the CIL, you can see that there is a lot of data-type conversion going on. First, the two integer parameters are converted to longs, then the sum is converted from long back to integer. Why? The answer is in how PowerBuilder does arithmetic versus how C# does arithmetic. This excerpt from the PowerBuilder online help "Arithmetic Operators" explains what you're seeing: "Integers are promoted to longs in calculations, wrapping does not occur until the result is explicitly assigned to an integer variable." The extra code generated by the PBCS.exe process guarantees 100% compatibility with Classic code. The generated code is imitating integer data type promotion to long before the operation and then converting the results back to an integer.
To streamline my PowerScript code, I altered the add method return and parameter types to long. Listing 4 shows the resulting CIL. Note that the code is now only 8 instructions long, one smaller than the C# code.
What I take away from this exercise is that those looking for best performance from critical PowerScript routines in the .NET environment would do well to look at how their code looks in CIL and take steps to make it as efficient as possible based on the core PowerScript rules.
Hiding from the Outlaws
As I went through the ILDASM exercise, it rapidly dawned on me that any code generated by PowerBuilder .NET, like CIL code generated from any Common Language Specification (CLS) compatible language, is totally open to disassembly by anyone with access to widely available tools. Any proprietary algorithms and processes would be plainly visible and easily decoded after deployment. What is now needed is a way to hide code from curious eyes. The good news is that because PowerScript .NET is CIL compatible, there are many standards-based and widely used obfuscation (code concealing) tools to choose from. My fellow TeamSybase member, Dave Fish, recommended that I give Crypto Obfuscator for .NET from LogicNP Software a try. See http://www.ssware.com/cryptoobfuscator/features.htm for a complete discussion of the product's capabilities. Crypto Obfuscator has a command-line interface that can be directly called from the PowerBuilder IDE after a successful full build via an entry on the target's project Post Build tab.
For the purpose of my test, I used a more substantial and typical PowerBuilder client/server application. The application had lots of DataWindows, business logic and data access logic. Being an obfuscation newbie, I used the tools' UI to select my options. Figure 2 shows CryptoObfuscator v2010's UI with the options I chose. The obfuscated code is emitted in a CryptoObfuscator_Output folder under the applications bin\release folder. After making a backup of my original code, I copied the obfuscated EXE assembly into the release folder. Figure 3 show what occurred when I attempted to disassemble the obfuscated code. Most important, the application ran without a perceived flaw. The runtime was able to correctly execute the obfuscated code. One important note: DataWindow Objects are load image optimized and packed into an assembly named <yourapp>.resources.dll. My attempt to obfuscate this assembly caused some of the DataWindows to fail. Upon closer examination, not obfuscating the resource DLL doesn't seem to be a security risk since the DataWindow object source packed inside it isn't visible from within the disassembler. However, since code security is an important issue, more study using a wider range of tools and approaches is necessary before drawing conclusions about obfuscation best practices.
From all the above I you can now understand how gaining insight into the CIL code generated from your PowerBuilder .NET application can help you fine-tune your algorithms to achieve a more efficient runtime performance. You should also be aware of why code obfuscation is a must to help you combat security risks that can arise as part of your application distribution.