The Other Way To Use OLE - Part 3

by Jim Kane

Published 1999-11-02    Printer-friendly version

Read Part 1

Read Part 2

When I was growing up my father often said to me he couldn't wait until I had a son of my own and he did to me some of the things that I did to my father. Well, my father got his wish and I have a son. Sometimes it seems my son's main goal in life is to help my father get even.

Not long ago I had the privilege of helping my son with a math assignment involving multiplication. While he was doing his work he kept longing to use a calculator and be done with it. As often as I explained the importance and virtue of doing it long hand so he learned the technique, the use of the calculator beckoned. As annoyed as I got at his insistence at using the calculator, I could not help but remember another little boy some 35 years earlier who thought memorizing multiplication tables was insane, and begged his father to let him use a calculator for math assignments.

Well, for much the same reason, in part one of this series I presented the assembler calling convention for calling OLE interfaces, and the Icall.A assembler procedure to call interfaces. Then in part two I outlined the needed API calls for calling interfaces. Now its time to "get out the calculator" and use the Icall.A assembler and some of the code and constants presented in part two to make a reusable OLE class for calling interfaces. While it isn't necessary to understand the material in parts one and two, it certainly would make your life much fuller and richer if you did!

To review from part two, the basic steps in calling an interface to create a Windows shortcut are:

  1. Initialize COM (CoInitialize or CoInitializeEX)
  2. Get interface pointers (CoCreateInstance and QuerryInterface)
  3. Use interface pointers to create the shortcut (IShellLink methods)
  4. Save the shortcut to disk (IPersistFile methods)
  5. Release any interface pointers obtained (Release method of respective interfaces)
  6. Uninitialize COM (UnCoInitialize)

Time to make a plan of attack for creating the class. As I read through the list, the data item that keeps popping up in the list is interface pointers. Managing these pointers and making sure they are released when I'm done is a constant headache with this kind of work. In addition, there are a few constants associated with each interface that are not much fun to track. Although not mentioned in the list specifically, in the code from part two there was a lot of work expended in detecting and reporting errors. Effectively managing the interface pointers would help with this.

Above and beyond the sexy interface pointers part of the operation are step 1 and 6. Gee, they certainly look like simple steps. Anyone thinking they are simple clearly forgot Microsoft is involved! In part two, I used CoInitialize to initialize the windows OLE subsystem. Actually the preferred API according to Microsoft is CoIntializeEX(0,ThreadingModel).

The two most common threading models are Apartment and Multithreaded. In this case, IShellLink uses the apartment model. How do I know? By the apartment number in the phone book address? Not quite. Either threading model is known via documentation or the information is available in the registry.

To make matters worse, depending on the threading model the initialization goes in different places. For the Multithreaded model, initialization code can be done one time per application, so it's easiest to call it in the frame procedure. For the apartment model, the code needs to go in the same apartment or thread where the OLE code lives. For that model it's best to call the initialization code just before calling the OLE interface(s), and uninitialize just after.

Well, surely those two "simple" steps are done now. Guess again! There's one more wrinkle. CoInitializeEX() is not included in the TopSpeed lib files so you need to make your own library from Ole32.dll before CoInitializeEX() will link. One is included with the downloadable zip. With all this in mind, the class OLECl.CLW was written to carry out steps 1 and 6.

Figure 1. Code to carry out steps 1 and 6.

CoInit_ApartmentThreaded equate(2)
CoInit_MultiThreaded     equate(0)

OLEClType   Class,type,module('OLECl.CLW'),cr.gif (846 bytes)
  LINK('OLECl.CLW',_ABCLinkMode_),DLL(_ABCDllMode_)
!Member Data
fInitComm       long(0)
ThreadType      long(-1)
    ! 0=Apartment model; 2=Multithreaded; -1=illegalvalue 
!Methods 
InitOLE             Procedure(long threadingModel),byte,proc  
    !0 is good, else if fatal error
KILLOLE                 Procedure()
GetOleInitializedCount  Procedure(),byte
GetThreadingType    Procedure(),long
            End

