beta it republik » Articles

Articles

Untitled Document
Friday, 23 May 2008 | Article

Winbinder for Fun and Fortune

Are you looking for a way to use the PHP code and the knowledge you’ve built up over the years to create a clientside Windows based application or game? This article introduces you to Winbinder, the first native Windows toolkit for PHP, and provides concrete examples for how you can use PHP to build everything from a simple five-line example script to a full-fledged commercial application.


Introduction
In a little over ten years since its debut in the web development space, PHP has rightfully earned its spot as the most widely used platform for web development. With PHP 4.3, the CLI SAPI (Server Application Programming Interface) was added into PHP. When PHP 5 came along the functionality was greatly enhanced, and the SAPI was enabled by default. This put the spotlight on using PHP as a general purpose scripting language in a command line environment, and enabled functionality that was previously not available in PHP.

Programmers employed the Command Line Interface (CLI) not only for traditional PHP uses, but also to push PHP to its limits to develop everything from web/ftp servers, to IRC bots and more. The most outstanding usage of PHP outside its web environment began when people started to experiment with using PHP to power programs with interactive graphical user interfaces. PHP-GTK was the first of these projects, and offered PHP bindings to cross-platform GTK libraries. Then came Winbinder, a unique platform that surpassed the functionality of other GUI bindings by not just providing the bindings, but also a full platform for GUI development.

Why Winbinder?
Winbinder was written by Rubem Pechansky, a software developer in Brazil, who was looking for a way to create rapid prototypes of development projects that he was creating for his clients. The platform he envisioned required a few basic features that would enable him to both create prototypes quickly and port the resulting products to other languages.

His vision was not just to have a GUI binding, but also to create an entire development framework. A base platform was required to create this GUI development framework. This platform was envisioned to be simple, C-like, interpreted, procedural (not limited to object-oriented programming), lightweight, database friendly, and cost efficient.

PHP was an obvious choice, now only because it met the basic criterion, but also because it offered other benefits, such as the huge library of functions available both in the PHP core libraries and in PECL. Further, PHP has a broad existing user base, many of whom are potential users and collaborators. It also has a fairly small footprint. Additionally, thanks to the work that has been done on the CLI SAPI, PHP is fairly easy to bundle for stand-alone installations. With PHP you can also leverage the object-oriented (OO) functionality to create OO wrappers for procedural code – this enabled the creation of the OO Winbinder library.

Along the way many existing frameworks were tested. While the frameworks were all high quality, none fulfilled all of the requirements, and so Winbinder was born.

Winbinder is a Windows only solution. (While Rubem has raised the option to focus on a cross platform solution, the vast majority who work with Winbinder have preferred that the development remains focused on its core goal – a simple, Windows-based development platform.)

Server Side versus Client Side
One comment I hear often, when talking about Winbinder and general client side GUI development, is that, “I can do that with a web-based interface using (insert buzzword technology of the month here).”

While it is tempting to take a look at the rich interfaces that Flex/ Laszlo offer, or even attempt to create a homegrown application (using JavaScript, Ajax, Comet, XUL, XAML, or some such), there are specific benefits to creating client side applications.
  • Latency: it is a problem with all web-based applications. No matter how fast an Internet connection you have, a quick response time is not always guaranteed. Latency alone can make Ajax-based applications difficult, if not impossible, to work with.
  • Offline Use: for many companies it is important to be able to access information while offline. While we are seeing more and more applications being ported to the Web, such as Microsoft’s Windows Live initiative, many projects aren’t suited for online usage.
  • File Access / IO / Networking Limitations: due to the security issues involved with public web sites, web browsers have implemented strict restrictions as to what client-side JavaScript is able to do. This makes simple tasks like accessing data, which has been saved on a local machine, tedious at best. Accessing local or networked resources on a Wide Area Network (WAN), for example, are also subjected to browserimposedrestrictions. Using a client side application allows the users to interface directly with their local (or networked) filesystem, and access the power of the Windows and thirdparty APIs.
  • Affordance: those who work in Windows are used to the way Windows-based applications feel. This is referred to as affordance. You put a Windows DataGrid in front of a user, and they will understand how it works. They will know that by clicking the header of a column it will sort the ‘datagrid’ by that column. But put the exact same functionality into a web-based datagrid, and the user may be at a loss.

