Directory Opus 9 Logo

C++ Plugin Source Code

for Directory Opus

Real-world example source-code to help programmers write Opus plugins. (Some of this code may be useful to people writing other things, too.)

Directory Opus is a file manager for Windows. Read my guide, Getting to know Directory Opus, for an introduction.

The complete C++ source code to some of my Directory Opus plugins is available here to help other people write their own Directory Opus plugins. (Some of this code may be useful to people writing other things, too. If it helps you make something cool then that's great!)

(Delphi developers: The RVF File Viewer plugin by Sergey Tkachenko is written in Delphi and comes with source code which you should find very useful.)

To understand the Directory Opus plugin API for Viewer and VFS (virtual file-system) plugins you should download the Opus Plugins SDK from GPSoftware's downloads page. This includes several manuals and some additional example source code.

If you wish to discuss Opus plugin development, or ask for help, your best bet is the Developers Forum at the Opus Resource Centre.

Unless otherwise stated, all of the plugins below are written in C++ using only the Win32 API (and the C RunTime, STL, etc.). In particular, MFC is not used.

The source code comes packaged in Visual Studio projects/solutions but should be fairly easy to convert to other build environments, if required.

Excluding the Maya IFF plugin, the source code here is not always kept up-to-date with the actual plugin releases and is here to serve as an example, not to invite development forks or custom versions of the plugins. (I'll try to update for major/interesting changes, but updating for every little typo fix etc. is a pain for me to do. That said, if you do re-use any of this code then talk to me and I can look into a better way of keeping you up-to-date. It's not so much I don't want to keep the source up-to-date as that I doubt anyone cares enough that it's worthwhile!) Forked code and custom releases of the plugins just makes it confusing for people to work out which versions to use. If you want something changed or added then please contact me first. Of course, I am happy to accept code patches for things which make sense but do get in touch before you start work in case your change conflicts with something else that I'm working on.

Some of the plugins are grouped into a single Visual Studio solution while others are separate. (I was going to move them all into one but that turns out to make updating the web copies more of a hassle so maybe not.)

Plugin Source Unicode x64 USB
Audio Tags (PGP sig)
Yes Yes Yes
Raw Digital Camera Yes Yes Yes
GIFAnim Yes Yes Yes
NFO Yes Yes Yes
Targa Yes Yes Yes
TextThumb Yes Yes Yes
Maya IFF (PGP sig) Yes Yes Yes
JP2Raw (obsolete) (PGP sig) No No No
Ogg/FLAC (obsolete) (PGP sig) No No No

(For information from a user perspective, see the main page for the plugin.)

The Audio Tags plugin source demonstrates the following:

  • File information plugin. (Returns information about audio file metadata.)
  • Thumbnails for album cover art, without being a viewer. (There is only placeholder thumbnail code for this at the moment.)
  • Thumbnails returned as a JPEG or PNG block of memory, passing on the decoding job to Opus. (So we don't have to link yet another copy of libjpeg and libpng!)
  • Safe conversion of unterminated UTF-8 strings into UTF-16.
  • Multiple formats handled by one plugin. (This isn't always a good idea. Be pragmatic.)
  • Streams (i.e. works with files within archives).
  • Direct I/O on streams (and normal file handles) via an abstraction class. (Some of the other plugins handle streams by extracting them to a temporary file and then opening that file. Some plugins, including this one, read the stream directly. Which is best depends on how much of the file the code typically needs to read, and whether it is likely to read it sequentially or seek around a lot.)
  • Supports USB mode, Unicode and x64.

Unlike the old Ogg/FLAC plugin, the Audio Tags plugin doesn't use a library for parsing any of the audio formats it supports (at least at the time of writing); instead it uses custom-written code.

(For information from a user perspective, see the main page for the plugin.)

The Raw Digital Camera plugin source demonstrates the following:

  • Viewer & Thumbnails plugin.
  • Given a file path or stream, returns an HBITMAP. (The first, less powerful and more simple method described in the Plugin SDK.)
  • Configuration dialog. (The actual dialog resources have been moved into Opus's language DLL to allow for translation, if you're wondering where they are.)
  • Wrestling a Unix command-line app into a thread-safe, static-linked, resource-tracking, UTF-16-using Windows DLL, hopefully without making it a nightmare to merge in changes from upstream. :-)
  • Streams (i.e. works with files within archives).
  • Supports USB mode, Unicode and x64.

Unlike the released plugin DLL, the source code has disabled the use of libjpeg and lcms so that you can build everything out-of-the-box without me having to include those libraries in the archive. You can turn them back on by removing the two #defines (which will be pointed out to you by a large message when you build the DLL), and linking against libjpeg.lib and lcms.lib.

There are no other dependencies other then the optional libjpeg and lcms. In particular, CxImage is not required like it was with the old JP2Raw plugin. Instead of using CxImage this time I wrote my own PNM decoder for the subset of PNM which DCRaw outputs. (PNM is a very simple format and I was no longer using CxImage for any other format so it didn't make sense to continue using it.)

This is basically a re-write of the raw aspect of the old JP2Raw plugin. Both plugins are based on Dave Coffin's DCRaw but the way they use them is quite different.

As you can read on his site, Dave Coffin isn't a fan of library code and as a result DCRaw.c is unsuitable for use as a library. (Fair enough, it's his program and he can write it however he wants, especially when he's about the only person working on raw decoders and when he allows so many people and projects to benefit from his hard work for free!) Things that want to use DCRaw are expected to run it as a standalone executable. (Plenty of things do just that, too.) I don't want to get into the library vs executable argument here, and chances are every programmer reading this is already firmly in one camp or the other anyway, but after considering things I still wanted to go the library route. That required converting the code into something suitable for use as a library in a multi-threaded Windows application.

The first time I converted the code, for the JP2Raw plugin, I simply modified things in-place. This turned out to be hundreds of changes to the original code. That is still probably the easiest thing to do if you only need to do it once, but DCRaw (much to Dave Coffin's credit) gets updated quite often. Some updates change a lot of code, such as when DNG support was added (which Dave described as almost a re-write). As you can imagine, my approach to converting DCRaw.c in-place resulted in a maintenance nightmare of extremely complex three-way merging. In fact, I only updated the DCRaw part of the JP2Raw plugin once after the initial version, and the time & effort required to update the conversion was close to what it took to do it the first time... There had to be a better way.

I messed around with SED scripts in an attempt to semi-automate the conversion process but that turned out to be a giant waste of time, except for the lesson that I should never, ever use SED again for anything. :-) Of course, I foolishly went the SED route a couple of more times before I truly learned my lesson. Much as I keep writing MS-DOS .bat files, thinking "it's such a simple task that using anything else would be overkill!", only to re-re-re-re-re-rediscover that the MS-DOS batch language is the worst thing ever and is never the quickest way to write something if you actually want your solution to work in anything but the most un-pathological of cases. But I digress...

With the requirement of 64-bit support (among other things), I wanted to scrap the old JP2Raw plugin and split it into several new, separate plugins for the different formats it handled. My conversion of the DCRaw code was very out-of-date so I couldn't just re-use that in a new plugin. I knew I couldn't keep converting it the way I had before if I wanted to keep people happy with up-to-date raw support from now on. I seriously considered switching to using an external dcraw.exe but decided not to again. I thought about how I could convert the DCRaw.c code to my requirements while changing as little of it as possible. To understand you have to look at the kinds of thing I had to change the first time around:

  • Conversion of function-static and global variables into class variables. (So two threads can use the code in parallel.)
  • Fixes or suppression of compile-time errors and warnings that result from differences between C and C++, plus slight differences in the compiler and C-runtime libraries.
  • Clean-up of memory and output files on error. (The original code was an exe which exited on error. It could get away with leaving resources allocated because the OS would clean up for it on exit. Not so with library code that may live within a long-running process.)
  • Support for UTF-16 filepaths, as required by Windows. (Changing the code to do this directly results in a lot of "plumbing" work with knock-on effects in a number of places.)
  • Provision of a reliable way to know the name(s) of the output file(s) and other information about what the code did or didn't do.
  • Provision of a way to run the code and pass arguments to it, given that it's written as a command-line program with a main/argv/argc.

That's probably not a complete list but you get the idea.

AFAIK, there's not a great deal that can be done to avoid changing the original code when fixing the static/global variables for thread-safety, so I still do that and it's probably responsible for most of the diffs now. The good news is that it's a systematic change which doesn't affect that much of the code and it's easy to merge those changes into new versions of the code.

Some of the compiler errors/warnings also require small changes to the code, but again there are not that many of them and they're easy to merge.

I had an idea for the other changes: Rather than change DCRaw itself, change the things that DCRaw calls. The DCRaw logic is fine; the problem is the assumptions it makes about its environment (such as resource clean-up on exit). So change the environment rather than the code.

As an example, DCRaw calls malloc and free for memory. In the old way I had to look through the code for all the error cases which might happen where there was a pending free call, then add that free call in the error handling code. That caused lots of changes and it was also easy for me to miss something and leave a memory leak. In the new way, DCRaw still calls malloc and free but they are not the usual malloc and free. I've overridden them (via a base class) with functions which keep track of outstanding memory allocations. Now when DCRaw's main() finishes I know which memory it's left allocated and can free it. I know which temp/output files it wrote to that I may wish to read and/or delete. I pass all of DCRaw's printf output through my own function which picks out and stores the information it is interested in (without having to parse any text, too, as it gets the printf call and not the processed text output of it). I tell DCRaw to open a made-up ASCII filename which my replacement for fopen looks up in a map and turns into a UTF-16 filename that was supplied earlier. When DCRaw calls fopen or fread it could be reading from a Windows IStream rather than a C-runtime file handle, but the original code is none the wiser and does not need modification... You get the idea...

It's not perfect and I did still have to make a few changes. For example, the original DCRaw code rarely checks the results of fseek calls and I wanted to change that to make things more robust with invalid inputs (to avoid reading and interpreting whatever random data was at the file pointer after the failed seeks). So I made my fseek replacement throw an exception on failure (which is caught by my clean-up code), but it turns out there are some fseek calls which routinely fail, even with valid inputs, where the code does recover (usually after processing and, usually, rejecting some random data) by seeking back to a known-good position. Finding those cases takes some trial and error, and there may be some left in the code paths I've not tested yet which could cause some images to fail to display (but nothing worse). Where I have found them I've had to make some changes to the original code so that it handles the error rather than allowing my exception to be thrown. Again, though, these are a small handful of changes compared to the old method which changed almost every single API call.

So, yeah... The end result is basically a custom runtime environment for DCRaw. :-) The code which does this isn't particularly pretty or easy to follow, but that's somewhat inherent. The aim is to change DCRaw.c as little as possible, while still turning it into suitable library code, and I think that has been a success. The maintenance burden is much less now and I have been merging changes much more often as a result.

The DCRaw wrapper I've made is Windows-specfic in places but I've tried to make it easy to find those places (by #error messages) so that you can see what needs changing if you want to try and use my wrapper on another platform. I've got no idea if anyone will want to do such a thing but if my work can save someone else some time then that's great! The Windows-specific stuff is things like thread synchronization primatives where I've used a Win32 CRITICAL_SECTION but have no idea what the Unix/Linux/OS X/etc. equivalent might be. The use of UTF-16 filepaths will get in the way on other platforms but that should be very, very easy to remove/replace.

(For information from a user perspective, see the main page for the plugin.)

The Animated GIF plugin source demonstrates the following:

  • Viewer & Thumbnails plugin.
  • Creates its own window. (The second, more powerful and more complex method described in the Plugin SDK. See the NFO plugin for a more simple example, though.)
  • Emulates the internal viewer code:
    • Zooming.
    • Tiling.
    • Rotation (right-angles only).
    • Cropping.
    • Printing.
    • Set Desktop background.
    • Clipboard support.
    • F1 info box (alpha blended).
    • Transparency.
    • Image borders.
    • Automatic background colour choice.
    • Mouse button configuration.
    • Context-sensitive mouse pointers (loaded via Plugin API).
    • Exact copy of the way Opus scrolls images when dragging with the mouse or scrollbars, for consistency.
    • Alpha blended selection rectangle.
    • Full-screen support (including allowing the menu/toolbar to appear).
  • Animation.
  • Thumbnail alpha channel.
  • Streams (i.e. works with files within archives).
  • Configuration dialog (with layout code which adjusts to the system font & localised string lengths).
  • Adds items to the viewer menus.
  • Custom toolbar (i.e. in addition to the viewer's standard toolbar).
  • Recognises files by inspecting their contents.
  • XML config files (using the more simple of the two config methods provided by the Plugin SDK).
  • USB-Mode.
  • Unicode.
  • x64.

(For information from a user perspective, see the main page for the plugin.)

This plugin is obsolete and has been replaced by several newer plugins (some of which have source on this page), but perhaps the source code to it will still be of interest to someone.

The TGA, JPEG 2000, PNM, Rasterfile and Raw Digital Camera plugin source demonstrates the following:

  • Viewer & Thumbnails plugin.
  • Given a file path or stream, returns an HBITMAP. (The first, less powerful and more simple method described in the Plugin SDK.)
  • Handles multiple image formats in a single plugin. (I no longer think this is a good idea, though. Sharing code is okay but I think it should look like there is one plugin per format unless the formats are closely related. You could have one big DLL that does all the work and some little proxy DLLs which make it appear to be several plugins, for example. Makes it easier to replace individual plugins without replacing the entire thing, and easier for the user to configure things.)
  • Alpha channel support for (for the TGA type, at least).
  • Streams (i.e. works with files within archives).
  • Configuration dialog.
  • Registry-based config. (NOT recommended as it means the plugin cannot be used with USB-mode.)
  • Does not support USB mode, Unicode or x64.

I plan to completely re-write this plugin and replace it with several smaller plugins. A new JPEG2000 plugin has already been written, based on a different library. The Raw digital camera support will be changed to call a pre-made dcraw.exe instead of building the DCRaw code into the plugin as it has proven far too time consuming to convert the DCRaw code to thread-safe C++ every time it is updated. TGA alpha support will be rolled into the old sample TGA plugin which comes with the plugin SDK. PNM and RAS would be easy enough to re-write by hand (I don't want to be tied to CXImage anymore due to x64/Unicode issues and there not being much point in using it just for RAS/PNM). I may just abandon RAS/PNM as they don't seem to be used much and were only supported because DCRaw used to require them.

You will need CxImage to compile the JP2Raw plugin. CxImage itself comes with the other required libraries. Below is the CxImage configuration used to built the DLL. Also note that CxImage and all child projects must be changed to statically link to the Multithreaded C-Runtime.

	// CxImage supported features
	#define CXIMAGE_SUPPORT_ALPHA          1
	#define CXIMAGE_SUPPORT_DSP            0
	#define CXIMAGE_SUPPORT_LAYERS         0


	// CxImage supported formats


(For information from a user perspective, see the main page for the plugin.)

The Maya IFF plugin source demonstrates the following:

  • Viewer & Thumbnails plugin.
  • Given a file path or stream, returns an HBITMAP.
  • Alpha channel support.
  • Streams (i.e. works with files within archives).
  • Supports USB mode, Unicode and x64.

You may also find this plugin's source useful if you wish to add Maya IFF support to other programs. I took the iff2tga tool by Luke Tokheim, which is itself based on the Maya IFF for SGI GIMP plugin by Mike Taylor, and made the following modifications:

  • Fix for reading 24-bit images (RGB with no alpha).
  • Fix for decoding compressed tiles which are bigger than their uncompressed data. (I've got no idea why an encoder would produce such a beast but there's at least one out there that does so with some inputs. I've also got no idea how you would detect a compressed tile that happened to be the same size as the decompressed data.)
  • Abstracted the file IO so that you can pass memory buffers, streams, etc. to the decoder, as well as simple filenames. (Also allows for Unicode filenames.)
  • Converted global variables into instance data so that you can have concurrent instances of the reader.
  • A lot more error checking.
  • Switches to prevent the reader from wasting time/memory when you only want the image dimensions, or just the RGBA data, but not the z-buffer or blurvec or whatever.
  • Note that there is only decoding support, and no encoding, in my version of the code, nor in Luke Tokheim's, but if you go back to Mike Taylor's original code then you can get an encoder as well if you need one.

The Maya IFF plugin and source are released under the GPL v2.

(For information from a user perspective, see the main page for the plugin.)

The NFO plugin source demonstrates the following:

  • Viewer-only plugin. (No thumbnails.)
  • Creates its own window. (The second, more powerful and more complex method described in the Plugin SDK.)
  • Streams (i.e. works with files within archives).
  • Recognises files by extension only.
  • USB mode. (Trivial in this case as the NFO plugin has no configuration data and just has to say "Yes, I support USB mode.")
  • Unicode.
  • x64.

The NFO plugin's custom viewer window is based on a subcalssed edit control. The source to the NFO plugin could be a good starting point for a text-editor plugin, or if you want to see how custow-window plugins work in general. The GIFANim plugin also creates its own window but I would advise looking at the less complex NFO plugin first and only refering to the GIFAnim plugin if you want to know how to do some the more advanced things.

(For information from a user perspective, see the main page for the plugin.)

This plugin is obsolete and has been replaced by the Audio Tags plugin, but perhaps the source code to it will still be of interest to someone.

The Ogg Vorbis & FLAC plugin source demonstrates the following:

  • File information plugin. (Only returns information about file metadata. No viewer. No thumbnails.)
  • Streams (i.e. works with files within archives).
  • Does not support USB mode, Unicode or x64.

You can find the original version of the Targa (TGA) plugin in the Opus plugin SDK. The version in my source archive was based on the SDK version but is largely re-written. From Opus onwards it is my version, not the SDK version, that ships with the program.

If you are learning how to write an Opus viewer plugin for the first time, you may find it useful to look at both versions of the Targa plugin.

The version in the SDK is very self-contained in that all of the code is in one place, without a lot of utility code etc. making it difficult to know exactly what's going on. That's good for getting to grips with the basic viewer plugin API without getting bogged down in different layers and details.

On the other hand, my version of the Targa plugin is IMO better in many ways and shows several techniques and utility classes which can help you write better plugins with less effort. It takes a bit of effort to work out what my utility functions and wrappers do, but once you've learned them they will free you from writing that code again and again yourself. For example, my FileAndStream and Win32IOWrapper classes can help you write code which doesn't care whether it's passed a filename or an IStream, making it easy to create plugins that can view files within zip archives, FTP sites, etc.

My version of the Targa plugin is also a bit more complex because it supports more of the Targa format. (Alpha channels, pre-multiplied alpha, indexed colour and greyscale.) That makes the code better, of course, but those details really just get in the way if your aim is to learn the plugin API and not how to decode Targa images!

Another thing in my version that may be of interest is its use of basic template meta-programming to write generic code for dealing with combinations of pixel sizes/compressions without the overhead of lots of virtual function calls or function-pointer calls. (Compare the old and new Targa code and you should find the new code much faster because the compiler can inline the pixel buffer loops.) If you've never heard of template meta-programming before, here's a couple of pictures that might help you understand why it's used in the plugin.

(Note 1: There are lots of other reasons for using the technique and also lots of places where using it is a bad idea.)

(Note 2: If you test this yourself, remember to use an optimized release build. Debug builds are less likely to inline the function calls and discard the unused code.)

Source code & generated asm code.
Source code & generated asm code.
The two method calls are identical and unused code was removed.
Targa plugin source fragment.
Targa plugin source fragment.

(For information from a user perspective, see the main page for the plugin.)

The Text-File Thumbnails plugin source demonstrates the following:

  • Thumbnail-only plugin (no viewer).
  • Thumbnail alpha channels.
  • Tells Opus not to cache & scale its thumbnails so that they are regenerated when the thumbnail size changes (so the text is readable).
  • Maintains a process-wide cache of background images to speed up thumbnail generation. This cache has a "housekeeper" thread that frees data after it has not been used for a few seconds. The cache is created in the DVP_Init call and destroyed in the DVP_Uninit call. Critical Sections provide thread-safety.
  • Streams (i.e. works with files within archives).
  • Configuration dialog (with layout code which adjusts to the system font & localised string lengths).
  • Recognises files by inspecting their contents.
  • XML configuration file (using the more complex, more powerful config API in the Plugin SDK).
  • USB mode.
  • Unicode.
  • x64.