Alan Burlison's Work Related Ramblings

All | General | Java | NetBeans | Perl | Solaris

20071015 Monday October 15, 2007

Producing CSS with self-caching JSPs

One thing that's always bugged me about CSS is that it doesn't have any sort of macro facility, so if you want to use (say) a HTML colour code in several places you need to hard-code it into all of them, which is a right pain. As the app I'm working on is written using JSPs, the obvious thing to do was to output the stylesheet from a JSP, and then use EL to define variables and then insert them in the appropriate places, e.g.:

<c:set var="background" value="#292B4F"/>

body {
    background-color:   ${background};
}

That works just fine, but each time the stylesheet is referenced it is regenerated, and as the content is completely static that was obviously a bit inefficient. Some googling found a number of ways to cache the output of JSPs, from the simple to the extremely complex, e.g. using a servlet filter to intercept requests and cache output. I wanted the simplest possible mechanism that would do the job, with no external dependencies.

A fairly obvious technique is to cache the output in the servlet's application scope the first time it is generated, then on subsequent requests you just send back the cached output rather than regenerating the content. The basic outline looks like this:

<c:if test="${empty cachedStylesheet}">
<c:set var="cachedStylesheet" scope="application">

    <!%-- Cached content goes here -->

</c:set>
${cachedStylesheet}

That works fine, but if you watch the conversation between the browser and the servlet, the cached content is still refetched each time this is referenced. This is because JSP output is usually dynamic, so the servlet container sets things up so that the client browser doesn't cache the content obtained from JSPs. To fix this we need to manually add the appropriate headers to the HTTP response we send back to the client to that it knows to cache the content, and we also need to respond to requests from the browser asking if the content has changed. The relevant HTTP headers are If-Modified-Since, Last-Modified and Expires, see the HTTP specification for more details. This requires a little bit of additional inline Java code in the JSP, so the final version looks like this:

<%@page contentType="text/css; charset=UTF-8" pageEncoding="UTF-8" session="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:if test="${empty cachedStylesheet}">
<c:set var="cachedStylesheet" scope="application">

<%-- Cached content goes here -->

</c:set>
<c:set var="cachedStylesheetDate"
  value="<%= new Long(new java.util.Date().getTime()) %>"
  scope="application"/>
</c:if>
<%
  long date = (Long) application.getAttribute("cachedStylesheetDate");
  long mod = request.getDateHeader("If-Modified-Since");
  if (mod > date) {
      response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
      return;
  }
  response.setDateHeader("Last-Modified", date);
  response.setDateHeader("Expires", new java.util.Date().getTime() + 86400000);
%>
${cachedStylesheet}

Here's how it works: We cache the content the first time the JSP is accessed as before, but we also set a session scope variable cachedStylesheetDate that records the time when the output was generated. Then on each request we fetch the value of any If-Modified-Since header specified by the browser. If the content was generated before the date, the browser already has an up-to-date version of the content, so we just send back a 304 response (Not Modified) to indicate that fact. Otherwise we set the Last-Modified header to the date when the content was generated, and then set the Expires header to tell the browser to check again in (60 * 60 * 24) * 1000 = 86400000 milliseconds, i.e. in 24 hours, and then we send the cached output. That way, after the initial fetch, the browser will only check once a day to see if the content has been updated - that figure can of course be adjusted as necessary.

Posted by alanbur ( Oct 15 2007, 03:29:59 PM BST ) Permalink Comments [0]

20070503 Thursday May 03, 2007

XML-based J2EE frameworks considered harmful

Are you using one of the XML-based web frameworks such as Spring, WebWork or Struts?  Then you've been duped.  Conned. Flimflammed. Bamboozled. Hornswoggled.  (Yes, this is a rant ;-) Here's why:

I'm fully aware that the list above is crammed with generalisations, and that there are various hacks and workarounds for some of the issues.  However the overall point I'm making is that XML isn't a programming language, yet virtually all of the major J2EE frameworks use it as if it is.  The pervasive use of XML for tasks for which it is not suited has effectively discarded the last 30 years of software engineering advances. This is a huge mistake, and I expect that in 5 years time people will look back at the current XML mania and say "What the hell were we thinking of?"

Posted by alanbur ( May 03 2007, 12:44:05 PM BST ) Permalink Comments [5]