The InitOLE method calls CoInitializeEX and increases fInitComm. The KillOLE method does the opposite: it calls UnCoInitialize and decreases fInitComm. On a good day, when your program is done, the count of calls to CoInitializeEX should exactly equal count of calls to UnCoIntialize and GetOleInitializedCount should return 0. That's on a good day. InitOLE should be called with a threadingModel parameter of 0 or 2 for apartment model or multithreaded respectively. Neither of the last two methods are needed in production code but may be valuable for debugging. Since it seems I spend most of my life debugging, that should make them very valuable indeed.

Well wait just a minute. Didn't I imply in the introduction that this was going to be easy? Lets take inventory. There's the Icall.A assembler module, the OLE32.Lib, OleCl.inc and Ole.Clw for initialization, plus if we ever get rolling here there will be one or so more classes to deal with those sexy interface pointers. Time to reach for Clarion's ace in the hole: templates. Also in the downloadable zip is OLETPL.TPL which adds all these bits and pieces to any application that will call an interface.

The template only has two prompts. First it asks for the threading model: Apartment or Multithreaded. In this case pick Apartment. There you have it, drop the OleTpl global extension template on an app and it's ready to call OLE interfaces. Oh, I did forget to mention one other detail: the second prompt asks for the name of the class that does the work of managing interface pointers. Looks like I'd better get busy building that piece.

The main goal of the following class is to manage all the interface pointers, being sure to release all it acquires and manage any errors and the constants associated with the interfaces. All the code for managing the interface pointers and errors can be put into a general purpose helper OLE class. I decided to name it holeClType , short for helper OLE class. Nothing in that class is specific to this project; it's just general purpose interface pointer management.

The basis of the system is a series of equates, three queues and some beer:

Figure 2. Equates and queues.

!Interface equates
IShellLink              Equate(20)
IPersistFile            Equate(19)

!method equates with Method offset in the Vtable.
IShellLink_SetDescrip           Equate(IShellLink*100H + 1CH)
IShellLink_SetWorkDir           Equate(IShellLInk*100H + 24H)

VtableQType     Queue,type
VtableID          Long
pVtable           Long
            end

IIDQType        Queue,Type    ! pairs an interface with its IID
eInterface        long        
IID_Address       long
InterfaceName     string(eDebugLabelLen)
        end

MethodNameQType queue,type
eMethod           long
MethodName        string(eDebugLabelLen)
        end

IBeerDrink  equate('JimKane')

One equate is created for each interface and for each method. In the code when I want to do any thing with an interface I use the symbolic name: IShellLink or IPersistFile. If I need to store or retrieve a pointer to pointer to the interface (see part one for an explanation) VtableQ.VtableID holds the equate and VtableQ.pVtable holds the pointer. The importance of this is seen in the release method code. When the job is complete and it is time to release all remaining pointers, all that needs to be done is loop through VtableQ and call the release method for each pointer that was acquired, like this:

Figure 3. The ReleaseAll method.

hOleClType.ReleaseAll        Procedure()
I    long
Recs long
    Code
    Recs = Records(SELF.VTableQ)
    Loop I = Recs to 1 by -1
       Get(SELF.VTableQ, I)
       If Errorcode() then break.
       SELF.Release(SELF.VTableQ.VtableID)
    end

Once all the interface pointers are stored in a queue, managing them is pretty easy.

Likewise in part two I had code that typically went like this:

Figure 4. Previous code to call QueryInterface.

!Get IPersistFile Interface by calling 
! QueryInterface in IShellLink
hr = ICall2p(ppVtable_IShellLink, IShellLink_QueryInterface,| 
   Address(IID_IPersistFile), Address(ppVtable_IPersistFile))
If hr<0 then
  Clear(ppVtable_IPersistFile)
  Res = Return:Fatal
  Do ProcedureReturn
end

One of the problems is if an error happens (and it always does if someone is with you watching), and do ProcedureReturn is called, you don't know which call failed. Ideally, if you could pair a string containing the interface name with the pointer to the interface you could make a more meaningful error message. The remaining two queues pair one of the equates for an interface or method with its name stored in a string. The IIDQ also stores the IID for the interface along with the string name. IIDs are needed in several of the API calls. The information is loaded by calling these methods:

