Delivering PHP-GTK to the desktop


1Introduction to the Issues

Last November in Frankfurt I gave a talk about the issues that php-gtk faced, one of the parts that struck an interest, was the possibility of building a setup.exe file for a php-gtk application, that behaved like most other windows applications. In this introduction I'm going to briefly recap some of that information.

1.1Why not just put PHP on the desktop?

Unlike PHP applications which are delivered to a web server, where the source code can be protected by restricting access to the server login. Desktop applications rarely make available the source code to the end user, there are a number of fundamental reasons for this.

So one of the key aims for a fully useful desktop application programming language is be the ability to create self contained binaries (exe's) and libraries (.dll's)

1.2What Windows users expect

While us good old unix developers are well used to configure and make, windows users have been brought up on setup.exe, the next,next,next method of installation. Since remote install is not generally feasible in a windows enviroment, this setup.exe is the only real solution.

Luckily Inno Setup1 makes this possible, and later I will go into the details of getting this working.

2Building the tools

This is more for reference, if you download the example exe application, it already includes all the components built in this section. So if you just want to try and build a php-gtk exe file, you could skip this section. However, it does help to understand how the php-gtk.exe system was created.



Most of the tools available for this are available in the standard PHP distribution, or PECL (the PHP extension C Library). It's just a matter of downloading (or building these)

Although I'm not going to go into detail of the installation of these, as it is covered in more depth in the PHP2 manual – below is an overview of the build setup procedure.



2.1Bcompiler

Bcompiler is available from PECL, in php's CVS3

bcompiler includes a couple of test programs to check that it is working (in the examples subdirectory), you can test that all is working by running the compiler tool .

C:\work\pear\PECL\bcompiler\examples>php.exe bcompiler_compiler.php C:\php4\pear\pear.php

Will generate a file C:\php4\pear\pear.php.phb

you can now check that it's compiled correctly by

C:\work\pear\PECL\bcompiler\examples>php.exe
bcompiler_read.php pear\pear.php.phb

This should list the available classes after loading the file. And should include pear and pear_error.

2.2Php embedded

Making php embed is very simple, in visual C++ open the file
C:\php4\sapi\embed\php4embed.dsw

just select active build and build all.

2.3Making the base phpe.exe



The base embedder is contained in the bcompiler distribution under examples/embed



You will probably have to modify your environment space in DOS to enable it to work.

Just go to the dos window properties and change the command line to say

C:\windows\command.com /e:4000

Now set up the Visual C++ variables (your location may vary)

C:\Program Files\Microsoft Visual Studio\VC98\Bin\Vcvars32.bat

the Makefile for the embedder is very simple:

#use with namake -f Makefile produces pembed.exe
# Put your compiled php source here
ROOT=C:\work\php4

LIBS=php4ts.lib php4embed.lib
INCLUDES=-I "$(ROOT)" -I "$(ROOT)\main" -I "$(ROOT)\Zend" -I
"$(ROOT)\TSRM" -I "$(ROOT)\sapi\embed"
LIBDIRS=/libpath:"$(ROOT)\Release_TS"
CC=cl
LD=link
CFLAGS=-MD -D ZTS -D PHP_WIN32 -D ZEND_WIN32

phpe.exe: phpe.obj
$(LD) $(LIBDIRS) /out: phpe.exe $(LIBS) phpe.obj
phpe.obj: phpe.c
$(CC) $(CFLAGS) $(INCLUDES) -c phpe.c
clean:
-del *.obj
-del phpe.exe

In the example above, the resulting file is phpe.exe, and the input file would be phpe.c which looks like this.

#include <php_embed.h>

int main(int argc, char **argv) {
char *my_filename = NULL;

/* the code to run */
char *code = "
$s =((PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '') .
'bcompiler.' .PHP_SHLIB_SUFFIX; "
"dl($s); "
"if (!extension_loaded('bcompiler')) {
echo 'bcompiler not loaded - dll/so should be
in same directory as exe';
exit;
}"
"bcompiler_load_exe($_SERVER['argv'][0]);"
"if (!class_exists('main')) {
echo 'main class does not exist';
exit;
}";
"main::main();";



the above code is simply the php code needed to fire up a program containing a method main and class main.



/* set up the embeded enviroment */
PHP_EMBED_START_BLOCK(argc,argv);

/* set up extension directory to match file */
my_filename = strdup(argv[0]);
php_dirname(my_filename, strlen(my_filename));
zend_alter_ini_entry("extension_dir", 14,
my_filename, strlen(my_filename), PHP_INI_ALL,
PHP_INI_STAGE_ACTIVATE);


