Implementing Interoperable Objects

And in this corner...

Ray Valdés

Ray is senior technical editor at DDJ and can be reached at rayval@well.sf.ca.us.


If you have carefully followed the previous articles on future-oriented technologies such as CORBA, SOM, COM, OLE, OpenDoc, PDO, ADB, and TalAE, you may find that your eyes have glazed over a wee bit. Like many DDJ readers, you perhaps realize that even the cleanest design can exhibit a surprising number of blemishes, or even flaws, when cast into the form of a working program. So you may be wondering at this point, "Where's the code?"

In this article, I'll try to provide a concrete basis for evaluation of some of the technologies discussed previously by presenting side-by-side implementations of similar functionality. This article is not a "shootout" in the sense of having a panel of judges evaluate each competing entry and arrive at a winner. As much as possible, you are the judge, and you can weigh the merits of each competitor according to your needs. A more accurate word for this is "bakeoff"--the same word used by the old Internet tradition of providing competing implementations at interoperability conferences. However, in a year when the game DOOM II shipped 500,000 copies during its first week, culinary metaphors lose out.

Setting the Time and Place

Earlier this year, DDJ made a request to each of the vendors whose technology offerings are described in this Special Report. The request was to implement a small program specification that could highlight some aspects of the technology that might not be covered by a narrative description.

For a variety of reasons, not all vendors could participate. Some packages are still in the confidential stages of development, scheduled for release next year. For other vendors, prior commitments prevented allocating resources for this project. And CORBA, from OMG, is a specification rather than an implementation. Although the CORBA spec is supported by more than a dozen vendors, each implementation is perhaps different enough (variations that add value and differentiation) that it would be a slight to the others to pick only one candidate.

Nevertheless, we do present here the two object-model technologies considered to be the leading contenders in the interoperable object wars: Microsoft's Common Object Model (COM) and IBM's System Object Model (SOM). In addition, there is a separate implementation from Microsoft that illustrates the higher-level services of OLE, the reason for which will be explained shortly.

Note that these are not the only viable approaches to doing distributed computing with objects today. As mentioned previously, a number of vendors are offering solutions that solve enough aspects of the interoperable object problem that you can build real-world systems today. These vendors include companies such as IONA, Orbix, Forte, Visix, ILOG, Peerlogic, and Ochre, among others. In general, these approaches do not attempt to address the entire problem. For example, the package from ILOG allows you to build distributed applications as long as you stick with C++ (that is, no attempt is made to be language neutral). Likewise, the Galaxy framework from Visix offers a facility known as "DAS" (short for "Distributed Application Services") that allows distributed processing if both client and server are built with the framework. Unfortunately, even if a proposed solution were to meet all requirements, the reality is that sufficient market presence and company resources are needed in order to be considered a viable contender in these platform wars. For this reason, it's no surprise that the major players in the interoperable-object wars are the large system vendors (IBM, DEC, Sun, HP, and Apple) and cash-rich, market-dominating corporations (Microsoft), rather than small software houses with limited resources.

Choosing the Weapons

The specification we provided to vendors was small and straightforward: a simple phone-directory database that manages customer names and telephone numbers. This database can be queried in two simple ways, by name and by number, reflecting the two fields that constitute the database. The goal here is not to show off prowess in constructing databases. Instead, the basic issue is how to take an existing chunk of application functionality and package it in the form of an interoperable object. For each vendor's technology, we want to show the amount of "glue" (APIs, constructs, libraries, tools, mechanisms) necessary to make a simple object interoperable.

To this end, DDJ provided the "legacy code" that implements the database. The requirements are so small, it can be implemented in a half-page of C code in a few moments, so we called it "the one-minute phone directory." Even so, the basic concerns related to interoperable objects can scale up by several orders of magnitude, in both code size and capacity.

The interface between the database and its clients consists of four services in Table 1: initialize, terminate, lookup-by-name, lookup-by-number. The nonobject implementation of this interface that DDJ provided to vendors is in Listings One, Two, and Three . These source files are all compiled and linked into a single DOS program. The actual client/server partitioning is partly a figment of our imagination. The "server" is all of 67 lines of C code, in Listing Two. The "client" is in Listing Three, less than 25 lines. Example 1 shows the procedural declaration of the interface. In this implementation, everything is static, hard-wired, and resident in memory, to minimize the guts and maximize the visibility of the glue.

