Skip to content

2004

.Net annoyances

I've spent some hacking time this weekend improving the COM/.Net extension for PHP. I am both happy and annoyed with the results. In order to convey why I'm annoyed, you'll need a bit of background.

COM is all about interfaces. PHP uses the OLE Automation interfaces that allow run time binding of classes and methods; the key here is IDispatch. There are also a set of interfaces known as Connection Points that effectively allow an object to call you back, via another object that you supply, to be able to handle events. The guts of Connection Points are hidden away from you in VB via the WithEvents keyword. PHP also supports Connection Points and also hides the details from your script via the com_event_sink() function.

Now, .Net is similar, but not. It has a number of COM-ish concepts and even ships with the "interop" layer that will automagically map COM into .Net and vice versa. PHP uses the interop layer to bind to .Net at runtime; this is cool because it means that PHP doesn't then require the .Net runtime to be installed on the target machine.

As part of my quest for a better offline blogging tool, I thought about using .Net from PHP to get at the System.Windows.Forms classes. It quickly became apparent that there are some shortcomings with the .Net interop stuff. The theory is that .Net will map its classes to IDispatch'able interfaces via CCW's (COM Compatible Wrappers). In practice it does that, but doesn't explicitly implement the interfaces; instead, IDispatch is present implicitly in the System.Object interface. The effect of this is that the PHP stuff breaks when it sees these objects coming back out from COM, because it can't see IDispatch: .Net is being naughty.

The fix for this is simply to import the interface definition for System.Object and explicitly check for that interface in each of the places where we are looking for IDispatch. This is fine, except that it isn't simple. System.Object is defined in the mscorlib assembly. There is no mscorlib.h header file, and there is no mscorlib.idl to generate a C header file either. In order to get at this stuff I had to load up the binary type library file and export it as IDL. I then needed to hand-edit the 20k lines of IDL so that it could be compiled by MIDL (the order of many declarations was wrong). This process resulted in a 3MB mscorlib.h file (115k lines).

Armed with the header, I added support for System.Object and was able to instantiate the System.Windows.Form::Form class from PHP, set its caption and size and see it on-screen. Yay!

The next step was events, because a form that doesn't react to user input is not very useful.

Events in .Net are based on delegates. Delegates is a posh way of saying "callbacks"; your delegate is essentially a structure containing a pointer to a function or method and a pointer to the object to which the method belongs. Hooking up the event is simply a matter of telling the source object (say, a button on a form) where your delegate structure lives.

At the lower level, the procedure for hooking up a 'Click' event for a button is something like this:

  • $type = $button->get_Type();
  • $event = $type->GetEvent('Click');
  • $event->AddHandler($button, new Delegate('my_click_handler'));

With the help of our 3MB header file, we can execute this code. We need to implement the Delegate interface on our own stub, because there is no standard Delegate class that we can set up with a generic C callback function.

And this is where things fall down: .Net doesn't accept our Delegate instance because it is not managed code.

The way to get this stuff to work is to build the PHP com extension as managed code, and re-implement chunks to use the Microsoft specific extensions to C++. This poses 3 problems:

  • Not everyone has a CLR capable compiler, so our build system needs to either detect this or provide a switch for the user to turn it on.
  • /CLR mode is not compatible with the majority of regular CFLAGS we use (like turning on debug info), so it requires some hackery in the build system to even get it to build.
  • Building with the /CLR switch will force a hard dependency on the .Net runtime, so we would need to:
    • make COM a shared extension (php_com_dotnet.dll)
    • build one version with and one version without .Net support (more build hackery)

Big ouch.

A really nice clean solution would appear to be to separate the .Net stuff out in its own extension; that would solve a couple of problems, but introduce others: there are a few places that need to handle .net specific cases, and those introduce linkage dependencies on the .Net extension.

This is fixable, but we're now into the realms of work-exceeds-fun-returns, so I'm probably just going to leave things as they are. Sorry folks.

If you want events in the mean time, all the .Net docs suggest that you should instead make your own wrappers for the objects, using a .Net compatible language, and implement them as Connection Point style interfaces. The interop layer can then perform behind the scenes voodoo for the CCW's, and you should then be able to use com_event_sink() to hook up your events.

VMware on win32 tweaks

My laptop has a dual boot xp/linux install.

For a little while now, I've been running linux 98% of the time, but I've started to miss my music. In the last couple of days I've tried a number of Ogg/MP3 players on linux and have been a bit annoyed by the memory and cpu usage. I suspect a combination of bad audio and firewire drivers as being responsible for most of CPU usage. Anyway, it was making the system unusable, and I need my music.