20070405 Thursday April 05, 2007

Why I hate XML configuration files

I'm working on a reasonably large (1000+ source files) J2EE application that makes extensive (some might say utterly excessive) use of external components - it requires in the order of 50 JAR libraries to run, over and above the standard J2EE ones.

And of course many of those JAR files have their own unique XML files to configure them.

And of course many bits information related to the configuration of the application (URLs, database details etc) have to be repeated in more than one of those XML files.

And there's no global way of doing this.

So the people who originally wrote the application came up with a scheme - they'd store the configuration values in property files.

But that only helped for things that got the values dynamically, for example by using the J2SE Properties class.  All those external components they'd used didn't know anything about the application's property files, they only knew about their own unique little XML files.

So the people writing the application came up with another scheme - they'd embed tokens in all those little XML files, then use the Ant Filter task to replace them at build-time with the values from the property files.

"Job's a good 'un!" they doubtless exclaimed, flushed with their extreme cleverness.


Then I came along and had to maintain the beast, when I found:

All this means that there's a whole series of bear traps set for anyone who innocently changes any of the properties files of the application, in the mistaken assumption that the application will actually take any notice of them.

I'm sure I'm not the only person to have been hit by this problem - how to configure multiple external components which all have their own XML configuration files - without having to hand-edit each of the XML files each time every time something changes, and without hard-coding the configuration at build time.  However I'm damned if I know of a good way of doing it - although I can easily think up several not-very-good ways of doing it.

If you know, please let me know!

Posted by alanbur ( Apr 05 2007, 11:38:44 PM BST ) Permalink Comments [1]

20060930 Saturday September 30, 2006

Printing simple tabular reports with JTable

I've been putting together a little Java application that required some simple tabular reports that could be both viewed on screen and printed out, the data coming from a Derby database. I spent quite a bit of time looking a the various libraries out there for doing this. A lot of the libraries are commercial, which mean they were a non-starter, and to be honest I wasn't overy impressed with any of the free alternatives. The main ones that I looked at were Jasper Reports and the associated iReport GUI. It looks OK, but although the software is free, it costs $89.95 for the documentation, which in my book makes it a commercial product, not free. I also looked at JFreeReport and the associated Pentaho GUI, but it didn't support subreports and it needed an extensive list of additional libraries, which would have been much larger than my entire application. The last one I considered was DataVision, but the report building GUI was pretty awful - there was no way to align the baseline of fields for example, and you couldn't modify the generated SQL.

Several of the reporting tools accept a TableModel as a source of data, so I was looking at that and the documentation for the associated JTable Swing class, when I noticed that JTable has a Print() method, added in Java 1.5. This takes care of all the scaling and page break management that makes Java printing such a pain, and as the data I wanted to display and print was tabular in nature, it looked like JTable might just be OK for my (admittedly simple) requirements.

I put together a simple form in NetBeans with a JTable in it - NetBeans wraps JTable in a JScrollPane for you when you add it to a form. The next thing was to try to get the JTable to render as close as possible to a printed page. I selected the JTable and modified the properties accordingly - I deselected showHorizontalLines and showVericalLines to remove the cell borders, and disabled focusable as the table was going to be read-only I didn't want the user to be able to select cells within it. I also didn't want the column headers that JTable displays by default - there isn't a way to disable them via the NetBeans form designer, but a call to dataScrollPane.setColumnHeaderView(null) disables them. The next problem is that if the JTable doesn't fill the entire JScrollPane which encloses it, you get a grey background in the unoccupied area. Setting the background of the scroll pane doesn't work as there is another pane between the scroll pane and it's contained JTable, so what we need is dataTable.getParent().setBackground(java.awt.Color.WHITE).