There are several ways that a developer would package this implementation. Currently, it is easy to package it as a procedural library (an OBJ module), a dynamic library (DLL), or a procedural component such as a VBX. This subsystem can also serve as a basis for a conventional object-oriented implementation, by wrapping a class around it. The interface would remain basically the same, except for adding constructor and destructor member functions (if you're using C++) and turning the API entry points into member functions.

To turn this code into an interoperable object, there are three possible approaches:

Given the time requirements, the third option was not practical for any vendors. As stated previously, both IBM and Microsoft provided a basic, nonvisual implementation illustrating their respective object models. In addition, Microsoft implemented the second alternative listed here, a visual implementation of an embeddable application component, as an OLE Custom Control using the Microsoft Foundation Classes (MFC) library. The rest of this article will discuss each of these implementations in turn. Because of space requirements, not all code can be shown here, only the key sections. The complete listings for each implementations are available in electronic form; see "Availability," page 3.

Microsoft COM

Sara Williams of Microsoft implemented the phone-directory database as a pure Component Object without a UI and without any OLE interfaces, in order to emphasize the distinction between the underlying object model and the higher-level OLE services. The non-UI object implements the desired client/server interface on a cross-process Component Object. A cross-process object, naturally enough, lives in a separate address space from its client and is packaged in an EXE file. An object can also be implemented as an in-process server (packaged in a DLL).

To a client application, a COM Object is simply a COM Object--it is up to the implementor to decide whether to make the Component Object an in-process or cross-process object. In either case, the code for the client application remains the same. At present, there is no available version of COM that supports interaction between objects across a network. Microsoft has promised availability of distributed COM/OLE in the not-too-distant future, and also stated that no changes to client code will be necessary.

There are three aspects to the COM implementation: interface, client code, and server code. The interface is formally specified using IDL, as shown in Example 2. This specification is fed to the MIDL compiler, which generates the stubs and proxies used by client and server. The COM technology demands that the developer create certain required interfaces and have certain run-time behavior in order to implement an interoperable object. Here is an excerpted narrative from Sara Williams that explains how she accomplished this:

First, I wrote a minimal COM object--one that just supports IUnknown and has a ClassFactory. This isn't very exciting, but it was the first step. I made sure that my object could be correctly instantiated, and that it would correctly destroy itself at the correct time. I used OLE2VIEW as a client here, because it will instantiate an object and then release it.

Second, I wrote the IDL file that defines my custom interface, and used the MIDL compiler to compile it into the proxy/stub DLL.

Third, I expanded my simple COM object to implement my custom interface. This was pretty straightforward. In C++, I changed my class so that it now derives from ILookup, instead of IUnknown, and I added the two methods (non-IUnknown) to my implementation.

Fourth, I wrote a client app that creates an instance of my object, and then uses my custom interface to find information in the server's phone database. A call to CoCreateInstance has COM instantiate a server object and return a pointer to me to use. To call the custom interface methods (LookupByName and LookupByNumber), I just hard-coded input values to make sure the call was being made correctly. Once I got it working, I added a UI to get the value from the user. When the user exits the application, I call Release on the object so that it can be freed at the appropriate time.

The client in this example is a small Windows app. The complete client code is not shown here, but is available electronically. The most interesting parts of the client code are in Listing Four . The class declaration for the server object is shown in Example 3. In addition, there are three principal source modules for the server: app.cpp (which has WinMain and initialization), obj.cpp (a simple IUnknown-based object), and icf.cpp (class factory). These are shown in Listings Five, Six, and Seven, respectively, in slightly abridged form. Not shown is the file pdserver.reg, a small registration file that gets merged into the Windows registry, so that COM knows what type of server mine is, and where to find it.

IBM SOM

The comparable SOM implementation of the phone-directory database is by Charles Erickson, a developer in the SOMObjects product group at IBM Austin, Texas. Erickson turned the DDJ phone-directory code into a SOM object, which can be either local to the main process or remote. The location of the SOM object is determined by its registration in the Implementation Repository. If the PhoneDir class is not registered in the Implementation Repository, the SOM object will exist local to the process. If the PhoneDir class is registered, the object will exist in the server with which the PhoneDir class has been registered. The server process may run on the same system as this client program, or on a networked system.

Unlike COM, which does not yet exist in a distributed version, the code in IBM's example works with both vanilla SOM and its distributed flavor, DSOM. Charles Erickson's narrative states:

This code was written in such a way as to allow the PhoneDir object to be created either in the same process as the client program (main) or to be distributed in a remote process without recompiling the client application_. As a result of providing this flexibility in the location of the PhoneDir object, there is some scaffolding or glue code designed to hide some of the current seams in SOM's local/remote transparency. These seams are related to memory management and object life cycle. In subsequent releases of the SOMobjects Toolkit, this scaffolding code will be absorbed behind the CORBA life cycle and seamless memory management APIs. As a result, new applications can take advantage of complete local/remote transparency, while applications written to the old APIs will continue to work unchanged.

You can see these "seams" in Listing Eight , which is the SOM/DSOM client for the phone-directory object. There is a static Boolean variable called isdsom, which is set in the PhoneDirInitialize function, depending on the class of the server. At program-termination time, there are some slight differences in the code that frees memory and destroys objects.

The SOM implementation only requires three source modules: the IDL file shown in Example 4, the client source in Listing Eight, and the PhoneDir server source in Listing Nine . The appropriate header file (PhoneDir.pxh) is generated automatically when the IDL file is run through the SOM compiler. The SOM compiler also generates a template for the PhoneDir code.

You can judge the results for yourself from the listings and examples here, but, in general, the code for the SOM/DSOM example is shorter and seems to require less "glue" than the COM case. This is partly because SOM provides more services at run-time and does not require you to implement things like class factories. Even so, as you can see from the server code, a fair number of lines of original code needed to be altered. And, as you can see from the interface declaration in Example 4, IDL usage in SOM is a bit more convoluted than the COM equivalent. Nevertheless, it seems fair to say that, if you are working purely at the object model level, and you are not using an application framework, and you are perhaps using a language other than C++, working with SOM seems easier and more straightforward than COM. In addition, third-party tools such as C++ compilers from MetaWare and Watcom provide direct-to-SOM, which further reduce the pain.

Microsoft OLE

In addition to the COM example shown previously, Microsoft also provided a visual implementation of the phone-directory example in the form of an OLE Custom Control. This implementation was done by Steve Ross of Microsoft, using tools such as Visual C++ and the MFC framework to automate the process of constructing a high-level application component. In a convincing demonstration of the power of these tools, Ross was able to complete his implementation more quickly and with less manually produced lines of code than the COM example implemented by Sara Williams, which required more bare-API programming.

To implement this example, Ross created an OLE control that is a subclassed Windows list box. This subclassed list box is used as a visual front end to access the phone-directory database and display its data. In addition, the example uses the notification machinery in OLE controls to fire off an event, NameNumberChanged, when the user changes the selection. This is an important difference between OLE controls and the standard list-box control. The component also implements methods (GetNameFromNumber and GetNumberFromName) for accessing the database routines (phonedir_LookupByNumber and phonedir_LookupByName) provided by DDJ. In contrast to the earlier SOM and COM examples, in which the original server code was basically rewritten, Ross's version shows how OLE controls can encapsulate legacy code. Other aspects of Ross's implementation include some read-only properties for inspecting the state of the object: CurrentNumber and CurrentName.

Ross's example brings to bear a number of key tools and technologies from Microsoft: Visual C++ Versions 1.5 and 2.0, the OLE Control Development Kit, the Microsoft Foundation Classes Versions 2.5 and 3.0 (included in the Visual C++ package), and the Control Wizard facility that is also part of the Visual C++ package. In addition, Ross used Microsoft Access to quickly design a forms-based user interface that shows off all aspects of the OLE control, including its properties, methods, and events; see Figure 1.

Ross provided an extensive narrative describing the implementation process, available with the electronic form of the listings. The following excerpt describes his approach:

To start an OLE Custom Control, the first step is to describe it to the Control Wizard [in Visual C++]. The Wizard then generates much of the code necessary to implement the control. For the purposes of this document, Visual C++ Version 2.0 will be discussed, although exactly the same steps work for Visual C++ 1.5 (with the OLE Custom Control Development Kit installed).

Before adding any implementation at all, you need to define the interface to your OLE Custom Control. This is most easily done using the Class Wizard. [After adding the methods GetNameFromNumber and GetNumberFromName], the properties are added using ClassWizard as well_. The last remaining part of the interface to this control is to add an event that is triggered when the user changes selection in the listbox. This [NameNumberChanged] event will not only notify the container that the selection has changed, but it will also save the container some time and pass the new name and number as event parameters.

This OLE control implementation consists of many files, totaling about 900 lines of code, a nine-fold increase over the original C-language source. However, Ross emphasizes, "There are only 34 lines of user-supplied code to provide the encapsulation of the phone database in an OLE Custom Control object (35, including the declaration of OnDrawMetafile in DDJDECTL.H)." The implementation for the methods, events, and properties in this control reside entirely in the file DDJDECTL.CPP (see Listing Ten), which indicates the lines that were manually added. Lines added without using the Wizard tools are denoted using the ==> symbol at the left margin. Example 5 presents the ODL file that is the source for the MKTYPLIB tool used to produce a TLB type library file. This type library file then becomes a resource for the OLE control.

Conclusion

The technologies covered in this article comprise the two principal contenders in the interoperable-object wars at present. Although the base code that DDJ provided to vendors was designed to be representative of much larger-scale projects, drawing conclusions from such a small program is still a bit risky. Evaluating a platform technology requires a certain amount of "living together" over a period of time. Even so, it seems apparent that, working at the object-model level, COM demands a bit more effort on the part of the programmer than SOM. However, as you can see from the listings, both examples did require a substantial rewrite of the "legacy code" provided to participants.

It is not clear how much, if at all, a programmer will be working at the object-model level. The ultimate goal of interoperable-object computing is compound documents that provide rich functionality by way of embedded components. From the articles in this Special Report, it appears that these technologies are complex enough that you would not undertake an implementation of compound documents without a tool or framework.

This is where the view gets murkier, because many of the higher-level tools or frameworks have not yet been released. And where some of these technologies fit needs clarification. For example, both OpenDoc and TalAE are compound-document technologies backed by IBM, and both use SOM as the underlying object model. Both are scheduled for initial release in the same time frame (early 1995). Yet, the connection between these two systems seems tenuous at best. Both cannot prevail, so choosing one over the other means no more than a 50 percent chance of picking the winner.

And then there's OLE. With its introduction of the OLE suite of technologies, Microsoft has dramatically increased the burden that rests on the shoulders of Windows programmers. Most observers agree that the size and complexity of OLE programming interface is at least equivalent to the Windows 3 API. Kraig Brockschmidt's introductory book on OLE runs almost 1000 pages, and does not even get into the subject of OLE Automation. However, it seems that Microsoft has learned valuable lessons from the early days of Windows programming, when developers had to chip away at a monolithic API with only the crudest of implements. The visual implementation of the phone-directory database presented in this article is a striking example of the power of Microsoft's tool suite.

Nevertheless, there remains a lingering uneasy feeling that the 35 lines of manually written code might be precariously perched on a pyramid that consists of an estimated 25,000 lines of OLE-specific code in MFC, plus an estimated 55,000 lines of non-OLE-specific MFC code. Microsoft states that developers should choose Visual C++ as an OLE implementation vehicle because MFC contains tens of thousands of lines of C++ code "that you don't have to write." However, if there's a bug, you might not escape having to trace through (and thoroughly understand) this large body of code.

In a recent presentation to the Software Entrepreneur's Forum (SEF) of Silicon Valley, Mark Ryland of Microsoft said that Microsoft has "bet the company on OLE." If you follow the computer industry, you can see that all sectors of the company have been marshaled to work on, implement, apply, support, and evangelize about OLE. In some cases, the pervasiveness of OLE has been overstated by Microsoft evangelists (for example, the extent to which OLE services are used in the Windows 95 environment is something of a myth, as revealed in "A Milestone on the Road to Chicago," Dr. Dobb's Developer Update, August 1994).

