|
Delivering PHP-GTK to the desktop |
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.
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.
Security
allowing an inexperienced user to modify code .
Allowing virus's to modify code
Making available scripting languages, in an insecure environment.
Licensing
Desktop Applications are more frequently licensed than server based applications
Number of dependent files to run an application
Desktop applications frequently consist of single binaries plus libraries, a PHP application normally would consist of a large number of class files plus the PHP interpreter.
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)
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.
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.
Get hold of Visual C++ (the primary build tool used for PHP on windows)
down load Cygwin (www.cygwin.com) – unix tools for windows (generally also useful for the command line history with up/down arrows)
get win32build.zip and bindlib_w32 (as detailed in the php manual)
configure VC+
build resolv.lib
put the php distribution into C:\work\php4\ (rename it from the distribution directory)
build php4ts (release)
build php4ts_cli (release)
copy the resulting (C:\work\php4\Release_ts\php4ts.dll files and php.exe into C:\php4)
Bcompiler is available from PECL, in php's CVS3
download this to C:\work\pear\PECL\bcompiler
load up the dsw file, set the active configuration to Release (Build->set Active Configuration) and run build All
this will put the resulting dll in C:\work\pear\Release_ts\php_bcompiler.dll
copy this file to C:\php4\
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.
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.
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
libphp4ts.dll (from C:\work\php4\Release_TS)
the php_bcompiler.dll (from C:\work\pear\Release_TS)
just running this should result in.
C:\work\pear\PECL\bcompiler\examples\embed>phpe.exe
main
class does not exist
Creating applications is a process of
create a bootstrap file (the 'main' class)
work out which classes are required
building the bcompiler binary
writing the inno-setup file
putting it all together.
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.
conditional class or file inclusion (eg. include 'DB/Pgsql..php';) inside methods
conditional defines (eg. Define() within an if () {} statement)
functions (are not currently supported – but would be easy to add.)
Almost any global code – except defines, fixed includes and requires.
In the examples in the wrapper class illustrate how some of these are handled.
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.
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
php4
pear (including pear.php)
MDB
** all the mdb files
Gtk
MDB (including Designer.php)
Designer
** and all the files
XML
Parser
The best way to start the build process is to create a directory specifically for the package. In this directory, you can put
bcompiler_compile.php (from bcompiler/examples)
your_base_script (the bootstrap script as discussed previously, with main:main())
php4ts.dll (the php4 dll)
php.ini containing the settings for php-gtk.extensions
php_bcompiler.dll (the bcompiler dll)
phpe.exe – the wrapper file
All the PHP-GTK dll's (both php ones and system dll's)
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
gtkmdbdesigner – the script to build (its the php file with the class and method main::main() defined).
phpe.exe – the wrapper script.
This will create a file gtkmdbdesigner.exe which is the 'runnable binary', you can rename this file to suit your requirements.
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
Missing Dll's
Missing Glade files
Missing XPMs (the icons)
Make sure you have a php.ini
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)
The installer that I've chosen is Inno setup from jrsoftware.org, It's free and reasonably easy to use.
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.
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.
There are a few enhancements that could be examined
Removal of ini file requirement – probably by building php-gtk and setting the default value for php-gtk.extensions
Change the default location for php-gtk extensions (like glade)
wrap the code in a windows main routine, so the dos window does not get run in the background.

Abb. 1: Bildunterschrift
2Http://www.php.net/manual/en/install.windows.php
3http://cvs.php.net/cvs.php/pear/PECL/bcompiler
|
|
|