![]() |
|
Published 1999-07-20 Printer-friendly version
One day a long time ago when CompuServe and not the newsgroups reined supreme, someone asked how to create a shortcut using Clarion. After a quick trip to my trusty MSDN (Microsoft Developer Network) CD, it became apparent to me that to create a shortcut I had to call an OLE interface called IShellLink. Unfortunately, while Clarion 5 supports the Topspeed, Pascal, and C calling conventions, it does not natively support calling OLE interfaces.
There are two general methods of calling OLE objects: late binding and early binding. Most OCX and OLE code Clarion programmers are use to seeing is late -binding, where the address of each method is looked up via the Idispatch API call before each call. No compile-time knowledge of the order the methods are in is needed for late binding; only the method name is required.
The early binding discussed here a more efficient approach, although it requires that you know the number of methods and the order in which they appear in the OLE interface. The address of the area which contains the method entry points is determined once and stored, and as all of the methods are at a fixed offset from this point they can subsequently be called based on that offset.
While calling a method based on its offset from a known address is slightly less efficient than the normal way of calling an API function directly, the extra step is quick and saves the trouble of storing the address for each method separately.
I posted an answer to the original question about creating a shell link that in effect that could have been summed up in two words: "No Way." Never liking to accept limits, when I next had some free time (about a year later), I read all about how to call OLE interfaces. I realize that with a little straightforward assembler work I could write one procedure (or a family of procedures) that would handle all the dirty work for me. Once you understand this one little ugly piece and how to use it, you'll be able to do anything Windows can do. Sorry, but no more excuses then!
My reading showed that a call to the API CoCreateInstance procedure returned the keys to the kingdom, so to speak, or at least a pointer to a pointer to the entrance to the OLE world. If only I could take that pointer to a pointer and call the address at the end of the chain, OLE would be mine.
Actually what CoCreateInstance returns is a pointer to a pointer to a table of addresses, called a vtable. A vtable is a simple list of addresses of all the methods in an OLE interface. For those not using OOP think of a method as a procedure. An OLE Interface can be thought of, for this discussion, as a collection of methods or procedures that when called does something. It's actually a whole lot more but that will do for now.
Calling an OLE interface is a two step process. First you navigate the pointer chain to get to the vtable, and then you determine where in the vtable the procedure to be called is located.
I'll take a common OLE interface called Iunknown (which also pretty well sums what I've said about OLE at this point!) with three methods: QueryInterface, Addref, Release. For now don't worry about what the methods do. Listing 1 shows what the mystical vtable would look like in Clarion syntax:
Vtable_IUnknown Group
AddressofIUnknown_QueryInterface long
AddressofIUnknown_Addref long
AddressofIUnknown_release long
end
Now I'll diagram the chain of pointers to arrive at a
vtable so it's not so abstract. I'll store the pointer to
a pointer in a Clarion long variable called
ppVtable (pp has nothing to do with the
bathroom in this case, it's a shorthand for pointer to a
pointer) and the offset into the vtable in a long variable called
OfsMethod. Figure 1 uses some arbitrary addresses to
demonstrate how to get to the code for the
Iunknown.AddRef method. Since Addref is
one down the list from the top and each long is four bytes,
it's address is stored at the start of the vtable plus an
offset of 4H.
| Arbitrary Address | Variable Name/Description | Contents |
| 120H | OfsMethod | 124H |
| 124H | ppVTable | 58204H |
| 58204H | pVTable - pointer to the vtable | 7F800H |
| 7F800H | Vtable - Address of 1st method AddressofIUnknown_QueryInterface) | 9AB00H |
| 7F804H | Vtable - Address of 2nd Method (AddressofIunknown_AddRef) | 9C000H |
At address 9AB00H sits the executable code for
Iunknown.QueryInterface.
At address 9C000H sits the executable code for
Iunknown_AddRef.
So given a pointer to a pointer for a vtable (in this case the
value 58204H), if you look in memory at that location
you get the pointer to the vtable (or pVTable) at
7F800H . If you look in memory at 7F800H
you find (at long last!) the start of the vtable. Jump down to an
offset of four bytes and look in memory at 7F804H and
you're all set: 9C000H is the correct target
address. This means lots of peeks and jumping around but it's
a relatively simple chain to follow. The chain navigation code only
has to be written once, and after that's done hopefully you
can get a lot of good use out of it.
Thinking further, the problem of calling an OLE interface (after obtaining a pointer to a vtable) really has five parts (one of which I've kept a secret until now):
I'm visually oriented so I'll turn that into a picture. A stack is an area of memory, just like a stack of books where each "book" is 32 bits. Every time you push something onto the stack it goes on top. Every time you pop something off the stack, a book goes away and exposes the book underneath. A stack grows from high memory to low memory. Here's what the stack looks like in a simple procedure call (not an OLE interface ) and diagram:
Module('SomeDll.Dll')
AddTwoNumbers(long p1, short p2),long,pascal
End
Result = AddTwoNumbers(2,3)
!After call to AddTwoNumbers
Message('Result is:' & Result,'Result')
AddTwoNumbers procedure(long p1, short p2) Result Long Code !Start of procedure Result = p1 + p2 Return Result
Here's what the stack looks like at the comment !Start of procedure (the absolute address values are arbitrary):
Address Stack Contents 1F4H ReturnAddress - address of Message statement 1F8H p1=2 1FCH p2=3
Notice that although P2 is a short, on the stack it
takes up 32 bits from 1F8H to 1FBH. In
fact, all parameters on the stack take up 32 bits no matter if the
value being passed is a byte, short or
long! If a parameter is longer than 32 bytes, such as
a cstring, then rather than trying to put the entire
cstring on the stack, just its address is placed on
the stack.
After the execution of the Return Result statement,
the three values shown above are removed from the stack. This is
how the Pascal calling convention works. All OLE interfaces use the
Pascal calling convention as do almost all API calls.
To complete the picture look at the assembler code to call
AddTwoNumbers. It's not very complex:
Mov eax, p2 ! move p2 into the eax register
Push eax ! Push a long onto the stack in this case
! p2 with a value of 3
Mov eax, p1 ! put p1 into eax, want to guess where its
! going next?
Push eax ! p1 on the stack
Call AddTwoNumbers ! puts the return address on the stack
! and jumps to the start of the procedure
! AddTwoNumbers.
After that entertaining jaunt into the world of assembler and
the stack lets get back to the problem at hand. Say you have a
pointer to a pointer to a vtable for an OLE interface call
IshellLink. You'll store that value in a long
called ppVtable_IshellLink. Further, say
IshellLink has a method call SetPath that
can be called with one parameter, the address of a
cstring. The purpose of the method is to set the path
and file name for the target of a shortcut, i.e. the program that
should be run when a shortcut is clicked. If Clarion directly
supported OLE, you might think of this as calling a class method
and would prototype it something like this:
IshellLink.SetPath(*cstring szPath),Pascal
and call it (if it worked!) to take a step to creating a shortcut to notepad like this:
SzPath = 'C:\Windows\NotePad.Exe' IshellLink.SetPath(szPath)
Since *cstring is a pointer to a
cstring, or in other words the memory address of the
cstring, you could also prototype and call the above
like this with the same result:
IshellLink.SetPath(long pszPath),Pascal
And use the address() function to get the
address:
IshellLink.SetPath(address(szPath))
Keep in mind Clarion does not support this calling convention so the above is hypothetical.
If you review the requirements for calling an OLE interface listed above, the picture you need on the stack for this to work is:
| Arbitrary Address | Value or Description |
| 200H | Return Address to Clarion code after the call to the OLE Interface |
| 1FCH | ppVtable |
| 1F8H | address(szPath) |
The other piece of information you need to be able to jump to
the OLE method of choice is the offset to the SetPath
method. You'll need to put that on the stack as well.
It's looking like to call an OLE interface with one parameter what you need is a magic function called
ICall1P(long ppVtable, long OfsMethod, long p1),long,
pascal,proc
that can create a stack like the one shown above and jump to the correct vtable entry.
Now you can write the Clarion code part so you can picture it better, diagram the stack this magic procedure will start with, and lastly present the assembler code to accomplish what you need.
Module(ICall.a) ICall1P(long ppVtable, long OfsMethod, long p1),long,pascal,proc,Name('ICall') End
Data:
SzPath cstring('C:\Windows\Notepad.exe')
PpVtable long(58204H) !pointer to a pointer for the
! vtable for IshellLink.
OfsMethod long(50H) !offset to SetPath
Hr Long,Auto !Ole Return code, < 0 = error
Code:
Hr = ICall1P(ppVtable, OfsMethod, Address(szPath))
If Hr<0 then
Message('Call Failed, blame Bill')
else
Message('Call Worked, blame Jim Kane)
end
After the calling ICall1P this is what the stack
will look like:
ReturnAddress = address of If Hr<0 after ICall PpVtable OfsMethod P1
If you compare that to the desired stack frame above, you will
see the extra OfsMethod in there. Also there are some
mundane details of preserving registers. You can think of a
register in the CPU as if it's a 32 bit variable. The
registers you will deal with are eax, ebx, ecx, edx.
The code to get the stack from the starting point just above to the
desired status prior to turning things over the OLE and Microsoft
is as follows:
Public ICall:
(*The parameters, how ever many there were are on the stack*)
(*code to save ebx,ecx,edx-omitted for clarity *)
Pop ecx (* pop the return address off the stack *)
(* into ecx *)
Mov Save_ret,ecx (* save the return address for later *)
Pop eax (* eax = ppVtable*)
Pop ebx (* ebx = offset into vtable*)
Push eax (* put the ppVtable back onto the stack *)
(* now that the offset is out of the way*)
Mov ecx,[eax] (* ecx = contents of memory at eax = *)
(* pVtable and NOT ppvtable any more *)
Add ecx,ebx (* add in the offset down the vtable*)
(* ecx now points to the address you *)
(* want to call*)
call dword [ecx] (*put the return address on the stack*)
(* and go to the address pointed to by ecx*)
(* upon return eax = the return value so leave eax alone! *)
(* the return from the OLE method also took the *)
(* parameters p1...pn off the stack.*)
mov ecx, Save_ret
push ecx (* put the return address to the Clarion *)
(* calling point back on the stack *)
(* code to restore ebx,ecx,edx- omitted for clarity*)
ret 0 (* I love it when a plan comes together!*)
So there you have it. Add those few lines of assembler to your
code and OLE away just like the big boys. Actually just choose
Project from the main menu and add Icall.a to the
external source module section, add the prototypes for
Icall1P to the global map. The project system will
take it from there - the assembler will be called to assemble
Icall.a and the Clarion compiler will compile the rest. The project
system recognizes the portion that needs to be assembled by the .a
extension.
The nice thing is regardless of how many parameters the
interface method has, the code above should handle the details of
calling the interface. It requires just two inputs: the
ppVtable available from CoCreateInstance
and the offset into the vtable available form OLE header files or
in some cases from a free Microsoft utility called OLEView. Now you
have reduced the barrier to COM in Clarion to finding the needed
constants and preparing the interface method parameters. Next time
I'll take on those challenges and show how to create a
shortcut the OLE way.
So if you catch me on the newsgroups, feel free to ask a question. Just remember it may take a year or more to get an answer!
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.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239