The Service Control Manager (SCM) and the System Registry

 < Free Open Study > 



In order for the COM runtime to locate and load a server into memory, the Service Control Manager (SCM) consults a utility known as the system registry. The registry is a local system database, which specifies (among other things) all the COM-centric information for a given computer. You may access the Registry Editor by running regedit.exe from the Windows Run command.

The registry is broken into a series of topmost nodes called hives. The most important hive for COM developers is HKEY_CLASSES_ROOT (abbreviate to simply HKCR). Figure 3-16 illustrates the hives found on a WinNT workstation installation:

click to expand
Figure 3-16: The Registry Editor.

Entries under a hive are called keys, which may contain subkeys. A given key or subkey may contain string or numerical values. Entire books have been written about the layout and programming of the Windows registry, but luckily COM developers only need to understand a small subset of its overall functionality, beginning with the ProgID.

Programmatic Identifiers (ProgIDs)

Let's explore your computer's registry. The first thing listed under HKCR is a long list of file extensions, which we have no interest in at all. Scroll past this list until you find the first real text entry located after the final file extension. When you find that item (mine happens to be Access.Application), expand it:

click to expand
Figure 3-17: ProgIDs map to CLSIDs.

These strings are COM related, and are called Programmatic Identifiers (ProgIDs). ProgIDs are a text-based alternative used to refer to a COM object residing in some server. ProgIDs are simply text mappings for CLSIDs. As you can see, every ProgID listing has a subkey mapping to the corresponding CLSID value as well as an optional CurVer (current version) subkey. The standard format to follow when creating a ProgID for your coclass is ServerName.CoclassName.Version (the version is optional). ProgIDs are not guaranteed to be unique! If you get into the habit of beginning a ProgID with the name of your company, you are quite reasonably assured uniqueness. For example: MyCompany.Shapes.CoHexagon.1.

Why do we need ProgIDs? ProgIDs are useful for certain COM-enabled languages that have no ability to refer to the raw GUID associated to your coclass. In effect, a ProgID is a language-neutral way to identify a COM object. For example, VBScript needs the ProgID of a coclass to load the server into memory. VBScript does not provide a way to reference the raw 128-bit number of CoHexagon in the code. Visual Basic proper does allow a VB programmer to work with the real GUID, and ProgIDs are optional in modern VB (but not VBScript) programming.

The COM library provides two functions that allow us to obtain a ProgID given a CLSID, and vice versa. You may obtain the CLSID of a given ProgID using the COM library function CLSIDFromProgID():

 // I have the ProgID, what was that CLSID again? CLSID clsid; CLSIDFromProgID(L"Shapes.CoHexagon.1", &clsid);

To fetch the ProgID given a CLSID, make a call to ProgIDFromCLSID():

 // I have the GUID of the object, what was that ProgID again? LPOLESTR progid;          // Array of OLECHARs ProgIDFromCLSID(clsid, &progid);

In order to obtain a CLSID from a ProgID, the ProgID is located from HKCR, and the CLSID subkey is consulted.

A Critical Key: HKEY_CLASSES_ROOT\CLSID

The next point of interest is the CLSID key. The CLSID key is where SCM ultimately ends up when looking for the physical path to your COM server. Each subkey of HKCR\CLSID begins with the GUID for the entry. Figure 3-18 reveals the CLSID of Microsoft's Data Access Object's (DAO) DBEngine coclass:

click to expand
Figure 3-18: HKCR\CLSID maps to ProgIDs andthe server's physical path.

Under a given CLSID entry, you may find any of the following subkeys:

  • ProgID: This key maps back to the ProgID associated with the coclass. When you call ProgIDFromCLSID(), SCM will examine the ProgID subkey of a given CLSID listing.

  • VersionIndependentProgID:  Same value as the ProgID key, without the version suffix. Recall that ProgIDs do not have to be versioned. Often, a developer may choose to register a version-independent and version-dependent ProgID for the same coclass. Using the VersionIndependentProgID subkey allows SCM to reference each from the same CLSID subkey.

  • InprocServer32:  For in-process COM servers, this is the most important of all CLSID subkeys. This value is the physical path to the DLL server (for example, "C:\MyServers\Shapes\Debug\shapes.dll").

  • LocalServer32:  If you have COM objects that live in an EXE rather than a DLL, the value of LocalServer32 is the path to the COM executable (for example, "C:\MyServers\Shapes\Debug\shapes.exe"). We will work with this subkey in Chapter 5, where we create EXE component housing for a local (and remote) server.