The next problem is that when we display the table, all the columns are set to the default width, which means that the contents of a lot of them are truncated. Hmm. What we actually want is for the columns to be sized to fit the widest row that they contain, and for a horizontal scrollbar to be displayed. Scrolling through the properties for the JTable show an property called setAutoResizeMode that looks like it might do what we want. However it doesn't - there is no supported mode that sizes all columns to fit and enables the horizontal scrollbar if required. After a lot of digging I eventually found the answer. What is required is to set the resize mode to AUTO_RESIZE_OFF, which enables the horizontal scrollbar when required, and manually resize the columns so that they fit the contents. I used a simplified version of the code provided in the above link, taking advantage of the fact that we've already disabled the column headers:

    private void sizeColumns(JTable table) {
        TableColumnModel columns = table.getColumnModel();
        TableModel data = table.getModel();
        int margin = columns.getColumnMargin() * 2;
        int columnCount = columns.getColumnCount();
        int rowCount = data.getRowCount();
        for (int col = 0; col < columnCount; col++) {
            TableColumn column = columns.getColumn(col);
            int modelCol = column.getModelIndex();
            int width = 0;
            for (int row = 0; row < rowCount; row++) {
                TableCellRenderer r = table.getCellRenderer(row, col);
                int w = r.getTableCellRendererComponent(
                  table, data.getValueAt(row, modelCol), false, false, row, col)
                  .getPreferredSize().width;
                if (w > width) {
                    width = w;
                }
            }
            column.setPreferredWidth(width + margin);
        }
    }

Perfect. The resulting table looks just as I want, and printing it is a snap:

    try {
        dataTable.print(JTable.PrintMode.FIT_WIDTH,
          new MessageFormat(title),
           new MessageFormat("Page {0}"));
    } catch (PrinterException e) {
        Main.handleException(this, "Printing failed", e, null, false);
    }

The last trick is an easy way of changing text attributes in the contents of the cells. We can take advantage of the fact that text cells in a JTable are rendered with a JLabel, and that a JLabel's contents can be HTML. So, if for example we want an underlined cell we can just wrap the cell's contents in <html><u> and </u></html>. We can also do the same for bold, but taking it much further isn't advisable as the height of the cells won't adjust to fit.

Posted by alanbur ( Sep 30 2006, 11:33:09 AM BST ) Permalink Comments [2]

20060713 Thursday July 13, 2006

Run-time type checking with Java Generics

I've been looking at the Apache XML-RPC library, and figuring out how best to marshal and unmarshal Java objects that I'm passing between the client and server. XML-RPC only supports a limited set of data types, so if you want to pass the contents of an object across the interface you need to convert the object before transmitting it, the normal method being to transform it into an XML-RPC struct, which is a key-value hash map. The Apache library uses a Java Map to represent the XML-RPC struct type, with the keys being String objects and the values being any valid Java object.

Because I'm paranoid, I want to check the data types of the values as I convert the incoming XML-RPC request back into the corresponding Java objects. I could do this by just casting to the appropriate type and catching any ClassCastException, something like this:

    Person p;
    try {
        String name = (String) map.get("name");
        int age = (int) map.get("age");
        p = new Person(name, age);
    } catch (ClassCastException e) {
        System.out.println("Bad parameter type");
    }

but the diagnostics are not exactly useful, especially where there are a lot of entries in the map. The other alternative is to have a method that checks the type of the value retrieved from the map, and that throws an exception with a helpful diagnostic if there is a mismatch. However, I'd either have to make the checking method return Object and then cast it to the right type, or have a whole set of methods for each type I was interested in, e.g. checkInteger, checkString and so forth, which would be a pain to have to write.

However, it occurred to me that Java Generics might provide a better way of doing this. The most excellent Java Generics FAQ gave me a clue as how best to do this - I needed to provide the class I wanted to check against as a Class parameter to the type checking method. And - the really neat bit - by parameterising the Class argument we can then use the same type for the return type of the method, thus dispensing with the need to cast the return type of the method when we call it.

    public static <T> T typeCheck(Object obj, Class<T> type, String name)
      throws ClassCastException {
        Class objClass = obj.getClass();
        if (! type.isAssignableFrom(objClass)) {
            throw new ClassCastException("invalid type " +
              objClass.getSimpleName() + " for " + name + ", should be " +
              type.getSimpleName());
        }
        return type.cast(obj);
    }

With this in place, we can check and convert the type of an object without needing an explicit cast:

    Object obj = map.get("name");
    String name = DataMarshaler.typeCheck(obj, String.class, "name");

In the final version I wrapped up the map retrieval and type conversion into a single method, but it was clearer to show the type conversion as a seperate step in the example above. This is really kind of neat, however it's not entirely perfect - you can't pass in a parameterised type as the class argument, i.e.

    Map<String, Integer> = DataMarshaler.typeCheck(obj, Map<String, Integer>.class, "map");