If sheer body mass were the sole indicator of which side will prevail in the platform wars, Microsoft would be the uncontested winner. One small indicator of Microsoft's commitment to OLE and COM is its level of participation in this Special Report, which outweighed all rival efforts by a factor of four or five. This fact has little to do with technology, but merits some consideration if you are making a strategic platform decision. In his presentation, Mark Ryland also said, "OLE will prevail because the history of the computing industry shows that the first plausible solution wins." Microsoft clearly believes it has such as solution. Whether you buy this argument depends on how you define "plausible," and on how much that definition has to do with business issues, as well as technology issues.

Figure 1 A front end to the phone-directory component built with OLE controls and Microsoft Access.

Table 1: Interface spec for the one-minute phone-directory database.

 Function           Purpose

 Initialize()       Called at program startup to initialize subsystem.
 LookupByName()     Given a name, returns corresponding phone number.
 LookupByNumber()   Given a number, returns corresponding customer name.
 Terminate()        Called at program termination
                    to do cleanup.

Example 1: The procedural interface to the phone directory, as declared in a C header file.

public bool  entrypoint   phonedir_Initialize       (void);
public lpstr entrypoint   phonedir_LookupByName     (lpstr name);
public lpstr entrypoint   phonedir_LookupByNumber   (lpstr number);
public void  entrypoint   phonedir_Terminate        (void);

Example 2: Declaration of phone directory interface in Microsoft IDL.

[   object,
    uuid(c4910d71-ba7d-11cd-94e8-08001701a8a3),
    pointer_default(unique)
]
interface ILookup : IUnknown
{
    import "unknwn.idl";
    HRESULT LookupByName(   [in] LPTSTR lpName,
                           [out, string] WCHAR **lplpNumber);
    HRESULT LookupByNumber( [in] LPTSTR lpNumber,
                            [out, string] WCHAR ** lplpName);
}

Example 3: Declaration of server class in COM implementation.

class CPDSvrObj : public ILookup
{
private:
    int m_nCount;                    // reference count
    CPDSvrApp FAR * m_lpApp ;        // pointer to app object so we can
                                    // tell it when we've been destroyed.
    DWORD m_dwRegister;              // Registered in ROT
    record theDatabase[MAX_RECORDS];  // phone book
public:
    // IUnknown methods
    STDMETHODIMP         QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
    STDMETHODIMP_(ULONG) AddRef ();
    STDMETHODIMP_(ULONG) Release ();
    // IPhoneDir methods
    STDMETHODIMP     LookupByName  (LPTSTR lpName,  TCHAR **lplpNumber);
    STDMETHODIMP     LookupByNumber (LPTSTR lpNumber, TCHAR **lplpName);
    // construction/destruction
    CPDSvrObj(CPDSvrApp FAR * lpApp);
    virtual ~CPDSvrObj();
    // utility functions
    BOOL Initialize  (void);
    void CreateRecord (int i,LPTSTR lpName,LPTSTR lpNumber);
};

Example 4: Declaration of phone-directory interface in SOM IDL.

interface PhoneDir : SOMObject
{
#ifdef __PRIVATE__     // Phone directory implementation details
    struct record {
       string name;
       string phone_number;
    };
    const long MAX_RECORDS = 5;
#endif
    // Operations on a PhoneDir
    string LookupByName(  in string name  );    // given a name, return number
    string LookupByNumber(in string number);    // given number, return name
    void   Initialize(inout somInitCtrl ctrl);  // Object initializer.
#ifdef __PRIVATE__
    // Return a new phone directory record, given the name and number
    record CreateRecord(in string name, in string phone_number);
#endif
#ifdef __SOMIDL__
    implementation {
        // Class modifiers:
        // releaseorder is for upward compatible release management.
        releaseorder: LookupByName,
                      LookupByNumber,
                      Initialize,
#ifdef __PRIVATE__
                      CreateRecord;
#else
                      Internal1;
#endif
        memory_management = corba;          // caller owns returned memory
        function_prefix   = phonedir_;      // language bindings directive
        dllname           = "phonedir.dll"; // class library
#ifdef __PRIVATE__
        // Phone directory implementation details
        sequence<record, MAX_RECORDS> theDatabase;
#endif
        // Method modifiers:
        Initialize:     init;             // this method is an initializer
        somDefaultInit: override, init;   // default initializer
  };
#endif /* __SOMIDL__ */
};

Example 5: Interface specification used by OLE Custom Control implementation.

[   uuid(AF3B752C-89D0-101B-A6E4-00DD0111A658), version(1.0),
    helpstring("Ddjdemo OLE Custom Control module")
]
library DdjdemoLib
{
    importlib(STDOLE_TLB);
    importlib(STDTYPE_TLB);
    //  Primary dispatch interface for CDdjdemoCtrl
    [ uuid(AF3B752A-89D0-101B-A6E4-00DD0111A658),
      helpstring("Dispatch interface for Ddjdemo Control") ]
    dispinterface _DDdjdemo
    {
        properties:
            // NOTE - ClassWizard will maintain property information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_PROP(CDdjdemoCtrl)
            [id(1)] BSTR    CurrentName;
            [id(2)] BSTR    CurrentNumber;
            //}}AFX_ODL_PROP
        methods:
            // NOTE - ClassWizard will maintain method information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_METHOD(CDdjdemoCtrl)
            [id(3)] BSTR    GetNameFromNumber(BSTR    szNumber);
            [id(4)] BSTR    GetNumberFromName(BSTR    szName);
            //}}AFX_ODL_METHOD
            [id(DISPID_ABOUTBOX)] void AboutBox();
    };
    //  Event dispatch interface for CDdjdemoCtrl
    [ uuid(AF3B752B-89D0-101B-A6E4-00DD0111A658),
      helpstring("Event interface for Ddjdemo Control") ]
    dispinterface _DDdjdemoEvents
    {
        properties:
            //  Event interface has no properties
        methods:
            // NOTE - ClassWizard will maintain event information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_EVENT(CDdjdemoCtrl)
            [id(1)] void NameNumberChanged(BSTR szName, BSTR szNumber);
            //}}AFX_ODL_EVENT
    };
    //  Class information for CDdjdemoCtrl
    [ uuid(AF3B7529-89D0-101B-A6E4-00DD0111A658),
      helpstring("Ddjdemo Control") ]
    coclass Ddjdemo
    {
        [default]         dispinterface _DDdjdemo;
        [default, source] dispinterface _DDdjdemoEvents;
    };
    //{{AFX_APPEND_ODL}}
};

Listing One

/****************************************************************
 > PHONEDIR.H -- Header file for PHONEDIR, the one-minute phone 
 > directory database.                               by Ray Valdes.
 >***************************************************************/

/****************************************************************
 > The following are some generically useful #defines and typedefs,
 > set in all lowercase just to be different.
 >***************************************************************/
#ifndef entrypoint
#define entrypoint   _far pascal
#define public
#define private static
#define nil 0
typedef char _far * lpstr;
typedef int bool;
#define true 1
#define false 0
#endif

/******************This is the PhoneDir API***********************/

public bool  entrypoint   phonedir_Initialize       (void);
public lpstr entrypoint   phonedir_LookupByName     (lpstr name);
public lpstr entrypoint   phonedir_LookupByNumber   (lpstr number);
public void  entrypoint   phonedir_Terminate        (void);

/*******************End of PHONEDIR.H****************************/

Listing Two

/**********************************************************************
 > PHONEDIR.C --the one-minute phone directory database. by Ray Valdes.
 >*********************************************************************/