So, I've switched back to the XP side of things for now, using vmware to get at the linux install (using a native persistent disk to provide access to the real linux partition). One of the things that annoyed me about vmware was that NAT networking sucked; it would cause my wifi to drop out and not come back, sometimes, if I rebooted the virtual machine. I also didn't like the fact that I had to run dhcpd and nat daemons. In a flash of inspiration today, I made the following adjustments to my config:

  • remove (by unchecking the box) the vmware bridge protocol from all of my network connections, including the vmnet connections.
  • for vmnet8 (the default NAT net), disabled NAT and DHCP and set the subnet to 192.168.0.0 in the VMWare "Virtual Network Settings"
  • in the Service MMC console, stop the 3 VMWare services (auth, nat and dhcpd). Set auth and nat to manual startup and disable the dhcpd.
  • edit your virtual machine config: remove your NAT NIC, then add a new NIC; choose custom networking and set it to use vmnet8
  • right click on your internet facing NIC (under "Network Connections") and turn on internet connection sharing; select vmnet8 as the private network to share.

The net effect (pun not intended ;-) is to have windows perform the nat and dhcpd for vmnet8 natively. By disconnecting the VMWare Bridge Protocol, vmware leaves your NICs well alone. I've noticed better stability and less latency using this approach today.

On the linux side, I run RHEL; for vmware I boot into run level 3 instead of the usual 5. I've turned off most services in run level 3: acpid, bluetooth, autofs, sshd and all that stuff, leaving just syslog, gpm, vmware-guestd and a couple of mingetty and tweaked rc.local to stop hotplug and unload additional modules, all to reduce the memory footprint of the vm.