There are other keys, subkeys, and values of HKCR that are useful for COM development; however, the above list is just right for now. At minimum, every COM class in your server (such as CoHexagon) needs a ProgID and CLSID entry in the system registry. So how do we stuff this information into the system registry?

Registering Your COM Servers

Recall that well-behaved in-proc servers export four well-known functions. Of these, DllRegisterServer() and DllUnregisterServer() are used to enter or remove the correct registry information for your server. These methods may be called by commercial installation programs, the regsvr32.exe utility, or programmatically with API calls. The implementations of these functions involve using a handful of Win32 Registry API functions, and to be honest, are about as much fun as discovering you have a computer virus. The code behind these methods is not impossible, but can be rather tricky.

COM development libraries, such as ATL, MFC, and Visual Basic, take care of the dirty work of implementing these functions for us. Until Chapter 6, where we begin using the Active Template Library to build our COM servers, we will take a shortcut ourselves. We will write our own registry scripts (REG file), which can be used to merge our server information into the registry automatically, bypassing the need to code DllRegisterServer() by hand. To create a REG file, we create a new text file and save it with an *.reg file extension. At the very least, this file should insert the following information into the system registry:

  • A ProgID listing directly under HKCR, with a single subkey providing the CLSID.

  • The CLSID of our coclass under HKCR\CLSID. Add in an InprocServer32 subkey (or LocalServer32 for EXE servers) that points to the physical path of our DLL (or EXE) server.

In order to get the correct CLSID format, you can copy the commented line from the DEFINE_GUID macro (guidgen.exe was kind enough to supply that for you):

 // {F12327B0-DE3A-11d2-AAF4-00A0C9312D57} DEFINE_GUID(CLSID_CoHexagon, 0xf12327b0, 0xde3a, 0x11d2, 0xaa, 0xf4, 0x0, 0xa0, 0xc9, 0x31, 0x2d, 0x57);

Here are some notes about REG file syntax: The REGEDIT tag is not optional and must be listed on the very first line. You must have a single space between each side of any assignment operator, and must not have spaces between keys and subkeys. To be sure, the devil is in the details. Here would be a minimal and complete REG file for our in-process shapes server:

REGEDIT HKEY_CLASSES_ROOT\Shapes.CoHexagon\CLSID = {F12327B0-DE3A-11d2-AAF4-00A0C9312D57} HKEY_CLASSES_ROOT\CLSID\{F12327B0-DE3A-11d2-AAF4-00A0C9312D57} = Shapes.CoHexagon HKEY_CLASSES_ROOT\CLSID\{F12327B0-DE3A-11d2-AAF4-00A0C9312D57}\InprocServer32 =  C:\ILoveCOM\Shapes\Debug\shapes.dll

Once you save out this file, simply double-click on it from the Windows Explorer to merge your information into the system registry. If your entries do not look something like the following, you have problems with your REG file syntax. Here is the ProgID listing entered from the shapes.reg file:

click to expand
Figure 3-19: ProgID listing for CoHexagon.

And here is the listing under the CLSID key:

click to expand
Figure 3-20: A path to shapes.dll, listed under HKCR\CLSID.

Note 

Needless to say, the GUIDs, ProgIDs, and physical paths will always depend on your current COM project.

Note 

Class factories are not entered into the system registry. Clients obtain IClassFactory interfaces using COM library functions.

Now, let's pull this knowledge together to create our first official COM server, using straight C++. As mentioned, this process will prove both painful and edifying. However, remember that ATL is just around the corner, and takes care of much of the necessary COM boilerplate code on your behalf.

On a related note, ATL expects that the system registry, component housing, class objects, interfaces, and coclasses are nothing new to you. Taking the time to write some raw COM code will serve you well.

Lab 3-2: Developing an In-Process COM Server in C++

Here is your first real COM server. You will be taking the CoCar class created in Lab 3-1 and develop a class factory to activate it per client request. You will also place your new objects into a Win32 DLL, implement the minimal server exports (DllGetClassObject() and DllCanUnloadNow()), and create a registration file to merge your COM information into the system registry. This lab will pull together everything you have read up to this point in the book, and leave you with a reusable COM server.

Note 

This lab will serve as the basis for Chapter 4, as well as Chapter 5.

 On the CD   The solution for this lab can be found on your CD-ROM under:
Labs\Chapter 03\CarInProcServer

Step One: Prepare the Project Workspace and Port CoCar

Begin by creating a new empty Win32 DLL project workspace named CarInProcServer (take care not to select an MFC-based DLL):

click to expand
Figure 3-21: New Win32 DLL CASE tool.