#include <string.h>   
#include "phonedir.h"

/****************************************************************
 > This sets up the database structure, a fixed size array of
 > fixed size records in memory, initialized at startup-time
 > by hard-coded program statements (can this get any simpler?)
 >***************************************************************/
typedef struct
{   lpstr name;
    lpstr phone_number;
} record;

#define MAX_RECORDS 5
static record theDatabase[MAX_RECORDS];

/****************************************************************/
private void phonedir_CreateRecord(int arrayindex,lpstr name,lpstr phone);

/****************************************************************/
public bool entrypoint       
phonedir_Initialize(void)
{   phonedir_CreateRecord(0,"Daffy Duck",         "310-555-1212");
    phonedir_CreateRecord(1,"Wile E. Coyote",     "408-555-1212");
    phonedir_CreateRecord(2,"Scrooge McDuck",     "206-555-1212");
    phonedir_CreateRecord(3,"Huey Lewis",         "415-555-1212");
    phonedir_CreateRecord(4,"Thomas Dewey",       "617-555-1212");
    return true; /* success */
}
/****************************************************************/
private void       
phonedir_CreateRecord(int i,lpstr name,lpstr phone_number)
{   theDatabase[i].name         = name;
    theDatabase[i].phone_number = phone_number;
}
/****************************************************************/
public lpstr entrypoint  
phonedir_LookupByName(lpstr name)
{   int i;
    for(i=0; i < MAX_RECORDS; i++)
    {   if(_fstrcmp(theDatabase[i].name,name)==0)
             return theDatabase[i].phone_number;
    }
    return nil;
}
/****************************************************************/
public lpstr entrypoint 
phonedir_LookupByNumber(lpstr number)
{
    int i;
    for(i=0; i < MAX_RECORDS; i++)
    {   if(_fstrcmp(theDatabase[i].phone_number,number)==0)
              return theDatabase[i].name;
    }
    return nil;
}
/****************************************************************/
public void entrypoint 
phonedir_Terminate(void)
{    return;
}
/*********************End of PHONEDIR.C**************************/

Listing Three

/****************************************************************
 > MAIN.C -- Sample client for the one-minute phone directory. by Ray Valdes.
 >***************************************************************/


int main(int argc,char*argv[])
{
    char *name,*number;
    (void) phonedir_Initialize();

    // do a lookup by name
    name = "John Doe";  number = phonedir_LookupByName(name);
    if(number) printf("%s's number is %s.\n",name,number);
    else       printf("%s does not have a number listed.\n",name);

    // do a lookup by number
    number = "408-555-1212";  name = phonedir_LookupByNumber(number);
    if(name)   printf("%s's number is %s.\n",name,number);
    else       printf("The phone number %s has not been assigned.\n",number);

    phonedir_Terminate();
}
/********************End of MAIN.C*************************************/

Listing Four

//**********************************************************************
// CLIENT.C -- a Windows client for phone directory database (excerpted)
//**********************************************************************

// This is your basic WinMain routine, plus OLE lib initialization
int APIENTRY WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
    MSG msg;
    if (!hPrevInstance && !InitApplication(hInstance))
            return FALSE;     
    //o see if we are compatible with this version of the OLE libraries
    DWORD  dwVer = OleBuildVersion();
    if (HIWORD(dwVer) != rmm || LOWORD(dwVer) < rup)      
    return FALSE;
    if (NOERROR == OleInitialize(NULL))    // initialize the OLE libraries
    fOleInitialized = TRUE;
    if (!InitInstance(hInstance, nCmdShow)) 
        return (FALSE);
    while (GetMessage(&msg, NULL, 0,0))    
    {
    TranslateMessage(&msg);
        DispatchMessage(&msg); 
    }
    if (fOleInitialized)  OleUninitialize();
    return (msg.wParam);
}
//********************************************************************
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,WPARAM uParam,LPARAM lParam)  
{
    switch (message) 
    {
    case WM_COMMAND:  // message: command from application menu
        switch (LOWORD(uParam)) 
        {
        case ID_EXIT:   DestroyWindow (hWnd);// exit application
                          break;
        case ID_CONNECT:  // Connect to the phone book server
            {
                HRESULT     hRes;
                // Create an instance of the phone book app.
                // Normally, we would query the registration database
                // for the CLSID; however, for the sake of simplicity
                // for this sample, we've hard-coded it.
                hRes = CoCreateInstance(&CLSID_PHONEBOOK,
                    NULL, CLSCTX_SERVER,&IID_ILookup,&pLookup);
                if (SUCCEEDED(hRes))
                {
                    MessageBox(hWnd, TEXT("Connected"), 
            TEXT("CoCreateInstance"), MB_OK);
                    fConnected = TRUE;  // we've got a pLookup pointer to use
                }
                else 
                    MessageBox(hWnd, TEXT("Failure"), 
            TEXT("CoCreateInstance"), MB_OK);
            }
            break;
        case ID_LOOKUPBYNAME:  
        case ID_LOOKUPBYNUM:
            {
                TCHAR  *ptszFound;     // returned string from method call
                TCHAR  ptszInput[MAXBUFF];  //  pass to ILookupByName/Number
                TCHAR   ptszResBuff[MAXBUFF * 3];  // results string
                BOOL         fByName = (ID_LOOKUPBYNAME == LOWORD(uParam));
                BOOL         fOK; 
                HRESULT      hRes;
                FARPROC      lpProcFind;
                FINDDLGINFO  fdInfo;
                // initialize structure to pass to DialogBoxParam
                fdInfo.ptszNameNum = ptszInput;
                fdInfo.uDlgType = LOWORD(uParam);
                // Get input  from user
                lpProcFind = MakeProcInstance((FARPROC)Find, hInst);
                fOK = DialogBoxParam(hInst, TEXT("FindDialog"), 
            hWnd,  (DLGPROC)lpProcFind, (LPARAM)&fdInfo); 
                FreeProcInstance(lpProcFind);
                if (!fOK)  // user cancelled dialog
                    break;
                // Call ILookupByName or ILookupByNumber
                // ILookup_<method> are macros generated by the MIDL compiler 
                // They are not necessary, are just provided for convenience. 
                // They expand to pLookup->lpVtbl-><method>(pLookup, <args>)
                if (fByName)
                    hRes=ILookup_LookupByName(pLookup,ptszInput,&ptszFound);
                else
                    hRes=ILookup_LookupByNumber(pLookup,ptszInput,&ptszFound);
                if (FAILED(hRes))  // Call Failed
                {
                    MessageBox(hWnd, TEXT("Failure"), fByName ? 
            TEXT("LookupByName"):TEXT("LookupByNumber"), MB_OK);
                    break;
                }
                // Call succeeded, but string user entered wasn't in database
                if (S_FALSE == hRes)  // entry not found in database
                    ptszFound = ptszNotFound;
                wsprintf(ptszResBuff,                 // Format output
                    TEXT("Name: %s\r\nPhone Number: %s"),
                    (fByName ? ptszInput : ptszFound),
                    (fByName ? ptszFound : ptszInput));
                // Display results to user
                MessageBox(hWnd, ptszResBuff, TEXT("Results"), MB_OK);
                if (ptszFound == ptszNotFound)
                {   ptszFound = NULL;
                    break;
                }
                if (NULL == pMalloc)// Free the memory passed to us.
                {   hRes = CoGetMalloc(MEMCTX_TASK, &pMalloc);
                    if (FAILED(hRes))
                        break;
                }
                pMalloc->lpVtbl->Free(pMalloc, ptszFound);
            }
            break;
            default: return (DefWindowProc(hWnd, message, uParam, lParam));
        }
        break;
    case WM_DESTROY:  
        if (pMalloc)
            pMalloc->lpVtbl->Release(pMalloc);  // release the IMalloc pointer
        if (pLookup)
            ILookup_Release(pLookup);  // release ptr to the phonebook object
        PostQuitMessage(0);
        break;
     default:  return DefWindowProc(hWnd, message, uParam, lParam);
    }
    return (0);
}