because parameterised types are not themselves first-class entities in the Java type system, as a consequence of type erasure.

Posted by alanbur ( Jul 13 2006, 03:38:48 PM BST ) Permalink Comments [0]

20060411 Tuesday April 11, 2006

OziGeocacheUK

I've been tinkering around for a while wrapping the API to OziExplorer in Java, as chronicled both here and on my personal blog. I've finally knocked together an application that uses it. OziGeocacheUK integrates information from GeocacheUK.com and Geocaching.com into OziExplorer. OziExplorer is a GPS mapping application and GeocacheUK is a database of geocaches in the UK. Hopefully this post will help google steer anyone who is looking for such a thing towards it ;-)

Posted by alanbur ( Apr 11 2006, 10:46:20 PM BST ) Permalink Comments [0]

20060325 Saturday March 25, 2006

Why I hate XML, and Ant's use of it in particular

I was googling around for examples of how to sign a JAR file in Ant using the signjar task and came across this monstrosity - 217 lines of turgid XML. How anyone can say Ant is an improvement over make is a mystery to me, and how anyone could ever think that XML is fit for consumption by humans is an even bigger mystery. XML is food for machines, not people. I sincerely hope that the current fad for doing everything in XML (and we have some examples close to home) is going to an early grave.

What I'm actually trying to do is to deploy an application via WebStart from within NetBeans - I've loaded up the NetBeans WebStart module but it really doesn't cut the mustard yet, it doesn't support JAR signing, it hard-codes the name of the JNLP file and it assumes you want a WAR file building. Here's hoping they make the necessary improvements in short order.

 

Posted by alanbur ( Mar 25 2006, 04:26:49 PM GMT ) Permalink Comments [1]

20060219 Sunday February 19, 2006

JNI_OnUnload(): mostly useless

I've been writing a JNI wrapper that requires the creation of a native thread and I've set things up using a JNI_OnLoad() function to initialise the DLL and create an event handling thread thread. I also used the JNI_OnUnload() function to kill the thread and clean up the DLL, but what was puzzling me was that although JNI_OnLoad() was being called OK, JNI_OnUnload() was never being called, and because the thread was attached to the JVM via a call to AttachCurrentThread(), the JVM would never exit as it still had an active thread. A simple workaround was to attach the thread to the JVM as a daemon thread using AttachCurrentThreadAsDaemon as daemon threads don't keep the JVM alive, but that still meant the DLL wasn't being cleaned up properly.

Google revealed a post on the Java Forums which explained that the ClassLoader was keeping a reference to the class containing the native code wrapping the DLL, and that was preventing the JNI_OnUnload() function from being called. The post also explains that if you make a call to System.runFinalizersOnExit() then the finalizer for the ClassLoader will be called on exit and that in turn will call the native code's JNI_OnUnload() method, and a simple experiment proves that is indeed the case. However the System.runFinalizersOnExit() method is deprecated:

This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock.

In light of this it appears that although JNI_OnLoad() is useful, JNI_OnUnload() is less than useful. There is a workaround, which is to register a native exit callback using Runtime.addShutdownHook() and do the cleanup that way, but it's kind of broken that JNI_OnUnload() doesn't actually do what it is supposed to, and even more odd that the JNI documentation is completely silent as to why it doesn't actually work. I think I feel a bug report coming on ;-)

Posted by alanbur ( Feb 19 2006, 12:11:26 AM GMT ) Permalink Comments [0]

20060214 Tuesday February 14, 2006

JSlider oddity - setFont() is ignored

Despite what you might expect, the labels on a JSlider ignore any font that you specify with setFont(), at least in the Windows L&F. Most odd.

Posted by alanbur ( Feb 14 2006, 11:21:11 AM GMT ) Permalink Comments [3]

20060115 Sunday January 15, 2006

WonDLLand revisited

