Archive
  • Ripsaw Series
  • Accelerated C++

Parks Computing

Pedagogy for the Autodidactic Programmer

Welcome to the personal web site of Paul M. Parks. This is where I keep my web development skills current while sharing snippets of code, utilities, libraries, and what little development wisdom I've gathered in my career. It's also where I show off my family, talk about my hobbies, or just pontificate from time to time.

If you find the articles interesting, or if you'd like more information on a particular topic, let me know [paul@parkscomputing.com].

Ripsaw Library Implementation

In this installment of the Ripsaw article series we'll start putting some actual code into an implementation of the COM interfaces that we specified in the last article. For those of you who just joined the series, Ripsaw is a log viewer for Windows, similar to the Unix tail utility. This series of articles details my rewrite of the application from the ground up.

So far, we have a test script implemented in JavaScript (or JScript, if you want to be really picky) that is intended to exercise our COM interfaces. There is a base interface that represents a generic log that a client might wish to follow, a more specific interface representing a file, and a dispinterface declaring the events that a Ripsaw library object can fire. In this article we'll start implementing these interfaces, with the goal of providing just enough functionality to allow us to execute the test script.

(Click the "Read the full article" link below for more.)
I'm implementing this project with Visual Studio 2010, which is still in beta as of this writing. You may download the solution, projects, and source code if you'd like to follow along, but if you haven't downloaded and installed the 2010 beta yet, you'll need to adapt the code to your IDE and C++ compiler of choice if you wish to build it (I recommend Visual Studio 2008).

Implementing the COM Interfaces

To briefly recap the design, there will be a Ripsaw COM library that exports objects representing sources of updates, and a Ripsaw client (such as a log viewer or a script) will create objects that fire events whenever the source changes its status. Now that we've specified the interfaces, it's time to implement them just enough to run the test script that we created in the last installment. I like to use ATL to implement the low-level, boilerplate code that's necessary for IUnknown and IDispatch, though once the wizard is done I usually take over and move all the generated code around. I've never liked round-trip development with code generators.

There are three interfaces (for now) that need to be implemented: IRipsawObject, IRipsawFile, and _IRipsawEvents. The first interface is the base interface for not only IRipsawFile but other potential interfaces that might be added later on as the library is developed. That means I want to create the implementation in such a way that I can easily extend it to other sources of data that Ripsaw might use. Unfortunately, if I took the code that the ATL wizard spat out, I'd have a really difficult time reusing it. I need to split the ATL code into a generic interface implementation and a concrete object implementation.

In ATL, when implementing an object derived from IDispatch, you usually get a class definition that looks like this:

class ATL_NO_VTABLE RipsawFile : 
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<RipsawFile, &CLSID_RipsawFile>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<RipsawFile>,
public CProxy_IRipsawEvents<RipsawFile>,
public IDispatchImpl<IRipsawFile, &IID_IRipsawFile, &LIBID_RipLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

That works fine, until it doesn't. If you ever want to derive a class from RipsawFile, you're going to run into problems. What I usually do is replace IDispatchImpl with my own template class that derives from IDispatchImpl and implements the interface that my object is going to expose. In this case, I want my object to expose both IRipsawFile and IRipsawObject, so I'll start by creating a generic template implementation of the base IRipsawObject interface. The template will be defined in a header file named IRipsawObjectImpl.h.

#pragma once
#include <comutil.h>

template <class Root, class T, const IID* piid = &__uuidof(T),
const GUID* plibid = &ATL::CAtlModule::m_libid,
WORD wMajor = 1, WORD wMinor = 0,
class tihclass = ATL::CComTypeInfoHolder>
class IRipsawObjectImpl :
public Root,
public ATL::IDispatchImpl<T, piid, plibid, wMajor, wMinor, tihclass>
{
public:
/* Internal public implementation */
IRipsawObjectImpl() : isOpen(FALSE)
{
}

virtual ~IRipsawObjectImpl()
{
}

public:
/* IRipsawObject interface */
HRESULT STDMETHODCALLTYPE get_Name(
__out BSTR* pName)
{
if (!pName)
{
return E_INVALIDARG;
}

{
ObjectLock(this);
*pName = NULL;
}
return S_OK;
}

HRESULT STDMETHODCALLTYPE get_IsOpen(
__out VARIANT_BOOL* pRetVal)
{
if (!pRetVal)
{
return E_INVALIDARG;
}

LONG retVal = 0;
InterlockedExchange(&retVal, isOpen);
*pRetVal = retVal ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}

HRESULT STDMETHODCALLTYPE Close()
{
_bstr_t fileName;

VARIANT_BOOL tmpIsOpen = VARIANT_FALSE;
get_IsOpen(&tmpIsOpen);

if (tmpIsOpen)
{
ObjectLock(this);
put_IsOpen(FALSE);
BSTR name = 0;
get_Name(&name);
Notify_Close(name);
SysFreeString(name);
}

return S_OK;
}

HRESULT STDMETHODCALLTYPE WaitForUpdate(
__in ULONG timeout,
__out BSTR* pNewData)
{
if (!pNewData)
{
return E_INVALIDARG;
}

*pNewData = NULL;
return S_OK;
}

protected:
/* Internal protected implementation */
HRESULT STDMETHODCALLTYPE put_IsOpen(
__in VARIANT_BOOL value)
{
InterlockedExchange(&isOpen, value);
return S_OK;
}

virtual HRESULT Notify_Open(BSTR fileName)
{
return S_OK;
}

virtual HRESULT Notify_Close(BSTR fileName)
{
return S_OK;
}

virtual HRESULT Notify_Update(BSTR updateData)
{
return S_OK;
}

LONG isOpen;
};