When it comes to visual design, people are comfortable with what they are familiar with.

How Winbinder Works
Winbinder is implemented as a native PHP extension (DLL) and an accompanying library of code that provides an abstraction layer to the Winbinder library as well as a database abstraction layer and additional functionality.

The Winbinder library is split into two parts (see Figure 1). The PHP layer works with the Zend Engine to provide functionality to the Winbinder PHP scripts. The API layer works directly with the Windows API as well as the APIs of any third-party DLLs you call.


Fig 1: The Winbinder Library


This abstraction between PHP and the Windows API was put into place for several reasons. Firstly, it makes the design more modular. If the project decides to support non-Windows platforms, it could theoretically replace the Windows API with something based on WXWidgets. Similarly, if the platform needs to be ported to another scripting language, only the PHP bindings will need to be re-implemented.

Another reason for this abstraction is that it allowed large parts of the PHP interface of Winbinder to be implemented in userland PHP code. This allowed for fast development of the library. It also enabled seamless integration of other features, such as the database abstraction layer and other useful PHP functions. As development continues on Winbinder, much of the userland PHP code will be integrated directly into the Winbinder library.


Table 1: Winbinder Library Files and Functionality



The Winbinder Library
The Winbinder library is the userland PHP code required for Winbinder scripts to run. During my development work with Winbinder, I’ve found myself referring back to the Winbinder PHP libraries. To give you a bit more in-depth understanding of the Winbinder library I’ve put together a list of the most important files and the functionality they provide in Table 1.

Event Based Programming
Unlike standard PHP scripts, which just respond to HTTP requests that are sent to them with POST/GET variable, a Winbinder application has to maintain a state and respond to events triggered by controls. There are several ways by which events can be triggered. The most obvious example would be a user clicking on a button, or a timer control triggering an event at a specified time or interval. An event is triggered when a user closes a window or clicks on a menu item. If parent control (a window, for example) is in the correct mode, other events such as mouse clicks, double clicks, or mouse movements can also trigger events.

To handle events that are continually being triggered by user interaction, Winbinder uses a procedural callback system which is incorporated in a main loop and triggered on every event. The loop function is assigned to a window using the wb_set_handler() function. You will see this in almost every Winbinder script. wb_set_handler() binds a function to a window and lets the window know that any events triggered will be handled by the specific function.

When an event is triggered, the function specified with the wb_set_handler() statement is sent the name of the window, the unique identifier of the control that triggered the event, and some other data. It is then up to the specified function to handle the event properly.

This approach to creating an event driven system makes it easy to create simple scripts quickly. However, you need to use caution when planning larger projects and ensure that your scripts do not become unmanageable.

Note that there is an OO version of the Winbinder libraries that handle the event triggering in an OO manner. Since this library is only for PHP 5 and has not been updated, it doesn’t benefit from some of the most recent additions to Winbinder. I will
focus on the procedural code in this article.

Using Winbinder: Error Handling
Before jumping into the code, let’s talk a bit about error handling. The first thing many first-time users of Winbinder discover is that if the program code generates a fatal error when loading, then nothing happens — the program simply fails to run. This is because Winbinder (by default ) uses the php-win executable to execute its scripts.

php-win is identical to the CLI version (php-cli in PHP4 and php in PHP 5) except it doesn’t display a console ‘dos box’. Therefore the errors and any other text generated by your script will not be displayed.

Since the debugging process is greatly aided by the error messages that PHP sends, the first task is to change the php.ini file to log all errors to a file. This can be done with the following code:

display_errors = On
log_errors = On
error_log = php_errors.txt
error_reporting = E_ALL & ~E_NOTICE


Due to the fact that the Winbinder library itself generates E_NOTICE warnings, it’s necessary to turn notices off.

Our First Code Example
So now that I’ve whet your appetite, let’s jump into Winbinder code with a ‘Hello World’ example. Listing 1 includes an example of a typical Winbinder script.


Listing 1
1 include “inc/winbinder.php”;
2 define(BUTTON, 101);
3 $mainwin = wb_create_window(NULL, AppWindow, “Button
Example”, 220, 90);
4
5 wb_create_control($mainwin, PushButton,
6 “Push Me”, 70, 20, 80, 22, BUTTON);
7 wb_set_handler($mainwin, “process_main”);
8 wb_main_loop();
9 function process_main($window, $event_id) {
10 switch($event_id) {
11 case BUTTON:
12 wb_message_box($window, “You Pushed Me!”);
13 break;
14 case IDCLOSE:
15 wb_destroy_window($window);
16 break;
17 }
18 }


