Beyond this example, all I can say is look at as many code traces as you can. I have spend hundreds of hours single stepping through code. In a masocistic kind of way, it's helped me understand a great deal. :)
Object:  dd 12345678h

pObject: dd OFFSET Object

mov edx, OFFSET pObject | mov edx, pObject
mov edx, [edx] | mov edx, [edx]
(edx = OFFSET Object) | (edx = 12345678h)

mov edx, OFFSET pObject | mov edx, [pObject]
mov edx, [edx] | mov edx, [edx]
(edx = OFFSET Object) | (edx = 12345678h)
The OFFSET is just a number, but it is special in that this is relitive to where the code resides in memory. If you use OFFSET, you are just moving a number into a register. Just remember everything is a number in ASM. Brackets (or memory references w/o brackets in MASM) get the number at another number (the memory address or number in a register). I really hope this helps some. ;)

I almost did what I think Ernie almost did. :tongue:
Posted on 2001-08-29 00:01:31 by bitRAKE
lol ok. I just tried a bunch of COM stuff. I also performed about 30 illegal operations :)

I came to the conclustion that instead of using pInterface, but directly moving the pInterface into edx instead of moving from pInterface to edx, then from to edx.

just all you have to do is to edx.

There. That's one optimization (I think) Tell me if I'm completely wrong again lol
Posted on 2001-08-29 00:22:55 by Kenny
Wait never mind because you need pInterface later in the call.

DOH!

Ok, I've had enough. Ernie is the man.
Posted on 2001-08-29 00:26:56 by Kenny
WAIT! What if it was stored in eax, would that work?

Would it optimize anything?
Posted on 2001-08-29 00:27:45 by Kenny
Kenny:

WAIT! What if it was stored in eax, would that work?

Would it optimize anything?


In this case you would get this:


mov edx, eax
mov edx, [edx]


which can be optimized into this:


mov edx, [eax]


For the class stuff I'm writing with NaN, we have a macro called method which is almost exactly the same as coinvoke.. And I have modified it some time ago to optimize it when you use registers.
note: in the class model, you don't have the 'mov edx, ', becase a class object pointer is something else than a COM object pointer (it doesn't use the indirect method with the vtbl pointer).. so keep this in mind.


METHOD MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG
LOCAL istatement, arg, invokereg
IF (OPATTR (pInterface)) AND 00010000b
invokereg EQU <pInterface>
ELSE
mov edx, pInterface
invokereg EQU <edx>
FOR arg, <args>
IFIDNI <&arg>, <edx>
.ERR <edx is not allowed as a Method parameter>
ENDIF
ENDM
IFIDNI <&pInterface>, <edx>
.ERR <edx is not allowed as a Method parameter>
ENDIF
ENDIF
istatement CATSTR <invoke (Interface PTR[invokereg]).>,<&Function, invokereg>
IFNB <args>
istatement CATSTR istatement, <, >, <&args>
ENDIF
istatement
ENDM


for the coinvoke macro, I think you would only have to add 'mov edx, ' or something but I haven't tested it..

IF (OPATTR (pInterface)) AND 00010000b
This line checks if the pInterface parameter is a register.. If it is, it sets invokereg to that register, if it's not, the parameter is assumed to be a variable, and the normal method (mov edx, variable), is used. As you can see, for the method macro, I could remove the check that warns you if you use edx as a parameter, when using a register as object pointer.. Because in this case, edx won't be used... But this is different with coinvoke because coinvoke always uses an additional mov edx, .

Ernie: maybe you can use some optimizations of this for coinvoke..

Thomas
Posted on 2001-08-29 02:11:06 by Thomas
Afternoon, Kenny.

Just a note to remind you that pInterface is a pointer. That's why you need the 'mov edx, ' after 'mov edx, pIterface'.

Cheers,
Scronty
Posted on 2001-08-29 02:17:22 by Scronty
Scronty: Yes, I didn't realize this at first. This whole vtable garbage threw me way off. I also had like 3 hours of sleep that night, so the combination didn't help any.

Thomas: Yeah, that's what I was getting at, but I didn't know how to say it. Dang, I'm glad someone knows what I was trying to say :)

-- ----------

Wow, well, to make annother long story short, I think I finally get it now :) It'll just take a while to sink in :)

THANKS SO MUCH THOUGH!!! You guys don't know how cool it is to help out a newbie like me. hehe
:alright:
Posted on 2001-08-29 14:10:35 by Kenny
Kenny,

Here's a corrected version of what I started last night:

Maybe if I explain why that code works, you will see how MASM is representing the code. So here goes....

All addresses following are purely arbitrary for the sake of discussion, though they all are 32 bit quantities.

Let's define some memory locations for a simple object (it's probably not a COM object, but that's OK for this discussion)



address contents

Function1:
0300 0000 function method 1 code
Function2:
0300 1000 function method 2 code

vtable:
0380 0000 0300 0000 (pointer to function 1)
0380 0004 0300 1000 (pointer to function 2)

object:
0400 0000 0380 0000 (pointer to vtable)

ObjX: ;(object reference)
0500 0000 0400 0000 (pointer to the object)





Say I have an Object X, and I save a reference to Object X at address 05000000H. An object is a blob of data that holds pointers to function tables ( the infamous "virtual" or "v-tables"). These tables hold pointers to functions.

By a pointer I mean at address 05000000H I can find a value corrosponding to address of the object itself, in other words 04000000H is stored at 05000000H.