Add a brand new file named CarInProcServer.cpp. The functions and data points we will add to this file constitute the component housing for your DLL. First, define two global ULONG data types to represent the lock count and object count server variables. Be sure this new file is inserted into the project workspace:

// Add the following global data points to your component housing file. #include <windows.h> ULONG g_lockCount = 0;         // Number of client locks. ULONG g_objCount = 0;          // Number of living objects in the house. 

The CoCar coclass and the related interfaces defined in the previous lab can be easily reused in this current lab. Open the Windows Explorer, and copy the following files from your previous lab directory into the current project directory:

  • CoCar.h and CoCar.cpp: the Car coclass

  • iid.h and iid.cpp: GUID files

  • interfaces.h: custom interfaces

Insert each *.cpp file into the project workspace (using Project | Add To Project | Files...) and compile. You do not need to manually include header files, as they will be brought in automatically.

As this iteration of CoCar will be placed into a binary package, we need a CLSID to uniquely identity CoCar in the COM universe. Generate a new GUID (using guidgen.exe) to serve as the CLSID for CoCar. Specify the constant CLSID_CoCar as the first parameter to the DEFINE_GUID macro. Add this GUID to your existing iid.h file:

// {7AD2D539-EE35-11d2-B8DE-0020781238D4} DEFINE_GUID(CLSID_CoCar, 0x7ad2d539, 0xee35, 0x11d2, 0xb8, 0xde, 0x0, 0x20, 0x78, 0x12, 0x38, 0xd4);

To finish this source code port, we need to increment and decrement the global object count in your CoCar constructor and destructor. Remember that we must provide hooks to inform the DLL server how many objects are currently living within it. Use the extern keyword to reference the globally defined g_objCount variable from within the CoCar class:

// cocar.cpp extern ULONG g_objCount; CoCar::CoCar() : m_refCount(0), m_currSpeed(0), m_maxSpeed(0) {      ++g_objCount;     // Other code... } CoCar::~CoCar() {      --g_objCount;     // Other code... }

Finally, you might notice that your interface definitions do not show up in ClassView. The IDE has placed all your *.h files under the External Dependencies folder in FileView. You may relocate them to the Header Files folder (just by dragging and dropping) and once you do so, you will see your interfaces visible from ClassView.


Figure 3-22: The 'repaired' ClassView.

Step Two: Develop the CoCar Class Factory

Now we need to develop a class factory to create the CoCar. Insert a new class named CoCarClassFactory, deriving from IClassFactory only. Recall that a class object provides a language-neutral way to create COM objects. Here is the initial definition of your class object:

// The class object for your CoCar. #include <windows.h> class CoCarClassFactory : public IClassFactory { public:      CoCarClassFactory();      virtual ~CoCarClassFactory();      // IUnknown      STDMETHODIMP QueryInterface(REFIID riid, void** pIFace);      STDMETHODIMP_(ULONG)AddRef();      STDMETHODIMP_(ULONG)Release();      // IClassFactory      STDMETHODIMP LockServer(BOOL fLock);      STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter,                                REFIID riid, void** ppv); private:      ULONG m_refCount;          // Init to zero in constructor! };

Begin by providing an implementation of the IUnknown methods AddRef(), Release(), and QueryInterface(). This code will be almost exactly like the IUnknown logic for CoCar, except this time your QueryInterface() will only be testing for IID_IUnknown and IID_IClassFactory:

// The class factory's IUnknown implementation. STDMETHODIMP_(ULONG) CoCarClassFactory::AddRef() {      return ++m_refCount; } STDMETHODIMP_(ULONG) CoCarClassFactory::Release() {      if(--m_refCount == 0)      {           delete this;           return 0;      }      return m_refCount; } // Remember! Class factories do not implement the interfaces of the coclass // they are responsible for creating! STDMETHODIMP CoCarClassFactory::QueryInterface(REFIID riid, void** ppv) {      // Which aspect of me do they want?      if(riid == IID_IUnknown)      {           *ppv = (IUnknown*)this;      }      else if(riid == IID_IClassFactory)      {           *ppv = (IClassFactory*)this;      }           else      {           *ppv = NULL;           return E_NOINTERFACE;      }      ((IUnknown*)(*ppv))->AddRef();      return S_OK; } 

Next, implement IClassFactory::CreateInstance(). In this method, create a CoCar object and query it for the requested interface. Return an HRESULT that your client may test against. If anything fails in the CreateInstance() method, you must be sure to delete the memory given to the coclass:

// Create the CoCar! STDMETHODIMP CoCarClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,                                           REFIID riid, void** ppv) {      // We do not support aggregation in this class object.      if(pUnkOuter != NULL)           return CLASS_E_NOAGGREGATION;      CoCar* pCarObj = NULL;      HRESULT hr;      // Create the car.      pCarObj = new CoCar;      // Ask car for an interface.      hr = pCarObj -> QueryInterface(riid, ppv);      // Problem? We must delete the memory we allocated.      if (FAILED(hr))           delete pCarObj;      return hr; }

To finish up the class factory, implement IClassFactory::LockServer() to increment or decrement the server's global lock count. Also, in the constructor and destructor of your class object, adjust the global object count. In order to reference the global items defined in CarInProcServer.cpp, use the extern keyword in your class object's CPP file:

// Final details of the class factory. extern ULONG g_lockCount; extern ULONG g_objCount; // Constructor. CoCarClassFactory::CoCarClassFactory() {      m_refCount = 0;      g_objCount++; } // Destructor. CoCarClassFactory::~CoCarClassFactory() {      g_objCount--; } // Lock server. STDMETHODIMP CoCarClassFactory::LockServer(BOOL fLock) {      if(fLock)           ++g_lockCount;      else           --g_lockCount;      return S_OK; }

Beyond this, just be sure you are careful with including the correct files in your class factory source code (you're referencing CoCar in CreateInstance(), so be sure to include your CoCar header file). Compile again and clean up any resulting errors. The next step is to develop the minimal and complete set of server exports.

Step Three: Implement the DLL Component Housing

In your CarInProcServer.cpp file, implement DllGetClassObject() to perform the following functionality:

// This export exposes class factories to the Service Control Manager. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) {      // 1. See if the CLSID is the same as CoCar, if not, return an error.      // 2. Create the CoCarClassFactory object.      // 3. Ask the new car class object for the REFIID parameter.      // 4. Set HRESULT for client. }

The final DLL export we will implement is DllCanUnloadNow():

// This export determines if the DLL can be unloaded from memory. STDAPI DllCanUnloadNow() {      // If g_objCount and g_lockCount are both zero, return S_OK      // otherwise return S_FALSE. }

Here is the relevant code for each export:

// DLL Exports. STDAPI DllCanUnloadNow() {      if(g_lockCount == 0 && g_objCount == 0)           return S_OK;          // Unload me.           else           return S_FALSE;       // Keep me alive. } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) {      HRESULT hr;      CoCarClassFactory *pCFact = NULL;      // We only know how to make cars in this house.      if(rclsid != CLSID_CoCar)           return CLASS_E_CLASSNOTAVAILABLE;      // They want a CoCarClassFactory      pCFact = new CoCarClassFactory;      // Go get the interface from the CoCarClassFactory      hr = pCFact -> QueryInterface(riid, ppv);      if(FAILED(hr))           delete pCFact;      return hr; } 

Next, create and insert a new file, carinprocserver.def, into your project to expose these two functions to the outside world. Be sure that the LIBRARY statement name is the same name of the project you are building:

LIBRARY "CARINPROCSERVER" EXPORTS      DllGetClassObject        @1     PRIVATE      DllCanUnloadNow          @2     PRIVATE

Step Four: Provide Registry Information

To complete our DLL server, we need to create a REG file to merge the minimal and complete server information into the system registry. Create a ProgID for your coclass with a CLSID subkey. Enter this directly under HKCR. Next, enter your CLSID listing for your coclass, with an InprocServer32 subkey, pointing to the physical path of your DLL.

Remember that you cannot have spaces around slashes (\) and must have spaces around the assignment operator (=). Also recall that REGEDIT must be the very first line in the file. Here is a possible carinprocserver.reg file (be sure to use your CLSIDs and path, of course):

REGEDIT HKEY_CLASSES_ROOT\CarInProcServer.CoCar\CLSID = {7AD2D539-EE35-11d2-B8DE-0020781238D4} HKEY_CLASSES_ROOT\CLSID\{7AD2D539-EE35-11d2-B8DE-0020781238D4} = CarInProcServer.CoCar HKEY_CLASSES_ROOT\CLSID\{7AD2D539-EE35-11d2-B8DE-0020781238D4} \InprocServer32 = E:\ATL\Labs\Chapter 03\CarInProcServer\Debug\CarInProcServer.dll

When you have finished, double-click on the REG file using the Windows Explorer to merge this information into the registry. Use regedit.exe to ensure the entries are correct. You have just created a COM server in straight C++, no libraries, no help. Sit back and grin. Once you have savored your achievement, read on to learn how to build a C++ COM client that accesses this server.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net