In Line 1 I include the Winbinder PHP library. In most cases you’ll have the Winbinder PHP library in the system path, so that all scripts can use the same library. However, if you plan to package and distribute your application, you will need to bundle the library with your code.

In Line 2, I define a constant that holds an arbitrary number which will be used to identify the control. You will need to have separate identification constants defined for each control you make. So it is a good idea to keep track of this in a predetermined place. If you assign the same number to a different control your script will not work correctly.

The wb_create_window function creates the main window. The first parameter specifies the parent window. Next, I define the type of window (AppWindow), the Title Text, and the dimensions.

Now that we have our window, we can create the button control. The wb_create_control function has parameters similar to wb_create_window. We will look at these functions in depth later in the article. I assign the control to the $mainwin parent window. Further, after setting the button text and size/position parameters, I assign the BUTTON constant as this control’s identifier.

Finally we see the guts of the event callback mechanism as described in the previous chapter. First I use wb_set_handler() to define the callback function for the specified window, and then use wb_main_loop() to start the main loop and the application.

In this example, the callback function is simply a big switch statement that determines which control triggered the callback (based on the parameter sent to the callback function) and handles the request accordingly.

The IDCLOSE constant is triggered when the window is closed, and allows you to handle it gracefully.

Creating Windows
Winbinder provides a simple way to create any of the window types available in Windows. The window styles and their names are included in Table 2.

The example shown in Listing 2 expands on the code provided in the preceding example.

Listing 2
$mainwin = wb_create_window(NULL,
ResizableWindow,
“Button Example”,
WBC_CENTER,
WBC_CENTER,
410, 400,
WBC_NOTIFY, WBC_RESIZE);



Table 2: Window Styles and Dialog Boxes


Creating Controls
Use the wb_create_control() function to create a control in Winbinder. This function is defined in the wb_windows_inc.php file in the Winbinder library. The parameters are fairly straightforward; excepting a few all controls use the same parameters:

wb_create_control($parent, // Parent Class
$ctrl, // Control Name
$param, // Parameter, Label
$xpos, // X position
$ypos, // Y position
$width, // Control Width
$height, // Control Height
$event, // Event this Control Triggers
$style, // Style parameter
$param2, // Additional Parameters
$tab // Tab position );


The third parameter is important. In the first example I used this parameter to specify the text of the label on the button I created. The Accel (keyboard shortcut) control sends an array as this parameter and lists various other options, as shown in the following example:

$parameters = array(array(IDCLOSE, “lt+X”));
wb_create_control($parent, Accel, $parameters);


The $parameters variable contains an array of which events are triggered by which keystrokes. In this example, the IDCLOSE event is triggered when the user uses the Alt+X keystroke combination. In the same way, when creating a menu for our application, create a menu control and then pass the data to the control using the third parameter, like so:

$parameters = array(&Exit,
array(IDCLOSE, “E&xit\tAlt+X”,
“”,””,”Alt+X);
wb_create_control($parent, Menu, $parameters);


Now we have a menu bar with an exit menu item that triggers the IDCLOSE event when it is clicked.

Since we are talking about menu items, let’s take a look at the second-to-last parameter sent to the wb_create_control() function – this is a space for additional parameters. In our case, I will use it to specify a bitmap file that Winbinder should use as icons for the menu items.

wb_create_control($parent, ToolBar, $parameters, 0, 0, 16, 15, 0, 0, “toolbar.bmp”);

Here, we ignore the positioning parameters. We specify that the icons will be 16×15px and then point to toolbar.bmp as the location for the icon images. (The Winbinder download includes a good example of this function in action.)

Creating More Controls and Building your User Interface
Winbinder allows for creating all standard Windows controls. It includes a lot of code examples that provide clear specifics on how to create instances of the controls you want for the user interface. So it is not necessary to go into the details of each control in this section.

In this section we’ll talk about an even easier way to create the user interface. Since one of the reasons for the creation of Winbinder was to create a platform for rapid application development, the need for a graphical form/dialog builder was apparent. Instead of putting the time and energy into programming a form builder, which is not a trivial task, the ability to parse standard Windows Resource files was created. This functionality enables forms and dialogs built with thirdparty tools to be incorporated into a Winbinder project, taking away the need for you to manually create each form.

Some complex examples of this type of form building can be found in the examples, but I will detail the basics here. At the time of writing, the only supported form builder was that included in the WinASM program. Once you have built your form, save the RC file into your Winbinder project directory and parse it using the parse_rc() function. This will return the RC file as a PHP code.

The following code snippet is useful in the initial development stages:

eval(parse_rc(file_get_contents(“form.rc”),
‘$mainwin’, null, ‘PopupWindow’));


This will enable you to save your form as form.rc. So when the Winbinder application is loaded it will read the form and display it. Once you get farther into the development process, however, you will want to save time by converting them to PHP code and saving them to a file to be included (instead of compiling your RC files on the fly). Although this saves time on the program execution, it means that you have to take an additional step each time you save the RC file.

Getting Closer to the Code
Now that we’ve gone over the basic usage and syntax of Winbinder let’s look at a simplified version of one of the samples distributed along with the Winbinder package.

This code displays how to create a window, add custom fonts, draw on the window, and then redraw when the window is resized. We also see some examples of other functions that Winbinder provides, such as accessing system resources. We’ll step through the code piece by piece. See Listing 3a.

We’ve seen a lot of this code already. What we haven’t seen yet is the additional parameters that are used in the wb_create_window() function. The first three parameters specify the parent window, the window type, and the window title. The next four parameters set the window’s size and positioning, remember that the WBC_CENTER constant is used to place the window in the center of the viewing screen.

The eighth parameter is the ‘style’ parameter, which sets style flags that are applied to the window. In this case we’re setting the following flags:
  • WBC_INVISIBLE: do not draw the window
  • WBC_NOTIFY: trigger an Event on Double Clicks and Window Resizing
  • WBC_CUSTOMDRAW: this window will be drawn using a custom function

Finally we give this window the handle ‘WBC_REDRAW’ that will be used to trigger an event when the window is resized and needs to be redrawn. After setting the window handler, the window is set to ‘visible’, so that the event is sent to the main loop and draws the window. If we were to not set the window as ‘invisible’ using the WBC_INVISIBLE constant, the window will only be drawn when the user resizes the window. See Listing 3b.

This example also uses first and second parameter fields, which contain some additional data we need to work with. The first parameter will contain the specific flag the event triggered. Since the window is set to WBC_NOTIFY it could be any of the Notification member flags such as WBC_DBLCLIC, WBC_MOUSEMOVE, WBC_HEADERSEL. So I used the bitwise & operator to ensure the event is a WBC_REDRAW, and then proceeded to redraw the screen.

After setting a few variables, you will notice we use the wb_get_size() command to get the size of the current window. This command returns all the information that we need in an array, which we can use later on.

Finally we start the drawing with a rectangle that stretches across the entire window with the specified background color. See Listing 3c.

Next on the list is to draw some lines that will create a grid. This is done using the wb_draw_line() function. The offsets are calculated based on the data supplied by wb_get_size(). See Listing 3d.

After the grid is drawn, we create some text. Here are two examples of creating text. They are exactly the same, except they are slightly offset to provide the shadow effect. After we’ve used the fonts we can safely use the wb_destroy_font() function to remove the previously created font resources.

We clean up things, and we’re done! See Listing 3e.

Listing 3a
1 include “inc/winbinder.php”;
2
3 $mw = wb_create_window(NULL, ResizableWindow, “Custom
Resizeable Window Example”, WBC_CENTER, WBC_CENTER,
589, 468, WBC_INVISIBLE|WBC_NOTIFY|WBC_CUSTOMDRAW,
WBC_REDRAW);
4
5 wb_set_handler($mw, “p_main”);
6 wb_set_visible($mw, true);
7 wb_main_loop();


Listing 3b
1 function p_main($win, $id, $ctrl, $p1=0, $p2=0)
2 {
3 switch($id) {
4
5 case IDDEFAULT:
6
7 if($p1 & WBC_REDRAW) {
$xoffset = 20;
$yoffset = 20;
$buffer = $p2;
$dim = wb_get_size($win, true);
$winwidth =$dim[0];
$winheight = $dim[1];
8
9 wb_draw_rect($buffer, 0, 0,
$winwidth, $
winheight,
0x838F80);


Listing 3c
1 for($y = $yoffset + 0;
2 $y < $winheight - 20; $y += 20){
3
4 wb_draw_line($buffer,
$xoffset + 0, $y,
$winwidth - 20, $y,
0xE8D8D8);
5 }
6 for($x = $xoffset + 0;
$x < $winwidth - 20; $x += 20) {
7
8 wb_draw_line($buffer,
$x, $yoffset,
$x, $winheight – 20,
0xE8D8D8);
9 }


Listing 3d
1 $font = wb_create_font(“Arial”, 25,
BLACK, FTA_BOLD);
2 wb_draw_text($buffer, “Example Text”,
$winwidth - 188, 24, $font);
3
4 $font = wb_create_font(“Arial”, 25,
WHITE, FTA_BOLD);
5 wb_draw_text($buffer, “Example Text”,
$winwidth - 190, 22, $font);
6
7 wb_destroy_font();


Listing 3e
1 }
2 break;
3
4 case IDCLOSE:
wb_destroy_window($window);
5 break;
6 }
7 }


The 32 Cards Case Study: The Graphic Interface
I opted to use only the Windows GDI instead of DirectX because the project scope didn’t require the display of very fast images or sprite animation. Therefore it is much more straightforward to use WinBinder to output data to the screen because its graphical functions access the GDI directly.

The game interface is composed of separate graphical elements:
  • The main screen
  • Main screen buttons and controls
  • Individual cards including backgrounds and foregrounds,

rendered text, flag, stars and photos (or maps) The main screen and buttons are rather straightforward because they are pre-rendered images that are already included with the game. The cards, though, are a different story.

Since the cards have many elements in common and the game is multilingual, pre-rendered cards were not an option. (4 card sets of 32 cards in 10 languages equals 1280 card variants.)


Fig 3: 32 Cards Interface


Each card is around 150 kB in PNG format, so the game would include a whopping 200 MB just for the cards! The solution was to render each card separately in real time after the installation. Once the card is rendered (which usually takes less than a second) it is stored in a cache directory, because it is much faster to load an image from the disk (it normally goes unnoticed) than to render it in real time.

I had to use two additional libraries just for graphics: GD which does the heavy work of image composition and text rendering, and FreeImage which does the conversion between formats. The reason for converting between formats is because GD does not support the BMP format (which is required for GDI). That is why you will see images in three formats if you browse the installation directory – BMP (to access the GDI directly), PNG (cards generated by GD and other images for compositing) and JPG (for the photographs and maps).

As for the game engine, as it is usual with Windows (and other graphical OSs, for that matter), a main loop takes care of most tasks. The main window processing function (process_main) is activated if the user does anything, such as clicking a button, pressing a key, or moving the mouse around. Other events, like a request for drawing the screen and timer ticks, are also sent to this routine. The first parameter of this function receives the window handle. The route of action is then decided according to the second parameter, which is the ID of the calling control (if any), timer, or zero (IDDEFAULT) if the message comes from the window itself. See the code for more details.

The 32 Cards Case Study: The Game Engine
The game is centred around a finite state machine that processes all actions. Depending on the user input, comparison results and other actions, the machine will change state and perform the necessary tasks for the game to work. The implementation is done inside function update_game_status() which contains a switch statement that contains all states. See the (simplified) code to check out the overall structure of this implementation.

The variable $wb->gamestatus always holds the current state and is used by the main loop to reflect these states in a manner that will be useful and understandable by the user. For example, if the user clicks the left card, the next state (and therefore the subsequent action) is dependent of the current state. So if $wb->gamestatus is equal to $player_turn, it means that the player selected an attribute and the game must continue. So the next state will be continue. If the game status is end, and the user clicked the left card, it means “please start a new game”. So the next state is start. Look at the code for more details.

The 32 Cards Case Study: Localization
The project was born as a multi-language project for obvious reasons. There are two things that need to be done to generate a new language package for 32 Cards (apart from documentation).
  • All texts, including dialog boxes, tooltips, messages and messages that appear on the interface ‘tickers’ are assembled together in a single data file. This takes the form of an INI file that is interpreted in real time when the application is started by auxiliary function parse_ini(), which is done in PHP (not in the DLL) and is included with WinBinder.
  • Buttons (Start, Options, Help, Exit, and such) that appear in the main screen are actually bitmaps that must be pre-rendered in advance. So the first section of the data file just lists these strings as a reference to render those images.

The 32 Cards Case Study: Bundling and Deployment
The executable goes through the following steps after testing and debugging:
  • Compiled using phc-win. This is a necessary step for source code protection.
  • The standard phc-win icon is replaced by the application’s custom icon using ResHacker for cosmetic purposes.
  • Executable is compressed using UPX, which provides a second layer of protection.
  • Protected with Armadillo, which adds a strong ‘protection shell’ around the program, that provides a powerful defence against cracking and also takes care of the registration part. The e-commerce provider, RegNow, has a key generator for Armadillo (and SoftwarePassport, which is the newer version) that allows the user to receive the key by e-mail immediately after the online purchase. Then he/she types the key into the ‘Register’ dialog box, which in turn sends the data to the Armadillo DLL (bundled with the program) that allows the unblocking of the registered code. In this case, it activates the random generation of games, unlocks the remaining user options (like removing the black background and customizing the difficulty level) and removes the reminder screens, among several other things.



The following steps are undertaken for the final distribution:
  • Data files are scrambled and encrypted using a simple homegrown algorithm. Since the goal is only to prevent them from being tampered with, strong encryption is not required.
  • The ‘Help’ file is compiled from the HTML sources and generated using the MS HTML Help Workshop. Instead of using a special HTML Help generator to generate the pages, I use a run-of-the-mill HTML editor. (In my case, it is Namo WebEditor 5, which is more stable than the newer and bloated Namo WebEditor 2006, which I regret purchasing). Then I use a custom script (available in the WinBinder distribution, file \phpcode\make_release\make_release.phpw) to convert the ‘normal’ HTML files to HTML help format. Among other things, this script changes the [title] tags and, most importantly, replaces the custom tag [!—KW …—] with the complex and rather unreadable [object] tag required by the HTML Help compiler.
  • Several files are copied and updated to a special directory, the cache is emptied, and so on, in preparation for the final step.
  • Finally the files are assembled in a single executable setup using Inno Setupand zipped using Info-Zip to generate the file that will be uploaded to the shareware directories. Inno Setup already provides multi-language support by including appropriate third-party language files that are available at the Inno web site.

Note: All the utilities are either open source or freeware.

These steps are automated using a custom WinBinder script that benefits from the fact that all utilities except phc-win (which is in the early stages) have command-line options that run in console mode. So I had to use AutoHotkey, an excellent macro utility that is also command-line driven, to simulate some keypresses to automate the compilation step.

These final steps are not simple and they cost me between three and four week’s work to make everything work together and deploy the final package. Creating a full application with WinBinder is new stuff!


About the Author

Aaron Wormus is a freelance developer, consultant and writer. With over eight years
in IT development work under his belt, he balances the importance of choosing the right
tool for a job with the enjoyment of pushing each tool he uses to their limits. Aaron has
been using and promoting PHP since the early versions of PHP4, or more specifically since the foreach construct was added.

Rubem Pechansky runs his own company, Hypervisual, and draws on over 20 years of experience in graphic design and programming to manage a team of developers who create interfaces and programming for high-end development projects. Rubem contributed Winbinder to the PHP project in 2004 and has been actively developing the package and supporting its users ever since. Rubem Penchasky lives with his wife and children in sunny Porto Alegre, Brazil.


   Related Links
http://pecl.php.net/
http://winbinder.org/index.php
http://www.swiftlytilting.com/category/phc-win/
http://www.angusj.com/resourcehacker/
http://upx.sourceforge.net/
http://www.siliconrealms.com/
http://www.microsoft.com/downloads/details.aspx?familyid=00535334-c8a6-452f-9aa0-d597d16580cc&displaylang=en
http://www.jrsoftware.org/isinfo.php
http://www.info-zip.org/
http://www.autohotkey.com/
http://www.winbinder.org
http://www.jrsoftware.org/isinfo.php
http://www.32cards.com
http://www.phpinfo.com


Comment

Name:

Comment:

Captcha Verification !
captcha_image