Figure 5. Typical method calls for loading IIDs.

SELF.AddIID(IShellLink,Address(IID_IShellLink),'IShellLink')   
! pair equate IShellLink with its IID
! and name stored as a string
SELF.AddMethodName(IShellLink_SetPath,'IShellLink_SetPath') 
! pair IShellLink_SetPath equate 
! for the IShellLink SetPath 
! method with its name stored in a string

Now use the information stored in the management queues to redo the above call to QueryInterface:

Figure  6. The QueryInterface code.

hOLEClType.QueryInterface   Procedure(long eInterfaceToQuery, cr.gif (846 bytes)
   Long pIIDNeeded, long eInterfaceNeeded )
pVtableNeeded  Long(0)
pVtableToQuery long(0)
hr long(0)
  code
  !using the interface equate, get the pointer
  pVtableToQuery = SELF.GetVtable(eInterfaceToQuery)  
  If ~pVtableToQuery then Return Return:Fatal.   !abort on error
  HR = ICall2p(pVtableToQuery, eQueryInterface, pIIDNeeded,|
    Address(pVTableNeeded))
  If SELF.Failed(HR,'QueryInterface', eInterfaceToQuery, |
     eQueryInterface)     !make the call and handle errors
    Return(Return:Fatal)  !on failure return an error code
  else
    ! On success add the new interface pointer to the vTableQ
    Return(SELF.AddVtable(eInterfaceNeeded, pVtableNeeded)) 
  end

Notice the Failed method is called with the equates for the interface and method so it can use the information in the queues to manufacture a decent error message or progress message. Depending on a simple setting, Failed will display a message on both success and failure so you can follow the progress of the code, on error only for debugging, or display no messages for released code. For details examine the code for the Failed method in the downloadable code.

Notice in the above discussion of the three principle queues nothing was specific to the current project of creating a shortcut. For any specific project just take holeClType and derive the class to add the code specific to your problem. HoleClType also contains another helper class called strcl, short for string class, that helps with wide string to C or Clarion string conversions which you will also need. It also can handle bstrings but that code will not be needed for this project.

While I will not specifically discuss the string class one method needs special mention. When presented with a cstring that needs to be converted to a wide string, there is always a problem knowing how much larger a buffer or string to allocate at compile time for the wide string. To solve that problem the CtoWStrAlloc method dynamically allocates memory for the wide string output, and the caller is required to dispose of it. While not revolutionary, it is a valuable technique that may be of interest to some.

Very often when I write a class that I know will be derived each time it's used, I write a starter class that reminds me how to derive the class and of some things I may want to do. If the reader (that would be you) would like to follow along on how to build the final class, open the downloadable zip file and load OLEStart.Inc and OleStart.Clw. That is my "cheat sheet" for starting an OLE interface project. Just unzip the downloadable zip file to an new directory, register the template if it is not already registered then rename OleStart.clw and OleStart.inc to something related to your project.

In this case I used scut.clw and scut.inc. So the first task is to rename OleStart then do a search and replace for OLEStart and replace with ScutCl through both files. Notice Scut.Inc includes holeCl.inc and strcl.inc. As a result, I don't need to add those files to the project, just Scut.inc which in turn will add holecl.inc and strcl.inc. Notice also the Class(HoleClType) which tells the compiler this class is derived from HoleClType. With that done, Scut.Inc looks like this:

Figure 7. The ScutClType class.

!ABCIncludeFile

OMIT('_EndOfInclude_'_SCutPresent_)
_SCutPresent_ EQUATE(1)

!Other Classes
  Include('HoleCl.Inc')
  Include('StrCl.inc')

!Equates - will be seen by using program
ScutClType  Class(hOleClType),type,module('Scut.CLW'),cr.gif (846 bytes)
    LINK('SCut.CLW',_ABCLinkMode_),DLL(_ABCDllMode_)
Init                    Procedure(byte pDebugMode=0),byte,proc
Kill                    Procedure()    
        end
 !_EndOfInclude_

As can be seen, there is nothing in this class other than the Init and Kill methods! The method I need is:

CreateShortCut Procedure(ShortCutStructType ShortCutStruct),byte

In support of that prototype I also need to add the ShortCutStructType that collects the information needed to create a shortcut:

Figure 8. Data structure for creating shortcuts.

ShortCutStructType Group,type
lpTarget             long     !can't be 0
lpDesc               long     !can be 0
hotkey               ushort   !can be 0 or a keycode
lpIconPath           long     !May not be null, but can use 
                  ! same value as lptarget
IconIndex            ushort   !0 is the 1st icon
lpWorkingDir         long     !Can be 0 or NULL
lpLinkFileName       long     !file name with optional path for 
                              ! the .lnk file to create
SpecialLocation      long     !special location to 
                              ! create shortcut, 0=desktop
  end

With that added before the class definition, Scut.inc is ready!

Now, open Scut.clw and change OLEStart to ScutCl as before. Notice OleStart had places reserved to add the interface and method equates:

!Equates for Interfaces
!1-19 is reserved for hOleCl interfaces
    Itemize(20)
    !List all interfaces called here
    end
!Vtable Offset - List all methods called
!Method           equate(Interface*100H      + 0CH)

The method equate is manufactured from its interface name plus the offset down the vtable required to get to the method. This serves to make a unique equate for the method and stores the offset. To complete this task, fill in the list of interfaces in the itemize list and the list of methods in the method list. Once again, the method offsets come from .h files (Ask Bill syndrome again!).

The Init and Kill methods are fairly self explanatory. Since Scutcl is derived from hOleClType to be sure the hOleClType class's Init and Kill methods are called. The format is as follows:

Figure 9. The Init and Kill methods.

ScutClType.Init                Procedure(byte pDebugMode=0)
Res byte,auto
    code
    Clear(SELF)
    res = Parent.Init(pDebugMode)
    If ~Res then
       SELF.AddIID(IShellLink,Address(IID_IShellLink),|
          'IShellLink')
       SELF.AddMethodName(IShellLink_SetPath,|
          'IShellLink_SetPath')
      !Other interfaces and methods deleted for clarity
    end
    Return(Res)

ScutClType.Kill      Procedure()
  code
  Parent.Kill()
  Return

Notice the general pattern of:

code
  !code before parent call
  !Parent call - calls corresponding hOleCl method
  !Code after call to parent
  return

This ensures the parent holecl methods are called. As you may guess, on Init hOleClType needs to create the queues I described and destroy them on Kill. One of the side benefits of deriving a class is all these mundane details can be hidden in the base class or down in the 'hole' in this case! I just love to put the busy work out of site so I can concentrate on the rest.

After CreateShortCut validates the data, the next task is to get the two interfaces needed by calling CoCreateInstance and then QueryInterface method to get the second interface. Since calling CoCreateInstance and QueryInterface is something that is very common and happens many times in any OLE program, there are special methods for each in hOleClType. The code is as follows:

!Get the IShellLink Interface with CoCreate
 If SELF.CoCreate(Address(Clsid_IShellLink), IShellLink) |
   then return(Return:Fatal).

 !Get Ipersist_File from QueryInterface
 If SELF.QueryInterface(IShellLink, Address(IID_IPersistFile)|
   , IPersistFile ) then Return(Return:Fatal).

Take a minute and compare these two calls with the originals in part two. They're much less complicated. Progress! I may get out of this alive after all. The really nice thing is all the busy work not specific to this project is hidden down in holeClType and can be reused in any OLE project. In fact, I've used it in several projects already. Upon inspection holeClType has several "hidden" features not needed for this project but which are so commonly needed on larger similar projects that they are included.

The next section of code tells the IShellLink interface different properties needed for the short cut. This involves using the Icall.A assembler piece. However, you need not panic; it's well wrapped in holeClType so it should not be too scary.

!Set Short Cut Attributes
If SELF.ICall(1, IShellLink, IShellLink_SetPath, |
    SCS.lpTarget) Then Return(Return:Fatal).
If SELF.ICall(2, IShellLink, IShellLink_SetIconLoc,|
    SCS.lpIconPath, SCS.IconIndex) Then Return(Return:Fatal).