Listing Five

//**********************************************************************
// APP.CPP --- Implementation of the CPDSvrApp Class. by Sara Williams.
//**********************************************************************


//**********************************************************************
// CPDSvrApp::CPDSvrApp() ---   Constructor for CPDSvrApp
//********************************************************************
CPDSvrApp :: CPDSvrApp()
{   m_nObjCount = 0;                   // Initialize member variables
    m_fInitialized = FALSE;
}

//**********************************************************************
// CPDSvrApp::~CPDSvrApp() --- Destructor for CPDSvrApp Class
//********************************************************************
CPDSvrApp :: ~CPDSvrApp()
{
    CoRevokeClassObject( m_dwRegisterClass ) ;    // Unregister our class
    if (m_fInitialized)     OleUninitialize();    // Uninitialize OLE libs
}

// ObjectCreated and ObjectDestroyed are useful for apps that support 
// multiple objects. Ours doesn't, so they are useful, but not necessary.
void CPDSvrApp :: ObjectCreated() 
{ m_nObjCount++ ; }

void CPDSvrApp :: ObjectDestroyed() 
{   m_nObjCount-- ;
    if (m_nObjCount == 0)  PostQuitMessage(0) ;
}

//**********************************************************************
// CPDSvrApp::fInitInstance ---  Instance initialization
//********************************************************************
BOOL CPDSvrApp :: fInitInstance (HANDLE hInstance, int nCmdShow, 
    CClassFactory FAR * lpClassFactory)
{
    DWORD dwVer = OleBuildVersion();    // Get current running OLE version

    // make sure app was built with compatible version
    if (HIWORD(dwVer) != rmm || LOWORD(dwVer) < rup)
        OutputDebugString(TEXT("Not compatible with current libs!\r\n"));
    if (OleInitialize(NULL) == NOERROR)    // initialize the libraries
        m_fInitialized = TRUE;
    // Create an instance of our class factory object; we pass this
    // pointer to CoRegisterClassObject.
    lpClassFactory = new CClassFactory(this);

    // inc our ref count to hold the CF alive during CoRegisterClassObject
    lpClassFactory->AddRef();

    // Register our class factory with COM so that instances of our
    // class can be created.
    CoRegisterClassObject(CLSID_PHONEBOOK,
        (IUnknown FAR *)lpClassFactory, 
        CLSCTX_LOCAL_SERVER, 
        REGCLS_SINGLEUSE, 
        &m_dwRegisterClass);
    lpClassFactory->Release();    // match our AddRef
    return m_fInitialized;
}
//**********************************************************************
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance,
    LPSTR lpCmdLine,int nCmdShow)

{   MSG msg;
    CPDSvrApp FAR * lpCPDSvrApp;
    CClassFactory FAR * lpClassFactory;
    BOOL fContinue = TRUE;

    lpCPDSvrApp = new CPDSvrApp; // Create new instance of application object

    // instance initialization
    if (!lpCPDSvrApp->fInitInstance(hInstance, nCmdShow, lpClassFactory))
        return (FALSE);
    while (fContinue)                                 // message loop
    {   while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {   if (WM_QUIT == msg.message)
            {   fContinue = FALSE;
                break;
            }
            TranslateMessage(&msg);    /* Translates virtual key codes     */
            DispatchMessage(&msg);     /* Dispatches message to window     */
        }
    }
    delete lpCPDSvrApp ;    // Delete our app object
    return (msg.wParam);   /* Returns the value from PostQuitMessage */
}

Listing Six

//**********************************************************************
// OBJ.CPP -- Implementation of the CPDSvrObj Class. by Sara Williams.
//**********************************************************************


typedef ILookup * LPLOOKUP;

//**********************************************************************
// CPDSvrObj::QueryInterface
// Purpose: Used for interface negotiation at the "Object" level.
// Params:  REFIID riid -- A reference to the interface being queried.
//          LPVOID FAR* ppvObj  -- Out param returns a ptr to interface.
// Returns:  S_OK (if interface is supported) or E_NOINTERFACE.
//********************************************************************
STDMETHODIMP CPDSvrObj::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{   SCODE sc = S_OK;
    if (IsEqualIID(riid, IID_IUnknown))  // asking for IUnknown
          *ppvObj = (LPUNKNOWN)this;
    else if (IsEqualIID(riid, IID_ILookup)) // asking for ILookup
          *ppvObj = (LPLOOKUP)this;
    else {                    // asking for something we don't implement
        *ppvObj = NULL;
        sc = E_NOINTERFACE;
    }
    if (*ppvObj) ((LPUNKNOWN)*ppvObj)->AddRef();  // increment ref count
    return ResultFromScode( sc );
};

//**********************************************************************
// CPDSvrObj::AddRef --- Increments the object's reference count.
//********************************************************************
STDMETHODIMP_(ULONG) CPDSvrObj::AddRef ()
{    return ++m_nCount;
};

//**********************************************************************
// CPDSvrObj::Release --- Decrements the object's reference count
//********************************************************************

STDMETHODIMP_(ULONG) CPDSvrObj::Release ()
{   // if ref count is zero, then we can safely unload 
    if (--m_nCount == 0)
    {   m_lpApp->m_lpObj = NULL ;
        m_lpApp->ObjectDestroyed() ;  
        delete this;
        return 0;
    }
    return m_nCount;
}

//**********************************************************************
// LookupByName --- Given a name, return the corresponding phone number
//********************************************************************
STDMETHODIMP CPDSvrObj::LookupByName(LPTSTR lpName, TCHAR ** lplpNumber)
{   int i;
    LPMALLOC pMalloc;
    HRESULT  hRes;

    *lplpNumber = NULL;

    for(i=0; i < MAX_RECORDS; i++)
    {
        if(_tcscmp(theDatabase[i].name,lpName)==0)
        {
            hRes = CoGetMalloc(MEMCTX_TASK, &pMalloc);
            if (SUCCEEDED(hRes))
                *lplpNumber = (LPTSTR)pMalloc->Alloc(25*sizeof(TCHAR));
            else
                return (E_FAIL);
            _tcscpy(*lplpNumber, theDatabase[i].phone_number);
            pMalloc->Release();
            return ResultFromScode(S_OK);
        }
    }
    return ResultFromScode(S_FALSE);
}

//**********************************************************************
// LookupByNumber -- Given a phone number, return corresponding customer name
//********************************************************************
STDMETHODIMP CPDSvrObj::LookupByNumber(LPTSTR lpNumber, TCHAR ** lplpName)
{   int i;
    LPMALLOC pMalloc;
    HRESULT  hRes;

    *lplpName = NULL;

    for(i=0; i < MAX_RECORDS; i++)
    {   if(_tcscmp(theDatabase[i].phone_number,lpNumber)==0)
        {   hRes = CoGetMalloc(MEMCTX_TASK, &pMalloc);
            if (SUCCEEDED(hRes))
                *lplpName = (LPTSTR)pMalloc->Alloc(25*sizeof(TCHAR));
            else
                return (E_FAIL);
            _tcscpy(*lplpName, theDatabase[i].name);
            pMalloc->Release();
            return ResultFromScode(S_OK);
        }
    }
    return ResultFromScode(S_FALSE);
}
//**********************************************************************
// CPDSvrObj::CPDSvrObj ---  Constructor for CPDSvrObj
//********************************************************************
CPDSvrObj::CPDSvrObj(CPDSvrApp FAR * lpApp) 
{   m_nCount = 0;
    m_lpApp = lpApp ;
    m_dwRegister = 0; 
    Initialize();  // initialize phone book database
}

