Jan 252010
 

Wow, it’s been more than a year since a release of this package. Not bad.

Today’s release just fixes a problem on some Windows configurations (64-bit MinGW, at least), where <windows.h> must be included in order for autoconf to detect <GL/gl.h> as usable.

In order to streamline GtkGLExt‘s configure.ac (and to provide decent support for the coming Quartz backend), I started using the macros there. So that’s a bit more exposure and exercise they’re getting.

 Posted by at 4:11 pm
Dec 262008
 

I started using Linux with Red Hat 5.0. When that distribution morphed into Fedora, I continued using it. And I’ve eagerly installed each new version within days of its release.

In the past 9 years of using Red Hat/Fedora, only my attempts to get Red Hat 5.0 to work with some on-the-motherboard SCSI hardware rivals the pain I’ve experienced installing Fedora 10. While there was some self-inflicted pain described in my previous posting, it was quite mild compared to what I was walking into unwittingly.

Bug 466607 was my first, and biggest, problem. Now, after finding this bug report, the problem isn’t too difficult to work around: there’s a kernel parameter that can be passed at boot time that would get things working. But until one has managed to find this bug report, life sucks.

Then there is the relatively well known problem that NetworkManager—the magical take-control-of-all-your-network-interfaces-and-everything-will-Just-Work system service—actually doesn’t work very well at all with static IPs. Turning it off, one is then bound to run into bug 469434. This one isn’t a show-stopper by any means; but it’s also something that’s clearly a simple goof somewhere that’s bound to be trivial to fix. And yet, even though it was reported back at the end of October, it made it into Fedora 10. Bug 466607, which is much more severe, was reported earlier in October and it made it into Fedora 10, too.

Both of these bugs are, fortunately, now fixed. But my impression is that Fedora release managers need to be more willing to hold up a release to fix bugs that are outright showstoppers for users or simply instances of an egregious lack of polish.

Oct 062008
 

Nvidia has stuck libraries that their libGL needs into a subdirectory; a subdirectory that the linker doesn’t know about. So the customary -lGL is insufficient; one needs to add

-L/usr/lib/nvidia

as well.

So I’ve made a lame hack to AX_CHECK_GL that should accommodate this. (Well, it’s no more—or less—lame than what I had to do for Mac OS X a little while ago.) And there’s a new release.

May 252008
 

The Tyan K8WE motherboard in hinge has always seemed to have flaky DIMM slots. I seemed to have to wiggle the DIMMs just right in order for the machine to recognize all of the memory. A recent graphics card upgrade nudged things and I was unable to nudge them back into an agreeable position. The board had about a month left on its three year warranty; so, back to Tyan it went.

So I am without my primary development machine for at least a week. It’s hard to believe I’ve had this thing for nearly three years. Last year I upgraded the CPUs to Opteron 285s from the 242s I originally installed; but still it doesn’t seem that old. Its predecessor was a dual Pentium 3 machine that lasted me five years

When hinge is put back together, I think I’ll go ahead and release OpenVRML 0.17.6. I still haven’t fixed that damn JPEG bug; but I think there have been enough less consequential changes to warrant a release.

Another change I’d hoped to work into a point release in the 0.17 series is the use of D-Bus to replace the existing IPC mechanism used in openvrml-xembed. Aside from being generally cleaner and nicer than the existing hacky IPC solution, I’m thinking that expressing the IPC interface in D-Bus should lead to an improved code organization that will in turn make it easier to convert the existing multiprocess approach into a multithread approach for Mac OS X and Windows. This is becoming more pertinent as it seems there may be folks interested in contributing code to a Windows plug-in.

 Posted by at 12:08 am
Jan 262008
 

I wrote this for parsing RSS feeds associated with a SourceForge project. It should be reasonably capable for that purpose; though I do not expect it to be generally robust for handling arbitrary feeds. Reed suggested that this might be generally useful; however, I don’t want to put it anywhere that I might feel compelled to maintain it. So this seems like a good spot.

//
// Copyright 2008  Braden McDaniel
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this library; if not, see .
//