There is quite a bit going on in this template, so let's go over its various parts.

The Root Type

If you look back at the example class definition for RipsawFile, you'll see that it derived from CComObjectRootEx<CComMultiThreadModel>. This is because, when the wizard generated the code, the user selected the option to generate an object that was free-threaded. This is an implementation detail of the object, not of the interface, so that's the first thing we need to abstract away from our generic implementation of the IRipsawObject interface. The problem, however, is that CComObjectRootEx provides a lot of useful types that we should take advantage of, such as methods for guarding blocks of code that need to be serialized when accessed from multiple threads. What I decided to do was make Root a template parameter and derive the implementation from that type. The concrete implementation may provide any type for this parameter as long as it implements the definitions expected from that type, such as ObjectLock.

The IDispatchImpl Type

This is a class that is provided by ATL to implement the ugly details of IDispatch. While the average COM developer can (usually) implement a workable IUnknown rather easily, implementing IDispatch is a good deal more complicated. We know, based on our IDL, that IRipsawObject is derived from IDispatch, so this is an implementation detail that's appropriate for our generic implementation. Classes that derive from IRipsawObject will also pick up the IDispatch implementation. All of the template parameters to IRipsawObjectImpl after Root are passed along to IDispatchImpl.

Notify_Open, Notify_Close, and Notify_Update

All objects that implement the IRipsawObject interface are expected to fire events to notify clients about open, close, and update events. How these events are generated is an implementation detail best left to the concrete object implementation, but I may need to fire events from the generic implementation. I decided to create stub methods that will be overridden in the concrete implementation.

The Rest of the Code

The remainder of the header is devoted to minimal implementations of the methods and properties defined in the IRipsawObject interface. Most of these will be overridden in IRipsawFile and other derived interfaces. Note that the implementations use InterlockedExchange and ObjectLock to serialize access to internal data. This is because we want to allow the generic implementation to work for free-threaded object implementations, which may be called by more than one thread at a time. The ObjectLock type is expected to be provided by the Root template parameter.

I Could Use Some More Cowbell

This still isn't enough code to create a usable IRipsawFile implementation, but since that's the default interface that we need to expose from our COM object we'll have to derive another generic implementation from IRipsawObject that will let us finally execute our test script. We'll tackle that template class in the next installment in the series. If you want to cheat and peek ahead, look at the file IRipsawFileImpl.h in the source code.

Labels: , , , ,

Posted November 23, 2009 0 Comments

Ripsaw COM Interface, First Pass

In this installment of the Ripsaw article series we'll finally get to write some code. We've already gotten a pretty good idea about how we want to implement the core Ripsaw library, so now we're going to define enough of the COM interface that we can create a simple test script that will eventually be used to exercise the library.

(This is a long article, so click the "Read full article" link below for more.)
To be callable from script, the object's interface should be OLE-automation compatible, so we'll declare it as such in our IDL:

[
oleautomation,
uuid(2D486A73-E912-4078-9F38-678226E4A0BD),
dual,
pointer_default(unique)
]
interface IRipsawFile : IDispatch
{
}

The interface is also derived from IDispatch to support late-binding, which means that scripting languages can discover the methods and properties of the interface at runtime.

Before we can write the test script we'll need decide what basic methods a Ripsaw object needs to implement. A single Ripsaw object will represent a connection to a file, and the object will fire events whenever the file changes so that listeners can take an action based on the update (change the display, parse the update, etc.). At a minimum, then, we need to be able to open a file and close a file.

interface IRipsawFile : IDispatch
{
[id(1)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);

[id(2)] HRESULT Close();
}

The Open method will accept a path to a file and return a Boolean indicating the success or failure of the method call. The Close method will close any existing connection, and we won't bother to check for a return code from that method.

Now, it would probably be useful to be able to query a property of the object for the name of the file to which it is connected, and perhaps another property to check if the object is currently opened or closed. Let's add those properties:

interface IRipsawFile : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);

[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);

[id(3)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);

[id(4)] HRESULT Close();
}

Okay, not bad so far. We can open a file, query for the status, query for the name, and close the file. That's no good if we can't catch events fired by the object. We need to define a dispinterface that specifies the events that the object can fire at its clients:

[ uuid(6671C129-C761-42F3-AB2F-D12C33D95160) ]
dispinterface _IRipsawEvents
{
properties:
methods:
[id(1)] HRESULT Open([in] BSTR fileName);
[id(2)] HRESULT Close([in] BSTR fileName);
[id(3)] HRESULT Update([in] BSTR updateData);
};