//**********************************************************************
// CPDSvrObj::~CPDSvrObj ---   Destructor for CPDSvrObj
//********************************************************************
CPDSvrObj::~CPDSvrObj()
{
    OutputDebugString(TEXT("In CPDSvrObj's Destructor \r\n"));
}
//**********************************************************************
// Initialize-- helper function to intialize phone directory database
//**********************************************************************
BOOL CPDSvrObj::Initialize(void)
{
    CreateRecord(0,TEXT("Daffy Duck"),         TEXT("310-555-1212"));
    CreateRecord(1,TEXT("Wile E. Coyote"),     TEXT("408-555-1212"));
    CreateRecord(2,TEXT("Scrooge McDuck"),     TEXT("206-555-1212"));
    CreateRecord(3,TEXT("Huey Lewis"),         TEXT("415-555-1212"));
    CreateRecord(4,TEXT("Thomas Dewey"),       TEXT("617-555-1212"));
    return TRUE; /* success */
}
//**********************************************************************
// CreateRecord--- helper function to set up phone directory database
//**********************************************************************
void CPDSvrObj::CreateRecord(int i,LPTSTR lpName, LPTSTR lpNumber)
{   theDatabase[i].name         = lpName;
    theDatabase[i].phone_number = lpNumber;
}

Listing Seven

//**********************************************************************
// ICF.CPP -- Implementation file for the CClassFactory Class
// by Sara Williams, Microsoft Corporation.
//**********************************************************************


//**********************************************************************
// CClassFactory::QueryInterface
// Params:  REFIID riid         -   Interface being queried for.
//          LPVOID FAR *ppvObj  -   Out pointer for the interface.
// Returns: S_OK if success, else E_NOINTERFACE
//********************************************************************
STDMETHODIMP CClassFactory::QueryInterface  ( REFIID riid, LPVOID FAR* ppvObj)
{
    SCODE sc = S_OK;
    // return pointer to interfaces we support
    if ((riid == IID_IUnknown) || (riid == IID_IClassFactory))
        *ppvObj = this;
    else  // request for interface we don't support
    {   *ppvObj = NULL;
        sc = E_NOINTERFACE;
    }
    if (*ppvObj) ((LPUNKNOWN)*ppvObj)->AddRef();
    return ResultFromScode(sc); // pass it on to the Application object
};

//**********************************************************************
// CClassFactory::AddRef
// Purpose:      Increments the ref count on CClassFactory object.
//********************************************************************
STDMETHODIMP_(ULONG) CClassFactory::AddRef ()
{   return ++m_nCount;
};

//**********************************************************************
// CClassFactory::Release
// Purpose:      Decrements the ref count of CClassFactory object
//********************************************************************
STDMETHODIMP_(ULONG) CClassFactory::Release ()
{   if (--m_nCount == 0)   // our ref count is 0; we can now free ourself
    {
        delete this;
        return 0;
    }
    return m_nCount;
};

//**********************************************************************
// CClassFactory::CreateInstance
// Purpose: Instantiates a new OLE object
// Parameters:
//      LPUNKNOWN pUnkOuter     - Pointer to the controlling unknown
//      REFIID riid             - The interface type to fill in ppvObject
//      LPVOID FAR* ppvObject   - Out pointer for the object
// Return Value:
//      S_OK                    - Creation was successful
//      CLASS_E_NOAGGREGATION   - Tried to be created as part of an aggregate
//      CLASS_E_CLASSNOTAVAILABLE - Tried to create a second object; 
//                                but we only support 1.
//      E_FAIL                  - Creation failed
//********************************************************************
STDMETHODIMP CClassFactory::CreateInstance ( LPUNKNOWN pUnkOuter,
    REFIID riid,
    LPVOID FAR* ppvObject)
{
    HRESULT hErr = ResultFromScode(E_FAIL);
    *ppvObject = NULL;    // need to NULL the out parameter

    // We can only have one instance.  Thus we must fail this call to
    // CreateInstance
    if (m_lpApp->m_lpObj != NULL)
        return ResultFromScode(CLASS_E_CLASSNOTAVAILABLE);
    if (pUnkOuter)    // we don't support aggregation...
        return ResultFromScode(CLASS_E_NOAGGREGATION);
    m_lpApp->m_lpObj = new CPDSvrObj( m_lpApp );    // create a new object
    m_lpApp->ObjectCreated();
    if (m_lpApp->m_lpObj)                       // get requested interface
        hErr = m_lpApp->m_lpObj->QueryInterface(riid, ppvObject);
    if (FAILED(hErr))
    {   delete m_lpApp->m_lpObj ;
        m_lpApp->m_lpObj = NULL ;
    }
    return hErr;
};

//**********************************************************************
// CClassFactory::LockServer
// Params:  BOOL fLock -- TRUE to lock the server, FALSE to unlock it
//********************************************************************
STDMETHODIMP CClassFactory::LockServer ( BOOL fLock)
{
    CoLockObjectExternal(this, fLock, TRUE);
    return ResultFromScode( S_OK);
};

Listing Eight

/*************************************************************************
 >  MAIN.CPP -- a SOM/DSOM client program.  by Charles Erickson, IBM Corp.
 >***********************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <somd.xh>
#include "PhoneDir.pxh" // Include the public definition of the class.

// Macros for checking for exceptions.
#define EV_OK(ev) ((ev)->_major == NO_EXCEPTION)
#define EV_NOT_OK(ev) ((ev)->_major != NO_EXCEPTION)

// Prototypes
static SOMObject * createObject(SOMClass *cls);
static void freeReturnedMem(void *mem);
static void freeObject(SOMObject *obj);
static SOMClass * PhoneDirInitialize();

// Static globals
static boolean isdsom = FALSE; // assume object local
/*****************************************************************/
int main (int argc, char *argv[])
{
    SOMClass *phoneDirClass;
    PhoneDir *phoneDir;;
    Environment *ev;
    string name, number;

    ev = SOM_CreateLocalEnvironment();

    // Get the phone directory class object. The class object
    // is used to create instances of the PhoneDir class.
    phoneDirClass = PhoneDirInitialize();

    // Create an instance of the PhoneDir class.
    phoneDir = (PhoneDir*)createObject(phoneDirClass);

    // Initialize the object instance.
    phoneDir->Initialize(ev, NULL);

    // Search the phone directory....
    name = "John Doe";
    number = phoneDir->LookupByName(ev, name);
    if (number) {
       printf("%s's number is %s.\n", name, number);
       freeReturnedMem(number);
    } else
       printf("%s does not have a number listed.\n", name);

    number = "408-555-1212";
    name = phoneDir->LookupByNumber(ev, number);
    if (name) {
       printf("%s's number is %s.\n", name, number);
       freeReturnedMem(name);
    } else
       printf("The phone number %s has not been assigned.\n", number);

    // Destroy the phone directory object.
    freeObject(phoneDir);
    SOM_DestroyLocalEnvironment(ev);
    return (0);
}
/*****************************************************************/
static SOMObject * createObject(SOMClass *cls)
{   return(cls->somNewNoInit());
}
/*****************************************************************/
static SOMClass * PhoneDirInitialize()
{   Environment *ev;
    SOMDServer *svr;
    SOMClass *cls, *dcls;

    ev = SOM_CreateLocalEnvironment();
    cls = PhoneDirNewClass(0, 0);
    SOMD_Init(ev);
    if (EV_OK(ev)) {
        svr = SOMD_ObjectMgr->somdFindAnyServerByClass(ev, "PhoneDir");
        if (svr && EV_OK(ev)) {
            dcls = svr->somdGetClassObj(ev, "PhoneDir");
            if (EV_OK(ev)) {
               cls = dcls;
               isdsom = TRUE;
            }
        }
    }
    SOM_DestroyLocalEnvironment(ev);
    return (cls);
}
/*****************************************************************/
static void freeReturnedMem(void *mem)
{   if (mem) 
    {   if (isdsom)        ORBfree(mem);
        else               SOMFree(mem);
    }
}
/*****************************************************************/
static void freeObject(SOMObject *obj)
{   Environment ev;
    SOM_InitEnvironment(&ev);
    if (obj) {
        if (isdsom)        SOMD_ObjectMgr->somdDestroyObject(&ev, obj);
        else               obj->somFree();
    }
}

Listing Nine

/************************************************************************
 > PHONEDIR.CPP -- SOM phone directory server.  by Charles Erickson, IBM.
 >***********************************************************************/