The first parameter (1 or 2) above tells Icall how many parameters will be passed to the interface. This is followed by the equate for the interface and method to call. In other words, the first line above calls IShellLink.SetPath(SCS.lpTarget) where SCS.lpTarget is the address of the file name to which the shortcut should point. This information is passed to the CreateShortcut method. All the details of getting the vtable pointers and method offset are taken care of in the holeClType never to be written again! On error, just return with a result code of return:fatal which signals the error condition.

The next section of code gets the path to the location where the shortcutshould go. This is often the desktop or some other special place. After calling two APIs to get the path, the code stores the path in the local variable szpath, a cstring. Unfortunately the last interface call requires a wide string. Since the length of the path string is unknown at compile time, the code will allocate heap memory for the wide string. The alternative would be to allocate a very large buffer certain to take care of any path. This is the ctowstralloc call in the string class mentioned earlier:

Figure 10. Convert to a wide string and save the shortcut.

!Now convert to a wide string
wszLinkFile &= SELF.StrCl.CtoWideStrAlloc(szPath)

!Last but not least, save the shortcut to disk 
! then cleanup and exit:
Res = SELF.ICall(2,IPersistFile, IPersistFile_Save, |
  Address(wszLinkFile), True)
Dispose(wszLinkFile)
If res then Return Return:Fatal.

Notice after the final interface call that rather than testing immediately for an error, the dynamically allocated wide string is disposed of and after that a test for error on the last Icall is done.

As soon as the last call completes the shortcut is created. All that remains is to release all the interface pointers and clean up. Because all the interfaces are neatly stored in queues, to release them just loop through the queue and call the release method. This is already coded in the Kill method so all you have to do is call scut.Kill(). The code that actually goes in the application to create the shortcutis as follows:

Figure 11. Application code to create the shortcut.

OLECl.InitOLE(CoInit_ApartmentThreaded)
ScutCl.init(2)   !0=no errormessages, 1=msg on error only, 
                 !2=verbose=msg on sucess and fail
Clear(ShortCutStruct)
!pgm to run when shortcut clicked
ShortCutStruct.lpTarget  = Address(Target)        
!Optional descriptions 
ShortCutStruct.lpDesc     = Address(Desc)
!Shortcut .lnk file name
ShortCutStruct.lpLinkFileName     = Address(LinkFileName)
!Optional working directory
ShortCutStruct.lpWorkingDir         = Address(WorkingDir)
!Take icon from target file
ShortCutStruct.lpIconPath         = ShortCutStruct.lpTarget   
!0=Desktop
ShortCutStruct.SpecialLocation    = SpecialLocation           
If ScutCl.CreateShortCut(ShortCutStruct)
   Message('Create Shortcut Failed')
else
   Message('Create Shortcut Worked')
end
SCutCl.Kill()
OLECl.killOLE()

When the demo SCUTABC.EXE is run the success (or failure) of each and every call is reported in a message since the ScutCl.init method was set for verbose. There are quite a few messages. To bad we don't get to bill customers by the step or message box! When ever you get tired of the messages just recompile with a debug setting in scut.init of 0 or 1. There are a few other goodies in holeClType for common OLE tasks such as memory allocation and expanded error handling which are available should you need them.

The nice thing is the next time out there is much less work now that the helper OLE Class is done. Just add the template, create the top level project specific class from OLEStart.CLW and call OLECL.INIT to initialize OLE. Then initialize the top level class and off you go.

Download the source code


Jim Kane was not born any where near a log cabin. In fact he was born in New York City. After attending college at New York University, he went on to dental school at Harvard University. Troubled by vast numbers of unpaid bills, he accepted a U.S. Air Force Scholarship for dental school, and after graduating served in the US Air Force. He is now retired from the Air Force and writing software for ProDoc Inc., developer of legal document automation systems. In his spare time, he runs a computer consulting service, Productive Software Solutions. He is married to the former Jane Callahan of Cando, North Dakota. Jim and Jane have two children, Thomas and Amy.

Printer-friendly version

Reader Comments

To add a comment to this article you must log in.

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links