The Open event fires when a file is opened successfully, of course. Close fires when a file is closed, and Update naturally fires when the file changes. The Update notification will also pass along the data that was gathered from the last file update.

Okay, based on those interfaces, our script might look like this:

var ripsaw = WScript.CreateObject("PMP.RipsawFile", "ripsaw_");

if (ripsaw)
{
ripsaw.Open("testfile.log");

if (ripsaw.IsOpen)
{
WScript.Echo("Ripsaw file is open: '" + ripsaw.Name + "'");

/* Uh-oh... */

ripsaw.Close();

if (!ripsaw.IsOpen)
{
WScript.Echo("Ripsaw file is closed: '" + ripsaw.Name + "'");
}
else
{
WScript.Echo("ERROR: Ripsaw file reported open");
}
}
else
{
WScript.Echo("ERROR: Ripsaw file reported closed");
}
}
else
{
WScript.Echo("ERROR: Failed to create Ripsaw file");
}

function ripsaw_Open(fileName)
{
WScript.Echo("ripsaw_Open(" + fileName + ")")
}

function ripsaw_Close(fileName)
{
WScript.Echo("ripsaw_Close(" + fileName + ")")
}

function ripsaw_Update(data)
{
WScript.Echo("ripsaw_Update(" + data + ")")
}

First of all, I'll explain what all this means for those of you that are new to JavaScript — err, JScript — under Windows Script Host (WSH). The first line creates a COM object based on its ProgID, or programmatic identifier. This is a string that resolves, through the registry, to a GUID that uniquely identifies a COM library that provides the requested functionality.

The second parameter to CreateObject tells the scripting engine to wire events provided by the object to functions that start with the specified prefix. In our script, we want to handle the Open, Close, and Update notifications, so we created functions named ripsaw_Open, ripsaw_Close, and ripsaw_Update to catch these events. Since they all start with the "ripsaw_" prefix, the script engine will call them when any of the corresponding events are fired by our object.

If you looked carefully at the script, you noticed that I inserted a comment: "Uh-oh." That's because the script has no way of sitting and waiting for an update other than looping in a tight loop, and that is almost never a good idea. The script would waste CPU time doing nothing while we wait for the file to be updated. In other languages we have access to message loops, WaitForSingleObject, and other means of yielding CPU until an event occurs, but not in WSH. We need something better. What we'll do is add a method to our object which, when called, will do absolutely nothing until an update occurs. We'll call it WaitForUpdate.

interface IRipsawFile : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);

[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);

[id(3)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);

[id(4)] HRESULT Close();

[id(5)] HRESULT WaitForUpdate(
[in] ULONG timeout,
[out,retval] BSTR* pNewData);
}

The first parameter is a timeout, in milliseconds, so that the function will return if no updates arrive in the specified amount of time. We'll take a page from Win32 and say that a value of 0xFFFFFFFF will tell the method to wait forever. If the timeout expires, the method will return a null BSTR; otherwise, the return value will be the new data from the last file update. We'll modify the heart of our script to call the new method:

      WScript.Echo("Ripsaw file is open: '" + ripsaw.Name + "'");

var timeout = 5000;
WScript.Echo("Waiting for data...");
var newData = ripsaw.WaitForUpdate(timeout);

if (newData)
{
WScript.Echo("Waited for data:", newData);
}
else
{
WScript.Echo("Timed out after waiting for data for " + timeout + " milliseconds");
}

ripsaw.Close();

In a real script we'll probably call WaitForUpdate in a loop, but for now we'll just wait for one update before exiting.

Generalizing the Interface

This looks good for files, but perhaps later on we'd like to extend Ripsaw to listen to other sources of events, like the Windows Event Log (the first Ripsaw actually did that for a while, but I backed out the feature to focus on files). It would be a good idea to go ahead and factor out the common methods and properties into a base interface that could be extended by other event sources.

[
oleautomation,
uuid(0270FDFF-56AF-42ec-9971-AE8DCB0DAB36),
dual,
pointer_default(unique)
]
interface IRipsawObject : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);

[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);

[id(3)] HRESULT Close();

[id(4)] HRESULT WaitForUpdate(
[in] ULONG timeout,
[out,retval] BSTR* pNewData);
};

[
oleautomation,
uuid(2D486A73-E912-4078-9F38-678226E4A0BD),
dual,
pointer_default(unique)
]
interface IRipsawFile : IRipsawObject
{
[id(10)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);
};

Later on, if we define an interface for listening to event logs, we'll derive that interface from IRipsawObject and define an Open method specific to event logs. We'll also assign a special ProgID to event log objects so our client applications can create them:

var ripsaw = WScript.CreateObject("PMP.RipsawEventLog", "ripsaw_");

Next Steps

Now we have an interface, and we have a script to exercise that interface. What we need next is some real code to implement the interface and a binary that the script can load and run. We'll start on a preliminary implementation in the next article. Until then, you can download the IDL and the script file and look them over. Please leave a comment with any suggestions you may have.

Labels: , , ,

Posted November 9, 2009 0 Comments

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]