#define PhoneDir_Class_Source
#define VARIABLE_MACROS // Access instance data via a macro

#include "phonedir.xih"
#include <string.h>

// The following function is used by the SOM runtime to initialize
// the PhoneDir class when dynamically loaded.
SOMEXTERN void SOMLINK SOMInitModule(integer4 majorVersion, 
    integer4 minorVersion, string ignore)
{
    PhoneDirNewClass(PhoneDir_MajorVersion, PhoneDir_MinorVersion);
}
/*****************************************************************/
SOM_Scope string  SOMLINK LookupByName(PhoneDir *somSelf,  
    Environment *ev, string name)
{   int i;
    string rname, rnumber, number = (string)NULL;
    PhoneDirData *somThis = PhoneDirGetData(somSelf);
    PhoneDirMethodDebug("PhoneDir","LookupByName");

    for (i=0; i<MAX_RECORDS; i++) {
       rname = sequenceElement(_theDatabase,i).name;
       rnumber = sequenceElement(_theDatabase,i).phone_number;
       if (strcmp(rname, name)==0) {
          number = strcpy((string)SOMMalloc(strlen(rnumber)+1), rnumber);
          break;
       }
    }
    return (number);
}
/*****************************************************************/
SOM_Scope string  SOMLINK LookupByNumber(PhoneDir *somSelf,  
    Environment *ev, string number)
{   int i;
    string rname, rnumber, name = (string)NULL;
    PhoneDirData *somThis = PhoneDirGetData(somSelf);
    PhoneDirMethodDebug("PhoneDir","LookupByNumber");

    for (i=0; i<MAX_RECORDS; i++) {
       rname = sequenceElement(_theDatabase,i).name;
       rnumber = sequenceElement(_theDatabase,i).phone_number;
       if (strcmp(rnumber, number)==0) {
          name = strcpy((string)SOMMalloc(strlen(rname)+1), rname);
          break;
       }
    }
    return (name);
}
/*****************************************************************/
SOM_Scope void SOMLINK Initialize(PhoneDir *somSelf,  
    Environment *ev, somInitCtrl* ctrl)
{   /* This function is the object initializer */
    PhoneDirData *somThis; /* set in BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    PhoneDirMethodDebug("PhoneDir","Initialize");
    PhoneDir_BeginInitializer_Initialize;

    PhoneDir_Init_SOMObject_somDefaultInit(somSelf, ctrl);
    //local PhoneDir initialization code added by programmer
    sequenceLength( _theDatabase) = MAX_RECORDS;
    sequenceMaximum(_theDatabase) = MAX_RECORDS;
    _theDatabase._buffer =
            (PhoneDir_record*)SOMMalloc(sizeof(PhoneDir_record)*MAX_RECORDS);

    sequenceElement(_theDatabase, 0) =
            somSelf->CreateRecord(ev, "Daffy Duck",     "310-555-1212");
    sequenceElement(_theDatabase, 1) =
            somSelf->CreateRecord(ev, "Wile E. Coyote", "408-555-1212");
    sequenceElement(_theDatabase, 2) =
            somSelf->CreateRecord(ev, "Scrooge McDuck", "206-555-1212");
    sequenceElement(_theDatabase, 3) =
            somSelf->CreateRecord(ev, "David Byrne",    "415-555-1212");
    sequenceElement(_theDatabase, 4) =
            somSelf->CreateRecord(ev, "Thomas Dewey",   "617-555-1212");
    /* Note: the fact that no exception was returned in the <ev> argument
     * indicates successful initialization.*/
}
/*****************************************************************
SOM_Scope PhoneDir_record  SOMLINK CreateRecord(PhoneDir *somSelf,
    Environment *ev, string name, string phone_number)
{
    // Return a new phone directory record given the name and number.
    PhoneDir_record retVal;
    PhoneDirData *somThis = PhoneDirGetData(somSelf);
    PhoneDirMethodDebug("PhoneDir","CreateRecord");
    retVal.name = name;
    retVal.phone_number = phone_number;
    return (retVal);
}
/*****************************************************************
 * The default initializer, called by default if no other is specified 
 * when the object is created.
 */