class RSS2Image
{
    var $url = "";
    var $title = "";
    var $link = "";
    var $width = "";
    var $height = "";
}

class RSS2Item
{
    var $title = "";
    var $link = "";
    var $description = "";
    var $author = "";
    var $category = "";
    var $comments = "";
    var $enclosure = "";
    var $guid = "";
    var $pub_date = "";
    var $source = "";
}

class RSS2Channel
{
    var $title;
    var $link;
    var $description;
    var $copyright;
    var $last_build_date;
    var $generator;
    var $image;
    var $items;

    function RSS2Channel()
    {
        $this->title = "";
        $this->link = "";
        $this->description = "";
        $this->copyright = "";
        $this->last_build_date = "";
        $this->generator = "";
        $this->image = null;
        $this->items = array();
    }
}

class RSS2Parser
{
    var $parser;
    var $channel;

    var $in_channel, $in_image, $in_item;
    var $current_element;

    function RSS2Parser()
    {
        $this->in_channel = false;
        $this->in_image = false;
        $this->in_item = false;
        $this->parser = xml_parser_create();
        xml_set_object($this->parser, $this);
        xml_set_element_handler($this->parser,
                                "start_element",
                                "end_element");
        xml_set_character_data_handler($this->parser,
                                       "character_data");
    }

    function parse($data)
    {
        xml_parse($this->parser, $data);
        return $this->channel;
    }

    function start_element($parser, $name, $attribs)
    {
        $name = strtolower($name);
        if ($name == "channel") {
            $this->in_channel = true;
            $this->channel = new RSS2Channel();
            return true;
        }

        if ($this->in_channel) {
            if ($name == "image") {
                $this->in_image = true;
                $this->channel->image = new RSS2Image();
                return true;
            } elseif ($name == "item") {
                $this->in_item = true;
                $this->channel->items[] = new RSS2Item();
                return true;
            }
        }

        if ($this->in_image) {
            $this->current_element = &$this->channel->image->$name;
        } elseif ($this->in_item) {
            $this->current_element = &end($this->channel->items)->$name;
        }
        return true;
    }

    function end_element($parser, $name)
    {
        $name = strtolower($name);
        if ($name == "channel") {
            $this->in_channel = false;
        } elseif ($name == "image") {
            $this->in_image = false;
        } elseif ($name == "item") {
            $this->in_item = false;
        }
        unset($this->current_element);
        return true;
    }

    function character_data($parser, $data)
    {
        if (isset($this->current_element)) {
            $this->current_element .= $data;
        }
        return true;
    }
}
Jan 212008
 

…or, solving the problem of how to delete local references when the execution context isn’t clear.

JNI includes notions of “local” and “global” references. Loosely speaking, local references correspond to those that are local to the scope of a function; and global references correspond to those that persist outside the scope of a function. When Java calls a native method, it provides the native code with at stack frame where local references can be stored. The native code can then proceed to make calls to JNI functions which, in general, return local references. These local references are automatically stored in the stack frame; and the Java runtime takes care of cleaning them up when it pops the stack frame upon leaving the native method implementation.

So far, so good. But not all JNI function calls occur in response to Java calling a native method implementation. In fact, if you’re starting up the VM via JNI, you probably end up calling JNI functions that return local references just the same. Only in this case, the Java runtime hasn’t provided you with a stack frame that it will magically clean up once your code is done executing. Instead, you’ll need to delete the local references explicitly.

Thus we have two very different ways local references must be handled, depending on the context of the JNI function calls. And the inevitable problem: in a utility function which might be called from either context, how should intermediate local references be handled? Consider a function that creates a java.net.URL instance:

jobject create_url(JNIEnv & env, const char * const url)
{
    const jstring url_string = env.NewStringUTF(url);
    if (!url_string) {
        env.ExceptionClear();
        throw std::runtime_error("failed to construct string for URL");
    }

    const jclass url_class = env.FindClass("java/net/URL");
    if (!url_class) {
        env.ExceptionClear();
        throw std::runtime_error("could not find java.net.URL class");
    }

    const jmethodID ctor_id =
        env.GetMethodID(url_class, "", "(Ljava/lang/String;)V");
    if (!ctor_id) {
        env.ExceptionClear();
        throw std::runtime_error("failed to get "
                                 "java.net.URL.URL(java.lang.String) "
                                 "constructor");
    }

    const jobject url_obj =
        env.NewObject(url_class, ctor_id, url_string);
    if (!url_obj) {
        env.ExceptionClear();
        throw std::runtime_error("could not create java.net.URL "
                                 "instance");
    }

    return url_obj;
}

The above code will work just fine when it is called from a native method implementation. But if it is called outside that context, it will leak the local references corresponding to url_string and url_class. (We can assume the caller has responsibility for the local reference corresponding to url_obj in both cases.)

So, let’s toss in the code to delete the local references. We need to be exception-safe, so let’s use ScopeGuard:

jobject create_url(JNIEnv & env, const char * const url)
{
    const jstring url_string = env.NewStringUTF(url);
    if (!url_string) {
        env.ExceptionClear();
        throw std::runtime_error("failed to construct string for URL");
    }
    scope_guard url_string_guard =
        make_obj_guard(env, &JNIEnv::DeleteLocalRef, url_string);

    const jclass url_class = env.FindClass("java/net/URL");
    if (!url_class) {
        env.ExceptionClear();
        throw std::runtime_error("could not find java.net.URL class");
    }
    scope_guard url_class_guard =
        make_obj_guard(env, &JNIEnv::DeleteLocalRef, url_class);

    const jmethodID ctor_id =
        env.GetMethodID(url_class, "", "(Ljava/lang/String;)V");
    if (!ctor_id) {
        env.ExceptionClear();
        throw std::runtime_error("failed to get "
                                 "java.net.URL.URL(java.lang.String) "
                                 "constructor");
    }

    const jobject url_obj =
        env.NewObject(url_class, ctor_id, url_string);
    if (!url_obj) {
        env.ExceptionClear();
        throw std::runtime_error("could not create java.net.URL "
                                 "instance");
    }

    return url_obj;
}

There. Now we can call the function outside a native method implementation. But in making that work, we’ve made the function unusable from within a native method implementation. Clearly we need to make the calls to JNIEnv::DeleteLocalRef conditional; and we’ll have to let the caller tell us what context the function is being called in.

jobject create_url(JNIEnv & env,
                    const char * const url,
                    const bool delete_local_refs)
{
    const jstring url_string = env.NewStringUTF(url);
    if (!url_string) {
        env.ExceptionClear();
        throw std::runtime_error("failed to construct string for URL");
    }
    scope_guard url_string_guard =
        make_obj_guard(env, &JNIEnv::DeleteLocalRef, url_string);
    if (!delete_local_refs) { url_string_guard.dismiss(); }

    const jclass url_class = env.FindClass("java/net/URL");
    if (!url_class) {
        env.ExceptionClear();
        throw std::runtime_error("could not find java.net.URL class");
    }
    scope_guard url_class_guard =
        make_obj_guard(env, &JNIEnv::DeleteLocalRef, url_class);
    if (!delete_local_refs) { url_class_guard.dismiss(); }

    const jmethodID ctor_id =
        env.GetMethodID(url_class, "", "(Ljava/lang/String;)V");
    if (!ctor_id) {
        env.ExceptionClear();
        throw std::runtime_error("failed to get "
                                 "java.net.URL.URL(java.lang.String) "
                                 "constructor");
    }

    const jobject url_obj =
        env.NewObject(url_class, ctor_id, url_string);
    if (!url_obj) {
        env.ExceptionClear();
        throw std::runtime_error("could not create java.net.URL "
                                 "instance");
    }

    return url_obj;
}

Well, there we are. It’s exception-safe and the caller can tell it the Right Thing to do. But…It sure does Suck.

  • It relies on the caller telling it to do the right thing; which means it’s pretty easy to use wrong.
  • We wind up having to check delete_local_refs at each point where we might (or might not) need to call JNIEnv::DeleteLocalRefs. Our need for exception-safety prevents us from consolidating this logic near the end of the function. Consequently, the code is interspersed with yet more error handling logic that gets in the way of understanding the function’s primary logic when reading the code.