/* run the code */
zend_eval_string(code, NULL, argv[0] TSRMLS_CC);
PHP_EMBED_END_BLOCK();
return 0;
}

now try making the file

C:\work\pear\PECL\bcompiler\examples\embed>nmake.exe -f Makefile.win

to test this, you need to copy these files into the embed directory



just running this should result in.

C:\work\pear\PECL\bcompiler\examples\embed>phpe.exe
main class does not exist



3Creating your application

Creating applications is a process of

3.1Restrictions of bcompiler

Bcompiler started life as a way to compile classes for an embedded device where protecting the code was a requirement. (it is based on some of the code in the APC cache). It has grown since into a tool to compile functions, classes and defines.

This means however that you have to arrange your code to suit compiling, as primarily raw, global code is not supported.

Bcompiler it'self only provides the API for writing and loading single classes, functions. However, normally an application is made up of multiple classes and files. In order to simplify the compilation of a collection of files, there is a script called bcompiler_compile.php in the bcompiler examples folder. This script currently has a few restrictions that you have to be aware of ..

The bcompiler script does not handle.

In the examples in the wrapper class illustrate how some of these are handled.

3.2The wrapper class main::main()

For the exe compiler, mentioned above, it is essential to create a class call main with at least one method main. This class should also include require_once calls for all classes that are conditionally included elsewhere in the code.

A conditionally included file is often one included by a class's factory method., this is an example from pear's DB class.

function &factory($type)
{
@include_once("DB/${type}.php");

$classname = "DB_${type}";
if (!class_exists($classname)) {
return PEAR::raiseError(null, DB_ERROR_NOT_FOUND,
null, null, null, 'DB_Error', true);
}
@$obj =& new $classname;
return $obj;
}

For this to work with bcompiler, you should add require_once lines like this to the wrapper file.

require_once 'DB/mysql.php';
require_once 'DB/pgsql.php';
require_once 'DB/oci8.php';

This will tell the compiler script to include these into the bytecode file, so that when the include line is ignored, these classes will be available anyway.



In the example program that we are demonstrating, the gtk mdb designer from pear, one of the things that is included is the pear.php class. - this notably has conditional defines for windows and unix settings. - to ensure that your class gets compiled correctly you must define the setting correctly in the wrapper.



From the original pear class:

if (substr(PHP_OS, 0, 3) == 'WIN') {
define('OS_WINDOWS', true);
define('OS_UNIX', false);
define('PEAR_OS', 'Windows');
} else {
define('OS_WINDOWS', false);
define('OS_UNIX', true);
define('PEAR_OS', 'Unix'); // blatant assumption
}

in our wrapper/bootstrap class we should add

define('OS_WINDOWS', true);
define('OS_UNIX', false);
define('PEAR_OS', 'Windows');

the compiler script will use these values rather than the ones defined conditionally by the later included files.


3.3Compiling the application

In the example I've put together, I'm using Gtk_MDB_Designer, this application at present runs from a php script and is installed using the pear installer.

To get it to build, you need to have



The best way to start the build process is to create a directory specifically for the package. In this directory, you can put



assuming you have built this in C:\work\embedtest, to build the binary do

C:\work\embedtest>c:\php4\php.exe bcompiler_compile.php gtkmdbdesigner phpe.exe

where you are running bcompiler_compile.php with

This will create a file gtkmdbdesigner.exe which is the 'runnable binary', you can rename this file to suit your requirements.





3.4Testing and issues.



Before actually running the binary that you have created it is a good idea to test it using plain php (as error reporting is turned off in the binary. - and you dont get an idea of what could have gone wrong)

This little script enables you to test the binary in PHP.

<?php
dl('php_bcompiler.dll');
bcompiler_load_exe($_SERVER['argv'][1]);
main::main();
?>

just run it by doing

C:\work\embedtest>c:\php4\php.exe -c C:\work\embedtest
test_exe.php gtkmdbdesigner.exe