SOM_Scope void SOMLINK somDefaultInit(PhoneDir *somSelf, somInitCtrl* ctrl)
{   Environment *gev;
    PhoneDirData *somThis; /* set in BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    PhoneDirMethodDebug("PhoneDir","somDefaultInit");
    PhoneDir_BeginInitializer_somDefaultInit;
    PhoneDir_Init_SOMObject_somDefaultInit(somSelf, ctrl);
    //local PhoneDir initialization code added by programmer
    gev = somGetGlobalEnvironment();
    Initialize(somSelf, gev, ctrl);
}

Listing Ten

//**********************************************************************
    // ddjdectl.cpp -- The CDdjdemoCtrl OLE control class. by Steven Ross.
    // NOTE: in this listing, the lines that were manually inserted
    // into this code are prefixed with a "==>" at the left margin. Also,
    // minor reformatting has been done during production to conserve space.
    //**********************************************************************
    #include "stdafx.h"
    #include "ddjdemo.h"
    #include "ddjdectl.h"
    #include "ddjdeppg.h"
    #include "phonedir.h" // API for "one-minute phone directory" by R.Valdes
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char BASED_CODE THIS_FILE[] = __FILE__;
    #endif
    
    IMPLEMENT_DYNCREATE(CDdjdemoCtrl, COleControl)
    
    ////// Message map ///////////////////////////////////////////
    BEGIN_MESSAGE_MAP(CDdjdemoCtrl, COleControl)
        //{{AFX_MSG_MAP(CDdjdemoCtrl)
        ON_OLEVERB(IDS_PROPERTIESVERB, OnProperties)
        ON_MESSAGE(OCM_COMMAND, OnOcmCommand)
        ON_WM_CREATE()
        //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    
    ///// Dispatch map ////////////////////////////////////////////
    BEGIN_DISPATCH_MAP(CDdjdemoCtrl, COleControl)
        //{{AFX_DISPATCH_MAP(CDdjdemoCtrl)
        DISP_PROPERTY_EX(CDdjdemoCtrl, "CurrentName", GetCurrentName, 
                        SetNotSupported, VT_BSTR)
        DISP_PROPERTY_EX(CDdjdemoCtrl, "CurrentNumber", GetCurrentNumber, 
                        SetNotSupported, VT_BSTR)
        DISP_FUNCTION(CDdjdemoCtrl, "GetNameFromNumber", GetNameFromNumber, 
                        VT_BSTR, VTS_BSTR)
        DISP_FUNCTION(CDdjdemoCtrl, "GetNumberFromName", GetNumberFromName, 
                        VT_BSTR, VTS_BSTR)
        //}}AFX_DISPATCH_MAP
        DISP_FUNCTION_ID(CDdjdemoCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, 
        VT_EMPTY, VTS_NONE)
    END_DISPATCH_MAP()
    
    ////// Event map ///////////////////////////////////////////
    BEGIN_EVENT_MAP(CDdjdemoCtrl, COleControl)
        //{{AFX_EVENT_MAP(CDdjdemoCtrl)
        EVENT_CUSTOM("NameNumberChanged", FireNameNumberChanged, VTS_BSTR  
                        VTS_BSTR)
        //}}AFX_EVENT_MAP
    END_EVENT_MAP()
    
    ////// Property pages ///////////////////////////////////////////
    // TODO: Add more property pages as needed. Remember to increase the count!
    BEGIN_PROPPAGEIDS(CDdjdemoCtrl, 1)
            PROPPAGEID(CDdjdemoPropPage::guid)
    END_PROPPAGEIDS(CDdjdemoCtrl)
    
    ////// Initialize class factory and guid /////////////////////////////////
    IMPLEMENT_OLECREATE_EX(CDdjdemoCtrl, "DDJDEMO.DdjdemoCtrl.1",
     0xaf3b7529, 0x89d0, 0x101b, 0xa6, 0xe4, 0x0, 0xdd, 0x1, 0x11, 0xa6, 0x58)
    
    ///// Type library ID and version ////////////////////////////////////////
    IMPLEMENT_OLETYPELIB(CDdjdemoCtrl, _tlid, _wVerMajor, _wVerMinor)
    
    ////// Interface IDs ///////////////////////////////////////////
    const IID BASED_CODE IID_DDdjdemo =
    { 
            0xaf3b752a, 0x89d0, 0x101b, { 
                    0xa6, 0xe4, 0x0, 0xdd, 0x1, 0x11, 
                    0xa6, 0x58 
            } 
    };
    const IID BASED_CODE IID_DDdjdemoEvents =
    { 
            0xaf3b752b, 0x89d0, 0x101b, { 
                    0xa6, 0xe4, 0x0, 0xdd, 0x1, 0x11, 0xa6, 0x58 
            } 
    };
    
    /////////////////////////////////////////////////
    // CDdjdemoCtrl::CDdjdemoCtrlFactory::UpdateRegistry -
    // Adds or removes system registry entries for CDdjdemoCtrl
    BOOL CDdjdemoCtrl::CDdjdemoCtrlFactory::UpdateRegistry(BOOL bRegister)
    {
            if (bRegister)
                    return AfxOleRegisterControlClass(
                            AfxGetInstanceHandle(),
                            m_clsid, m_lpszProgID, IDS_DDJDEMO, IDB_DDJDEMO,
                            TRUE,        //  Insertable
                            OLEMISC_ACTIVATEWHENVISIBLE |
                            OLEMISC_SETCLIENTSITEFIRST |
                            OLEMISC_INSIDEOUT |
                            OLEMISC_CANTLINKINSIDE |
                            OLEMISC_RECOMPOSEONRESIZE,
                            _tlid, _wVerMajor, _wVerMinor);
            else
                    return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
    }
    
    ////// CDdjdemoCtrl::CDdjdemoCtrl - Constructor /////////////////////////
    CDdjdemoCtrl::CDdjdemoCtrl()
    {
            // Set sensible initial size for the control
==>         SetInitialSize(250, 100);
            InitializeIIDs(&IID_DDdjdemo, &IID_DDdjdemoEvents);
    
            // Call Ray's Initialize function
==>         phonedir_Initialize();
    }
    ///// CDdjdemoCtrl::~CDdjdemoCtrl - Destructor  ////////////////////////
    CDdjdemoCtrl::~CDdjdemoCtrl()
    {
            // Call Ray's Terminate function
==>         phonedir_Terminate();
    }
    ////// CDdjdemoCtrl::OnDraw - Drawing function ///////////////////////////
    void CDdjdemoCtrl::OnDraw(
    CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
    {
            DoSuperclassPaint(pdc, rcBounds);
    }
    
==> #ifndef _WIN32
    // For Windows 3.1, some subclassed controls can't be safely drawn
    // to a metafile. As we don't draw to a metafile anyhow, supply an 
    // empty override for the function. If we had a drawing representation, 
    // we'd iterate the list box, calling DrawText/TextOut for each list item.
==> void CDdjdemoCtrl::OnDrawMetafile(CDC* pdc, const CRect& rcBounds)
==> {
==> }
==> #endif
    
    ///// CDdjdemoCtrl::DoPropExchange - Persistence support /////////////////
    void CDdjdemoCtrl::DoPropExchange(CPropExchange* pPX)
    {
            ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
            COleControl:: DoPropExchange(pPX);
            // TODO: Call PX_ functions for each persistent custom property.
    }
    
    ////// CDdjdemoCtrl::OnResetState - Reset control to default state ///////
    void CDdjdemoCtrl::OnResetState()
    {
        // Resets defaults found in DoPropExchange
        COleControl::OnResetState();  
        // TODO: Reset any other control state here.
    }
    
    ////// CDdjdemoCtrl::AboutBox - Display an "About" box to the user ///////
    void CDdjdemoCtrl::AboutBox()
    {
            CDialog dlgAbout(IDD_ABOUTBOX_DDJDEMO);
            dlgAbout.DoModal();
    }
    
    ///// CDdjdemoCtrl::PreCreateWindow - Modify parameters for CreateWindowEx
    BOOL CDdjdemoCtrl::PreCreateWindow(CREATESTRUCT& cs)
    {
            // Modify the style bits for the listbox so we 
            // 1) can make a tab-separated 2-column list; 
            // 2) get notification of listbox events; and 
            // 3) can do vertical scrolling.
==>         cs.style |= LBS_USETABSTOPS | LBS_NOTIFY | WS_VSCROLL;
            cs.lpszClass = _T("LISTBOX");
            return COleControl::PreCreateWindow(cs);
    }
    
    // CDdjdemoCtrl::GetSuperWndProcAddr - Provide storage for window proc ////
    WNDPROC* CDdjdemoCtrl::GetSuperWndProcAddr(void)
    {
            static WNDPROC NEAR pfnSuper;
            return &pfnSuper;
    }
    
    ///// CDdjdemoCtrl::OnOcmCommand - Handle command messages //////////////
    LRESULT CDdjdemoCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
    {
    #ifdef _WIN32
            WORD wNotifyCode = HIWORD(wParam);
    #else
            WORD wNotifyCode = HIWORD(lParam);
    #endif
    
         // This is where the listbox notifications are received. The only
         // one we're interested in is LBN_SELCHANGE. When the selection is
         // changed, we reuse the code for GetCurrentName and GetCurrentNumber
         // to retrieve the correct strings for name and phone number, then
         // call the FireNameNumberChanged event that ClassWizard created.
==>         switch(wNotifyCode)
==>         {
==>             case LBN_SELCHANGE:
==>                  FireNameNumberChanged(GetCurrentName(), 
==>                                        GetCurrentNumber());   break;
==>         }
            return 0;
    }
    
    ////// CDdjdemoCtrl message handlers /////////////////////////////////////
    BSTR CDdjdemoCtrl::GetNameFromNumber(LPCTSTR szNumber) 
    {
            // Use one-minute phone directory API to retrieve a name
            // given a number
==>         CString s = phonedir_LookupByNumber((char *)szNumber);
            return s.AllocSysString();
    }
    
    BSTR CDdjdemoCtrl::GetNumberFromName(LPCTSTR szName) 
    {
            // Use one-minute phone directory API to retrieve a number
            // given a name
==>         CString s = phonedir_LookupByName((char *)szName);
            return s.AllocSysString();
    }
    
    BSTR CDdjdemoCtrl::GetCurrentName() 
    {
==>         UINT nIndex;
            CString s;
    
            // If there is a selection, then get the corresponding name,
            // otherwise return an empty string.
==>         if((nIndex=(UINT)SendMessage(LB_GETCURSEL)) != LB_ERR)
==>               s = phonedir_LookupByOrdinal(nIndex).SpanExcluding("\t");
==>         else
==>               s = "";
    
            return s.AllocSysString();
    }
    
    BSTR CDdjdemoCtrl::GetCurrentNumber() 
    {
==>         UINT nIndex;
            CString s;
    
            // If there is a selection, then get the corresponding number,
            // otherwise return an empty string.
==>         if((nIndex=(UINT)SendMessage(LB_GETCURSEL)) != LB_ERR)
==>         {
==>                 s = phonedir_LookupByOrdinal(nIndex);
==>                 s = s.Mid(s.Find("\t") + 1);
==>         }
==>         else
==>                 s = "";
            return s.AllocSysString();
    }
    int CDdjdemoCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
            if (COleControl::OnCreate(lpCreateStruct) == -1)
                    return -1;
            // Access the database using an ordinal lookup to get
            // tab-separated strings. Add the strings to the listbox
            // for initial population of the list.
==>         CString strTemp;
==>         for(int i = 0; 
==>             (strTemp = phonedir_LookupByOrdinal(i)).GetLength() != 0; 
==>             i++)
==>                 SendMessage(LB_ADDSTRING, 0, (long)(LPCTSTR)strTemp); 
            return 0;
    }


Copyright © 1994, Dr. Dobb's Journal