Now since an object points to a v-table, address 04000000H would hold the address of a list of functions. That list is the vtable, located at 0300000H (plus). Address 0300000H holds the starting address of the first function, address 03000004 holding the 2nd function, and so on.


code:--------------------------------------------------------------------------------
mov edx, ; brackets optional here, will always do 'mov edx, [04000000H]'
mov edx, ; brackets mandetory
invoke .0 ; call function 1 (ignore the arguments for now)
--------------------------------------------------------------------------------



Let's do this again slow and in english:


code:--------------------------------------------------------------------------------
mov into edx the contents of address
{ObjX = 05000000H, value at that address is 04000000H
so edx is now 04000000H}

mov into edx the contents of the address held in edx
{edx = 04000000H, value at this address is 03800000H
so edx is now 03800000H}

invoke the function at address held in edx+0
{edx = 03800000H, we add zero offset for the 1st function,
look at address 03800000H, value at this address is 00300000H
so we jump to (call) address 00300000H}
--------------------------------------------------------------------------------

That's the best I can explain it.

Good luck.
Posted on 2001-08-29 18:24:42 by Ernie
Thomas and Nan,

I sure wish you guys would hurry up and publish so I could follow along with what you are doing.

Yes, it's true coinvoke could be slightly optiomized for register usage, but since I don't use my registers that way... ;-)

It's my library and I'll keep it as it is.

Actually, one should never assume the eax, ecx and edx registers get left as they were anyway, and that's pretty flimsy, but that's my reason for not changing coinvoke.
Posted on 2001-08-29 18:30:25 by Ernie
Ok Ernie...

lol No wonder I was confused! COM is pretty pointless dude!

There are so many other, more efficient ways of doing things than that. That's like using a database to store the locations of your programs PROCs :)

Well whatever. I guess Microsoft is pretty full of themselves.

So, if I knew ahead of time what the vtable was going to point to, I could theoretically make my COM code faster right?
Posted on 2001-08-30 16:42:24 by Kenny
Certianly, Kenny, but your program might break if the COM objects you used changed - like new versions. That is one of the reasons for COM - it was intended to be a more flexible way for code reuse. More flexible than PROCs in a DLL is what I mean, and I believe it accomplishes that.

<RANT-CELL charge-mode=ON>Machines are cheaper than people and computers are getting faster - people aren't. So, they can pay you bunches of money to write reusable code, fire you in a year, and use your code after you've died. I need to move this to the ranting section of the board...<RANT-CELL charge-mode=STANDBY>
Posted on 2001-08-30 18:45:05 by bitRAKE
Sure, if you know the address you can call it direct. That's called procedual code, and it has nothing to do with COM objects.

The whole point is you DO NOT know where the object will be. You probably didn't write it. Nor do you have to know where it is. It could be in a dll running in your process, or another .exe in another process. It could be around the world and you're connecting to it over a network.

To COM, it don't matter. All that matters is the binary layout of these members.

COM hands you an object pointer and off you go.

When I use my CoLib to make an object, I place the vtable in the .data section of my program, and the object is created on the heap (the COM heap that is). When MSVC makes an object, that table is created on the fly (mostly for inheritance reasons) in the object itself. When *using* an object, you don't care where that is, because you follow the rule by following this binary pattern.

BTW, when talking to an object not in your own process (other .exe or computer), the COM parts of the operating systems create what's known as a proxy object. The proxy's binary layout looks, you guessed it, just like the same layout pObj->pVTble->pMethod as if the object was running in your process. COM sticks some dll code in there so when you run what you *think* is one method in your own process, you actually call in the remoting code to access the object where it really resides.

I'll suggest you go read my first tut on acessing COM objects and try to see *why* it's done this way. (get the copy off hutch's site in SP2, the one on my site has been reported to have a Word virus inside and I havn't disinfected it yet).
Posted on 2001-08-31 16:09:55 by Ernie
I think I will :)

I'm converting the DX8 includes to FASM, because fasm is easier for me to program in, and I have the source to it. I'm sure knowing what I'm doing will help out a little :)
Posted on 2001-08-31 16:12:39 by Kenny
Hello Kenny. Consider this:

When you call QuerryInterface() the system creates a "pointer" to a vtable - pVtbl. It would be nice if QuerryInterface returned this pointer in the pInterface variable. But it does'nt it returns it's ADDRESS. So pInterface ACTUALLY holds a "pointer to a pointer". That is why you need the two moves in coinvoke.

mov edx, pInterface ; get the ptr to pVtbl
mov edx, ; get pVtbl (now edx points to vtable)

Hope this helps


GF
Posted on 2001-09-08 10:43:21 by gfalen
hehe thanks so much. :alright:

I've actually learned a bunch since I posted this last. Recently I wrote a macro for FASM that does COM style invoking.



macro calldx object,proc,[arg] ; invoke COM method
{ ..args equ
..invoke = $ }
{ ..args equ arg ..args }
{ if ..invoke = $
if ~ ..args in <>
push ..args
end if
mov eax,object
push eax
mov eax,[eax]
call [eax+proc+0ch]
end if }

macro mcalldx number,object,proc,[arg] ; invoke multiple COM methods of the same type
{ ..args equ
..invoke = $ }
{ ..args equ arg ..args }
{ if ..invoke = $
if ~ ..args in <>
push ..args
end if
repeat number
mov eax,object
push eax
mov eax,[eax]
call [eax+proc+0ch]
end repeat
end if }
Posted on 2001-09-09 13:43:55 by Kenny