Back on windows, I run the Cygwin X server and use that to serve xterms up from the virtual machine (don't forget to use xhost under cygwin to allow the vm access to the X server).

Blog API and blogging tools

After reading George's solicitation, I decided to take a look around for win32 offline blogging apps. The two I looked at this morning (w.bloggar and SharpMT) didn't work with my old 'zlog, which is a bit annoying. From what I could see, w.bloggar uses methods not supported by s9y and SharpMT is broken (the correct responses were being sent, it just kept thinking that there was an HTTP violation).

Is there good, bloat-free (or at least, not over bloated) app for win32? Is there one for linux ?

Shoddy software

sacred_cover_gb.jpg.t ... and I'm not talking about my own this time.

I just sat down with Juliette to play a two-player game of Sacred, a game that I got a little while back, but never found the time to finish.

Sacred looks lovely and is a fun game, when it's not crashing out. It's one of the least stable games I've laid eyes upon (even with the latest patches) since I had the misfortune to install the first Star Trek: Armada game.

None of the above

I caught the tail end of the BBC news earlier tonight (I don't normally watch it) and one of the reports was concerning the "Presidential Debate". The report was rounded off with the news that "Kerry has been attending debating camp, learning to use shorter sentences" and that "Bush has been practicing staying up later than 9.30pm (his usual bedtime) in order to appear alert later in the evening".

Now, I couldn't help but laugh at this news, particularly when there is no clear leader in the polls. I'm, once again, reminded of the "Vote for None of the Above" campaign from the film Brewsters Millions. Brewster has to spend $30 million in the space of 30 days in order to qualify for his inheritance, and decides that one good way to get rid of the cash is to enter the presidential campaign. However, he isn't in it to win it, he is merely there to point out that none of the candidates is worth voting for, especially himself.

I Am Legend

I Am Legend

The other day I received a couple of goodies from my wishlist (thanks to David Costa) in return for fixing an SQLite bug. One of the items was I Am Legend.

If you are a fan of vampire stories, or even if you're not, this is an excellent short story (just shy of 160 pages of very readable type). It was so good that it only took me a few hours to read, and as I approached the end of the book I got the "oh no! the story is almost over" feeling that accompanies books of this calibre.

I thoroughly enjoyed the book. If you're not a vampire fan, don't be put off by the frightening cover artwork--the story is not particularly violent or explicit, making excellent use of mood and tension instead.

It's like 5 bucks, right?

Well, I'm finally back from php|works, feeling pretty good about how well the conference went for everyone, and pretty jet-lagged and travel tired.

If there is one thing I'll always remember from the conference, it will be the slur on Canadian currency value from the title of this post; it got a lot of mileage (all in good spirits though!).

The conference seemed to go very well; I didn't see (m)?any attendees playing truant, and that suggests the there was enough going on to satisfy them all. After hours there was plenty to do in and around Toronto (even if it meant going to Best Buy first) and that made a welcome change compared to the venues for some of the other conferences I have been to--they have a tendency to be somewhat isolated, and that can lead to a kind of cabin fever.

I was one of the unfortunate speakers to be harasmussed, but I didn't mind; it was kinda funny to see people walk in, hang around for a bit, and then sneak out carrying the vacant chairs from the room.

Thumbs up to Marco and php|a for another great conference! :-)

PDO semi-toy example

Here's a little script I just cooked up for my crontab. It watches the sites listed in the URLs array for changes in the "plain text"; when a change is detected, it outputs the URL.

Although the task is somewhat trivial, this script does a good job of demonstrating how convenient bound parameters can be. To make the update, I simply need to call $upd->execute(). No need to manipulate or massage the variables in the script, and no need to generate the SQL statement on each iteration.

<?php   
   $URLS = array(
      'http://www.nvidia.com/object/linux.html',
      'http://ipw2200.sourceforge.net/news.php',
   );
   if (!extension_loaded('pdo_sqlite')) {
      dl('pdo.so');
      dl('pdo_sqlite.so');
   }
   $db = new PDO('sqlite:' . getenv('HOME') . "/.web-watch.sql3");
   $db->query('create table hash(url primary key, hash)');
   $db->setAttribute(PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION);
   $cached_hash = null;
   $stmt = $db->prepare('select hash from hash where url = :url');
   $stmt->bindParam(':url', $url);
   $stmt->bindColumn('hash', $cached_hash);
   $upd = $db->prepare('replace into hash (url, hash) values (:url, :hash)');
   $upd->bindParam(':url', $url);
   $upd->bindParam(':hash', $hash);
   foreach ($URLS as $url) {
      $body = file_get_contents($url);
      $hash = md5(strip_tags($body));
      $cached_hash = null;
      if ($stmt->execute()) {
         $stmt->fetch(PDO_FETCH_BOUND);
      }
      if ($cached_hash != $hash) {
         $upd->execute();
         echo "Changed web:\\n$url\\n";
      }
   }
?>

Gmail accounts up for grabs

I've got a few invites going spare; if you'd like to try it out, you (should!) know how to contact me.

Update: I'm back up to the full complement of 6 available invites again; seems like you can't give the damned things away.... please take them off my hands...

Update2: Another morning, another full complement of 6 invites...

Update3: still got the full complement. They're obviously not as sexy as they once were.

PHP 5 Extension Dependencies - we need your help

update (in response to a recent comment): this patch is already in the 5.1 tree; please test that instead; thanks!

[patch updated again: if you have a BSD-ish system that complained about "making too many open files", please try it again]

[patch updated: please try it again. Note that if you only have "mawk" you should install GNU awk instead, as mawk is broken]

I've created this patch to force extensions with dependencies on other extensions to be initialized in the correct order when they are compiled statically into the core of PHP.

It's known to work on Linux (and Tru64--thanks Magnus), but we'd really like to know if it works on other systems too--we don't have access to them, and we really want to get this into PHP 5.0 (due in July!).

So, please test it if you have SunOS, Solaris, BSD, HP UX, AIX, IRIX or any other "weird" unix systems.

Instructions:

Checkout PHP 5 (or download a snapshot from snaps.php.net)

Apply the patch:

   cd php5
   curl http://www.php.net/~wez/configure-deps.diff | patch -p0
   ./configure

Now look in main/internal_functions.c and/or main/internal_functions_cli.c

You should see something like this in the file. Each line corresponds to the extensions you enabled through configure; your list may be longer or shorter depending on how much stuff you compile into PHP.

   zend_module_entry *php_builtin_extensions[] = {
    phpext_libxml_ptr,
    phpext_xml_ptr,
    phpext_tokenizer_ptr,
    phpext_sysvshm_ptr,
    phpext_sysvsem_ptr,
    phpext_sysvmsg_ptr,
    phpext_standard_ptr,
    phpext_sqlite_ptr,
    phpext_simplexml_ptr,
    phpext_spl_ptr,
    phpext_session_ptr,
    phpext_posix_ptr,
    phpext_pcre_ptr,
    phpext_pcntl_ptr,
    phpext_gd_ptr,
    phpext_ftp_ptr,
    phpext_exif_ptr,
    phpext_ctype_ptr,
    phpext_calendar_ptr,
    phpext_bz2_ptr,
    phpext_bcmath_ptr,
    phpext_zlib_ptr,
};

If you see libxml before xml, it's worked. If you see repeated entries, it broke.

Please try building it now too.

If it all works, please either comment here, or send an email to internals@lists.php.net indicating which platform you used.

If it breaks while compiling main/internal_functions.c and/or main/internal_functions_cli.c, please let me know by email to wez@php.net.

Prefix the subject with [DEPS-PATCH], and include the name of your platform and the internal_functions.c file.

Thanks for helping to make PHP 5 better :-)