I previously documented my problems getting my Java/JNI wrapper for OziAPI to work. As I said in my previous post, some of my problems were down to the OziAPI DLL not getting a chance to clean up properly when the application exited. Over the Xmas break I continued hacking away and wrapping more of the interface, but when I got to the OziExplorer callback hooks I hit another showstopper. The hooks allow you to trap mouse clicks on the map displayed in OziExplorer, but every time I activated one of the callback hooks and then clicked on a map the application hung, and it stayed hung until I quit the Java application. My initial thought was that I was falling afoul of the awful mess of calling convention types on Win32 and that something was barfing on a malformed stack frame somewhere on the round trip from my Java app to OziExplorer and back. I was particularly suspicious because I was using the MinGW tools to build the DLL containing my JNI code rather than the Microsoft tools.

To try to track down the root cause I hacked together a console-only C program and DLL to mimic my Java/JNI setup, and whilst that worked fine with the non-callback OziAPI functions (as did the Java/JNI version), any callback functions hung in just the same way as the Java/JNI version. I noticed that the Visual C++ example included with the OziAPI DLL contained a Windows GetMessage loop, so I added one to my toy program and hey presto, it worked! This implied that OziAPI uses windows messages to implement the callback mechanism, and an email to Des Newman, the OziExplorer developer, confirmed that was in fact the case.

That didn't really help explain what was wrong with the Java/JNI setup - the Java code was a Swing application, so it had to contain a Windows dispatch loop anyway - right? What on earth was going on? I was more more less ready to admit defeat when I stumbled across an old JavaWorld article that contained the following:

Messages are not sent directly to windows, but put on an event queue owned by a thread. The thread must occasionally check for messages on its queue, and choose to either deal with them or delegate them to other callbacks ... we cannot be sure that these threads (which are owned by the VM process) check their Win32 message queues and delegate messages. In this case, in fact, they don't. Because of this, we will create our own thread, with our own message queue handling, and make sure all our windows are created within its context. It is simple to asynchronously delegate work to our worker thread by posting custom messages to its message queue.

Ahah! So it appeared that it is necessary to have a custom dispatch loop to handle the Windows messages dispatched by the OziAPI callback hooks. However, because this thread has in turn got to be able to dispatch the callbacks to Java methods, we have to play nicely with the JVM as well. We also have to make sure that the events from the OziAPI DLL get dispatched to our dispatch thread - some experimentation revealed that OziAPI sends them to the first thread that makes a callback-related OziAPI call. Putting this all together, here's what is necessary to get it to work:

The various OziAPI calls necessary to enable and disable the callback hooks are also made from inside the new thread - the event loop responds to custom WM_APP messages and enables or disables the callbacks as required. The callbacks themselves are handled by C routines that vector the events to the appropriate Java method calls. I've left out all details of the necessary inter-thread synchronisation that's needed, but that's an overview of how it all hangs together.

I'm still working on wrapping the rest of the API - it's rather large so it is going to take a while, but once I have it knocked into shape I'm intending to release the code here on SourceForge.

Posted by alanbur ( Jan 15 2006, 11:26:11 PM GMT ) Permalink Comments [0]

20051116 Wednesday November 16, 2005

Thank-you James Gosling

I'd previously tried my 12-year old son (another James!) with Logo, but he got bored with drawing boxes fairly quickly, and the Lisp-like syntax proved to be pretty impenetrable when it came to doing anything more complicated - all in all Logo wasn't a great success.

Over the weekend I'd had another try, teaching him the rudiments of OO programming using Java and NetBeans as a quick way of compiling and running some simple examples, but to be honest NetBeans was really a bit of overkill. Coincidentally James Gosling posted a recommendation yesterday on his blog for a learning environment for Java. The package is called BlueJ, and it's basically an IDE for Java where the explicit goals are to make it easy to create and explore the behaviour of an OO program - for example it allows you to instantiate and exercise the methods of your class without having to have a completed program.

James G also recommended an associated book - Objects First with Java - A Practical Introduction using BlueJ. The first two chapters are available online, so before I splashed out and ordered the book I installed a copy of BlueJ, printed off the sample chapters and let James B loose on them. I'm extremely impressed with BlueJ - the developers have obviously spent a lot of time considering not just what features to include, but also what features to leave out, and the result is a system that allows people to get to grips with OO concepts without getting swamped by the details of editing and compiling the code. What I've seen so far of the book is excellent, for example rather than getting bogged down in the details of syntax and control structures it starts by explaining (and illustrating) the OO fundamentals - classes, objects and their attributes and behaviours. I was impressed enough to order a copy :-)