Note the setting of the the ini file location with -c (you might also consider moving php.exe into the test directory and renaming your C:\php4 directory to ensure that you are testing on something similar to a 'virgin system'.



You will begin to see some of the issues that the real binary will encounter



The first thing to fix is the Dll's – these can just be placed in the same folder as the script.



For Glade files and XPM's you have to work out how they now relate to the current path. In Gtk_MDB_Designer, it uses

$this->glade = new GladeXML(dirname(__FILE__).'/Designer/Designer.glade');

The complier replaces this with

$this->glade = new GladeXML($_SERVER['argv'][0].'/Designer/Designer.glade');

as __FILE__ would have been stored as the original location of the file, not where you eventually execute the file from.



So in this case a subdirectory of embedtest called Designer is where the file should be located. Similarly with the xpm's you must work out exactly where the program expects the files to be (usually by printing and error if they cant be found.)



Note at present, due to the way php-gtk works, you need to have a php.ini file to load things like the glade module. (this is not necessary if you do not use glade)



4The installer

The installer that I've chosen is Inno setup from jrsoftware.org, It's free and reasonably easy to use.



4.1Building a configuration file



Inno-setup includes a very simple wizard that you can set up very quickly to produce the application. (in fact it only took me 2 efforts to make a working installer!). This is an example of the file that is created by the wizard, (with a few modifications)



[Setup]
AppName=Gtk MDB Designer
AppVerName=Gtk MDB Designer 0.2
AppPublisher=Alan Knowles
AppPublisherURL=http://pear.php.net
AppSupportURL=http://pear.php.net
AppUpdatesURL=http://pear.php.net
DefaultDirName={pf}\Gtk MDB Designer
DefaultGroupName=Gtk MDB Designer


The setup section is pretty simple, just setting the titles

[Tasks]
Name: "desktopicon"; Description: "Create a &desktop icon";
GroupDescription: "Additional icons:"

Tasks, just sets what needs to be done (other than copy files), in our case – just create a icon on the desktop.

[Files]
Source: "C:\work\embedtest\gtkmdbdesigner.exe"; DestDir: "{app}"; Flags: ignoreversion

The main file to run

Source: "C:\work\embedtest\iconv.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libgdk-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libglade-0.1.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libglib-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libgmodule-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libgobject-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libgthread-2.0-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libgtk-0.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libintl-1.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\libxml2.dll"; DestDir: "{app}"; Flags: ignoreversion

These files provide the gtk widgets

Source: "C:\work\embedtest\php.ini"; DestDir: "{app}"; Flags: ignoreversion

If you use glade or scintilla, you will have to include a php.ini file.

Source: "C:\work\embedtest\php_bcompiler.dll"; DestDir: "{app}"; Flags: ignoreversion

Source: "C:\work\embedtest\php_gtk.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\work\embedtest\php4ts.dll"; DestDir: "{app}"; Flags: ignoreversion


the php embedded library, along with php gtk and bcompiler extension.

Source: "C:\work\embedtest\Designer\*.*"; DestDir: "{app}\Designer"; Flags: ignoreversion

In the case of Gtk_MDB_Designer, I copied the glade file into the Designer folder.

Source: "C:\work\embedtest\MessageBox\*.*"; DestDir: "{app}\MessageBox"; Flags: ignoreversion

Again, the Message Box icons and glade file are in this folder.

Source: "C:\work\embedtest\php_gtk_combobutton.dll"; DestDir: "C:\php4\"; Flags: ignoreversion
Source: "C:\work\embedtest\php_gtk_libglade.dll"; DestDir: "C:\php4\"; Flags: ignoreversion
Source: "C:\work\embedtest\php_gtk_sqpane.dll"; DestDir: "C:\php4\"; Flags: ignoreversion

The last section of files is the php-gtk extended libraries, unfortunately these don't work unless they are in the C:\php4 directory at present.



[Icons]
Name: "{group}\Gtk MDB Designer"; Filename: "{app}\gtkmdbdesigner.exe"
Name: "{userdesktop}\Gtk MDB Designer"; Filename: "{app}\gtkmdbdesigner.exe"; Tasks: desktopicon

The icons section indicates what menu items and desktop icons should be used.

[Run]
Filename: "{app}\gtkmdbdesigner.exe"; Description: "Launch Gtk MDB Designer"; Flags: nowait postinstall skipifsilent

The last bit is the actual executable that will be installed into the menu.

4.2Testing it

After you run the build option in the inno-setup tool, a file in the Output directory of the working folder (where you saved the inno-setup iss file) will contain the setup.exe, so lets see what happens when we run it.










Spash Page




Select where to install it.








Create a folder in the start menu.




Desktop Icon?






Look over what you selected










Install those files








All done – start the program?






And there it is – php-gtk on the desktop.

4.3TODO..

There are a few enhancements that could be examined








Abb. 1: Bildunterschrift

1http://www.jrsoftware.org/isinfo.php

2Http://www.php.net/manual/en/install.windows.php

3http://cvs.php.net/cvs.php/pear/PECL/bcompiler


16