So, is there a better way? Fortunately, yes.

JNI provides functions that allow us to create (and destroy) stack frames for local references, JNIEnv::PushLocalFrame and JNIEnv::PopLocalFrame. So, instead of relying on a stack frame that may or may not be there depending on the calling context, we can just create our own, regardless.

jobject create_url(JNIEnv & env, const char * const url)
{
    if (env.PushLocalFrame(3) < 0) { return 0; }
    scope_guard local_frame_guard =
        make_obj_guard(env, &JNIEnv::PopLocalFrame, 0);

    const jstring url_string = env.NewStringUTF(url);
    if (!url_string) {
        env.ExceptionClear();
        throw std::runtime_error("failed to construct string for URL");
    }

    const jclass url_class = env.FindClass("java/net/URL");
    if (!url_class) {
        env.ExceptionClear();
        throw std::runtime_error("could not find java.net.URL class");
    }

    const jmethodID ctor_id =
        env.GetMethodID(url_class, "", "(Ljava/lang/String;)V");
    if (!ctor_id) {
        env.ExceptionClear();
        throw std::runtime_error("failed to get "
                                 "java.net.URL.URL(java.lang.String) "
                                 "constructor");
    }

    const jobject url_obj =
        env.NewObject(url_class, ctor_id, url_string);
    if (!url_obj) {
        env.ExceptionClear();
        throw std::runtime_error("could not create java.net.URL "
                                 "instance");
    }

    return url_obj;
}

Once again, we use ScopeGuard; only now we’re using it to call JNIEnv::PopLocalFrame. Note that we’ve provided space for three local references in our frame, corresponding to url_string, url_class, and url_obj.

But the above code is horribly broken: when we return from the function, the local reference associated with url_obj gets cleaned up with all the others! So, how do we get this reference out of our local frame and return it, unbound, to the caller?

The solution is a temporary global reference. We can convert url_obj to a global reference for the remaining duration of the local frame; and then convert it back to a local reference before returning to the caller. Once more, ScopeGuard is our friend:

jobject create_url(JNIEnv & env, const char * const url)
{
    using boost::ref;

    //
    // We can safely run DeleteGlobalRef in the scope guard because
    // calling DeleteGlobalRef with 0 is a no-op.
    //
    jobject result_global_ref = 0;
    scope_guard result_global_ref_guard =
        make_obj_guard(env,
                       &JNIEnv::DeleteGlobalRef,
                       ref(result_global_ref));
    {
        if (env.PushLocalFrame(3) < 0) { return 0; }
        scope_guard local_frame_guard =
            make_obj_guard(env, &JNIEnv::PopLocalFrame, 0);

        const jstring url_string = env.NewStringUTF(url);
        if (!url_string) {
            env.ExceptionClear();
            throw std::runtime_error("failed to construct string for URL");
        }

        const jclass url_class = env.FindClass("java/net/URL");
        if (!url_class) {
            env.ExceptionClear();
            throw std::runtime_error("could not find java.net.URL class");
        }

        const jmethodID ctor_id =
            env.GetMethodID(url_class, "", "(Ljava/lang/String;)V");
        if (!ctor_id) {
            env.ExceptionClear();
            throw std::runtime_error("failed to get "
                                     "java.net.URL.URL(java.lang.String) "
                                     "constructor");
        }

        const jobject url_obj =
            env.NewObject(url_class, ctor_id, url_string);
        if (!url_obj) {
            env.ExceptionClear();
            throw std::runtime_error("could not create java.net.URL "
                                     "instance");
        }

        //
        // Create a global reference so that the new object will outlive
        // the local frame.
        //
        result_global_ref = env.NewGlobalRef(url_obj);
        if (!result_global_ref) { return 0; }
    }

    //
    // NewLocalRef does not throw any Java exceptions.
    //
    const jobject result = env.NewLocalRef(result_global_ref);
    if (!result) { throw std::bad_alloc(); }

    return result;
}