As I was digging around I found this thread in response to an article on BlueJ on java.net, and I really was astonished by the whining on it, which could be summarised as falling into one of two categories - the first one being "IDEs are bad, everyone should use the command line". That strikes me as a pretty dumb complaint, somewhat equivalent to insisting that everyone has to understand how an ECU works before they can have driving lessons. The second complaint is along the lines of "BlueJ isn't suitable for building enterprise applications", i.e. "How come I can't shift this using this?". Well, Duh!. BlueJ does in fact have the ability to export code to other IDEs, so what exactly is the issue here?....

In my experience, people with an engineering bent first get interested in stuff by playing with something they think is neat, then they get really hooked by taking it apart and learning how it works, and on that basis I think the BlueJ environment and the associated book are an excellent starting point - certainly my James thinks so!

Posted by alanbur ( Nov 16 2005, 12:43:42 AM GMT ) Permalink Comments [4]

20050914 Wednesday September 14, 2005

Alan in wonDLLand

Yesterday evening I had another poke at the Java wrapper for OziExplorer that I'm sporadically working on, OziExplorer is the GPS mapping application that I use. It's a pretty fat API so I'm gradually chipping away at it. My latest tweak was to add a simple Swing GUI to my test app, but as soon as I did it blew up every time I exited with two rather foreboding messages, The exception unknown software exception (oxoeedfade) occurred in the application at location 0x7c81eb33 and Runtime error 217 at 0009D54. Google told me that second message was coming from Delphi - OziAPI.dll is generated with Delphi, but didn't shed much light on exactly what was causing the error. Some experimentation revealed that the problem was actually nothing to do with Swing, I could get the same effect by just calling System.exit immediately after calling any of the functions in the Delphi DLL. If I just let the program fall off the end of main the problem didn't happen, which suggested that whatever was going belly up was being caused by something that System.exit was doing, either directly or by indirectly perturbing the ordering of the JVM exit processing.

A bit more googling revealed that other people had hit the same problem and that one workaround was to manually open the DLL with LoadLibrary, find the symbol addresses via GetProcAddress and finally manually free the DLL with FreeLibrary on exit. (For those in the Solaris seats that's the Win32 equivalent of dlopen, dlysm and dlclose). This suggested to me that the problem was something to do with the timing of the DLL initialisation and/or cleanup. In my case I was pulling in the OziAPI DLL via a linker dependency from the JNI DLL, and I was loathe to have to manually pull in OziAPI.dll and look up the symbols just to work around this problem. Some more spelunking through the Microsoft documentation revealed the following interesting snippets:

If you terminate a process by calling TerminateProcess or TerminateJobObject, the DLLs of that process do not receive DLL_PROCESS_DETACH notifications. If you terminate a thread by calling TerminateThread, the DLLs of that thread do not receive DLL_THREAD_DETACH notifications.

The TerminateProcess function is used to unconditionally cause a process to exit. The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess.

I grabbed a copy of strace for NT and traced the execution of my test application, and although I could see calls to TerminateProcess I couldn't see any calls at all to ExitProcess, not only when I called System.exit but also when I let the program fall of the end of main. Hmm. If that was what was happening there didn't seem to be much I could do to change the behaviour of System.exit, but I already knew that if the process exited by falling off the end of main the problem didn't occur - what I needed to do was to find out how to make that happen from inside a Swing callback. I was wandering around the JDK documentation when I found an interesting document titled AWT Threading Issues, which says:

Therefore, a stand-alone AWT application that wishes to exit cleanly without calling System.exit must:

So the code to work around my problem turned out to be trivial, I just needed to put the following in my exit button callback to make sure that all the threads in the application exit cleanly:

        for (Frame f : Frame.getFrames()) {
            f.dispose();
        }

Hey presto, everything now works. Obviously if the application creates it's own threads they will also need to be gracefully terminated. I'm still discussing the exact cause of the problem with the JVM folks, but for now I'm happy that I've got a workaround. Hopefully this information will save someone some grief as my searching revealed that quite a few people had been hit by this issue but that nobody quite understood how to work around it other than the nasty manual DLL loading hack.

Posted by alanbur ( Sep 14 2005, 11:14:36 PM BST ) Permalink Comments [2]