Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Automation with Juno

In this first tutorial, we'll learn how to generate a source module from a COM type library. Then we'll use it to automate the Windows shell.

Step 1: Convert the type library

Open up a console window and use Tlbimpd to create an import module from the Microsoft Shell Controls and Automation library - otherwise known as shell32.dll.

browser:docs/tlbimpd.shell32.png

The above command generates a module named shell32 in a file named shell32.d. The /comments argument just includes extra information found in the type library that can't be represented in code, for example member identifiers and certain IDL attributes. It also adds documentation to the module if the type library provides it.

Step 2: Write a program to automate the shell

In your code editor, create a new source file called shellauto.d and add the following import declaration:

import juno.com.core, shell32, bstr = juno.com.bstr;

juno.com.core contains the bare essentials to work with COM objects; shell32 is the module we generated in Step 1.

Our demonstration program will list the files on your Desktop. First, we need to create an instance of the Shell coclass. If Shell wasn't a COM class, but an ordinary class that implemented an interface named IShellDispatch, you'd write:

  IShellDispatch shell = new Shell();

But to create an instance of a coclass, you use CoCreateInstance. Juno provides a more convenient way that's similar to newing a class:

  IShellDispatch shell = Shell.coCreate!(IShellDispatch);

Note that there's no need to call CoInitialize or CoUninitialize - Juno takes care of starting and shutting down COM for you.

There's one other thing to note with the above code: it leaks memory. Unlike typical D objects, COM objects aren't garbage collected. Instead, COM uses reference counting to manage the lifetime of an object. In D, as in C++, we have to explicity decrement the reference count. Usually, this is done by calling Release() once you've finished using an object. But it's easy to forget to do this. Juno defines a function that you can use nearer the declaration instead of at the end of a scope: releaseAfter

  IShellDispatch shell = Shell.coCreate!(IShellDispatch);
  releaseAfter (shell, {
    // Use the shell variable.
  }); // shell gets released here.

We'll employ the above technique throughout the code to ensure our objects are released. The remainder of the code is fairly straightforward. Here's the complete listing:

void main() {
  IShellDispatch shell = Shell.coCreate!(IShellDispatch);
  releaseAfter (shell, {

    // Get a reference to the Desktop folder.
    Folder folder;
    shell.NameSpace(toVariant(ShellSpecialFolderConstants.ssfDESKTOP), folder);
    if (folder !is null) {
      releaseAfter (folder, {

        // Get the collection of FolderItem objects.
        FolderItems items;
        folder.Items(items);
        if (items !is null) {
          releaseAfter (items, {

            // Iterate through all the FolderItem objects.
            int count;
            items.get_Count(count);
            for (int i = 0; i < count; i++) {
              FolderItem item;
              items.Item(toVariant(i), item);
              if (item !is null) {
                releaseAfter (item, {

                  // Get the name of the item.
                  wchar* bstrName;
                  item.get_Name(bstrName);

                  // Convert the BSTR to a UTF-8 string. bstr.toString frees the BSTR.
                  char[] name = bstr.toString(bstrName);
                  // Display the result.
                  writefln(name);
                });
              }
            }
          });
        }
      });
    }
  });
}

Step 3: Compile and run

Add shell32.d and shellauto.d to DMD's command line, and compile. When you run shellauto.exe, it will output the names of each item on your Desktop.

Considerations

So how do you know which applications have programmable interfaces? Perhaps the simplest way is to look the application up in OleView, which displays a list of registered type libraries along with any classes and interfaces it contains. You can also find out the GUID and path of a type library, either of which can be passed to Tlbimpd to generate a D module.

If you run into a Stack Overflow when running the example, make sure that you compile with optimizations turned on (-O switch).