Ta da. We introduce an additional block scope for the duration of our local stack frame. We propagate the reference to the URL class instance out of the frame’s lifetime by creating an additional global reference. Once the frame is destroyed, this global reference is all that’s left. We then create a new local reference to return to the caller, and let a scope guard take care of cleaning up the global reference.

Nov 302007
 

After nearly a year since pushing this project to Google’s project hosting, I’ve finally made a release tarball of my Autoconf macros for OpenGL. This was motivated mostly by significant changes that were precipitated by requirements for building on Mac OS X 10.5.

10.5 is a bit of a mixed bag relative to its predecessor as far as building with OpenGL is concerned. The good: they finally fixed the longstanding bug with the GLU tesselator callback function type being interpreted incorrectly. The bad: linking with X11 now requires some extremely goofy linker flags.

One nice thing, though, is that I was finally able to get rid of the annoying ‑‑with‑apple‑opengl‑framework option.

Jun 182007
 

My word… was it really all the way back in 2001 that I first latched onto the idea of replacing OpenVRML‘s ANTLR-based parser with one written using Spirit?

I guess I shouldn’t be so surprised; I was immediately taken with Spirit upon discovering it. Though back in 2001 Spirit still had a good deal of growing up to do; it’s come a long way since then. And given that Joel de Guzman and his cohorts are hard at work on Spirit 2, I get the idea that it will yet go a good deal farther. But I digress.

It’s the middle of 2007 and I’ve gotten serious about writing a VRML parser for OpenVRML using Spirit. Why now? Well, ANTLR certainly hasn’t gotten to be any less of an annoying dependency over the years. But another factor is that a new major version of ANTLR has been released (3.0). I think my efforts are better spent moving away from ANTLR entirely than on upgrading to the new version (which I understand to include nontrivial changes to the grammar format).

Now, I don’t want to come across as disliking ANTLR. It’s a really nice tool. In fact, it’s the nicest parser generator I’ve ever used. Functionality is very discoverable and I found, for the most part, the general behavior of ANTLR parsers to be very intuitive. But it has its downsides:

  • It’s a code generator, and thus it has the caveats that go with any code generation.
  • It’s a Java program, and thus it has the caveats that go with any Java program.
  • Even though it can generate code for a number of languages that are Not Java, the Not Java language backends are maintained by persons other than the primary author of ANTLR; thus, these languages wind up being second class citizens. (For example, ANTLR 3.0 has been released without C++ support since the maintainer of the C++ backend for ANTLR 2.x didn’t have time to port it to the new version.)

I actually started in earnest on this project at the end of last summer. I made a good deal of headway, getting as far as developing a good understanding of how to use Spirit’s stored_rule to create a grammar with productions that get modified as part of the parse. This solved the somewhat tricky issue of parsing node field values. But then I got side-tracked with getting the stand-alone viewer (openvrml-player) working reliably; that took much, much longer than I’d anticipated. But now I’ve picked up pretty much where I left off. As of this writing, I can parse nodes, EXTERNPROTOs, and PROTOs, except for IS mappings. I still have to do ROUTEs; though they will be pretty easy now that I’ve got DEF working.

As with the ANTLR-based parser, I’m doing a good deal of semantic checking; this parser will be just as aggressive about rejecting code that’s Not Quite Right as OpenVRML’s current parser. But unlike OpenVRML’s current parser, I’m using very little of OpenVRML’s runtime machinery to accomplish this checking. The idea is to make this parser much more reusable than OpenVRML’s current parser. The current parser isn’t really exposed; users can read the file into the runtime and then inspect the node tree created for the runtime. It turns out, though, that a good deal of OpenVRML’s users (and prospective users) don’t care one bit about a VRML/X3D runtime—they just want to read a VRML or X3D file and do something with the data. So, the new parser will have

  • Pluggable semantic actions
  • Minimal dependencies on the rest of libopenvrml—ideally, linking with libopenvrml won’t be required at all for someone just using the parser

This will all be possible through the magic of Spirit and C++ templates.

 Posted by at 12:21 am