Using OLE instead of the standard message loop.

I have recently been forced to learn - mainly copy-and-paste type learning! - a little OLE and windows interface work in order to create PARGB32 bitmaps for display as menu item decorations. All incidental really, but while doing the rather tortuous research necessary for this task I came across an official MS example that used a sub classed OLE interface as the window procedure!

This is FASCINATING and completely new to me. I have only ever used either a straight C-style message loop/winproc or - when feeling very adventurous! - the same type of loop/proc but handing off to a class to hold the actual message handler functions and associated data.

The example I am talking about is here:

http://msdn.microsoft.com/en-us/library/bb757020.aspx?s=6

All this 'pFactory' and 'CoCreateInstance' business really catches my attention with its detail and intricacy! OLE as a whole is something I have tended to avoid as it had little relevance to the type of applications I wrote in the past. I thought it was primarily for cut-and-paste and to replace DDE in the windows shell. It seems I was wrong!

Can any of you chaps point me to a source or book that could teach me the ropes of using OLE as an actual window process - preferably in straight C++ and not MFC or .NET?
Last edited on
If you are talking about COM, that's an interest of mine also. I became interested in it way back in the 90s with Visual Basic 4 through 6. A lot of the controls used in Visual Basic were ActiveX Controls which are high powered COM objects.

"Inside COM" by Dale Rogerson is a good introductory book. Another book I worked hard was "Inside DCOM" by Guy and Henry Eddon. A lot of folks liked Dob Box's book "Essential COM". Also there are some good ATL books.

Basically, Microsoft's theory was that C++ coders would use ATL to do COM. Personally, I don't like cookbooking things, and I have time, so I don't use it but do everything with bare bones low level code, including building ActiveX Controls that way. There are some hardcore COM/OLE experts at this site...

http://www.jose.it-berater.org/smfforum/index.php

Its not C++ oriented but rather PowerBASIC oriented, although there are some C++ coders there, particularly me. I've posted a lot of C, C++ and PowerBASIC COM code there...

http://www.jose.it-berater.org/smfforum/index.php?board=362.0

Basically, the reason I like COM/OLE is that it is an alternate object model somewhat different from the typical C++ object model. It emphasizes encapsulation and polymorphism rather than inheritance, and supports modular architectures very well. I get a blast out of being able to interoperate freely between PowerBASIC, C/C++, old VB6, and .NET.

Likely my most ambitious project was creating an ActiveX Grid control, which I used PowerBASIC for. I got my release build down to 49K, which compacts to 22K using UPX. Last year at this time I rewrote it in C++ (VC9), and got that one down to 43K. Just for kicks and giggles I'd like to redo it in C to see if I could get it as small as the PowerBASIC one. By the way, I managed to get that one working in 32 bit and 64 bit architectures. Sorry I'm bragging, its just that COM/OLE interests me too!
Last edited on
MANY thanks Freddie!!!

Yep, I think it is COM I am after. I also think in regards the bare code we feel the same way. I absolutely HATE object libraries and wrappers - all you are doing at the end of the day is learning a slightly different language, NOT how to use C++. There are so many books that pretend to 'teach C++ in 28 days' or whatever and all they are really teaching is how to use the project wizard!!!

I will give both those books a look over! I would like to be able to integrate my - fairly reasonable - knowledge of the the win32/64 API with these 'interface' commands which seem to duplicate a great deal of the functionality.

The first time I read through that example from MS I was completely floored by all the weird data-types and unfamiliar structures likes 'pfactory' and so on. However, after mulling it all over for a couple of days I don't really think it is so hard. All you seem to be doing is accessing more API functions - but through pointers to an underlying class system. Basically it looks like you ask the system to make you a class using 'CoCreateInstance' and then through a pointer to that new class you make use of IT'S member functions just as you would a win32 function.

Very cool - and all the weird data-types are actually quite appealing after a while!
Couple other good references are an article by Jeff Glatt entitled "COM In Plain C" Do a search on that and you'll come up with links to it. It was wildly popular, because C++ (for reasons I'll discuss later), largely obfusicates a really deep understanding of the object model itself. Actually, any book on ATL will cover COM in the raw pretty good - particularly Andrew W. Troelsen's "Developers Workshop To COM And ATL 3".

You need to look at the IClassFactory Interface as being the counterpart of a C++ Constructor. The IClassFactory::CreateInstance() interface method is where you perform your memory allocations to put an object together.

The way the Registry figures into the whole thing is cool, but not entirely necessary. At some of the links at my board at the above site I show how to load COM objects directly with LoadLibrary() and GetProcAddress(). So its totally possible to completely bypass the Registry. I usually don't though.

The reason why C++ classes are useful is that with them one can create the COM memory structure (which is a 'standard') quite easily, if you follow certain easy guidelines, and that can be convenient. However, it really is just a wrapper for what's actually going on in memory. That end of it had always intrigued me, as basically I'm a low level coder, and at the site above I give examples of creating simple COM classes and calling the interface member functions directly through the VTable with function pointers. Had all kinds of fun with that. Later I'll post here a little example of that. Gives good insight into the memory footprint of COM classes and interfaces (VTables).
Since you are interested in this, I have a fun little tutorial type thing I wrote up ages ago but never posted anywhere, but I think it does a good job of elucidating the basic COM memory layout, in spite of how effectively C++ attempts to hide it. I need to break the tutorial part in two piecies, and the 3rd piece is the program....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
  This program attempts to show and describe the memory foot print of a COM object.  One of the fundamental ideas of COM
  is the seperation of interface from implementation.  So when a COM object is instantiated there will be memory allo-
  cations in multiple places.  Specifically, there will be a memory allocation for the object itself, and in that memory
  block will be found instance data variables that will maintain 'state' for an instantiation of an object.  Also in that
  memory block will be pointers to structures known as virtual function tables or interfaces.  An object can support any
  number of interfaces, and for each interface supported there will be what is known as a virtual function table pointer,
  i.e., Vtbl*.

  So, for an object that supports two interfaces (as in the example below) there will be a block of memory allocated for
  the object itself, and then two seperate additional allocations for each of the two VTables or interfaces.  If each
  interface contains five functions/methods, in a 32 bit operating system, each interface will then require a 5 X 4 = 20
  byte allocation to hold function pointers to the implementations of the interface functions.  From this then you can
  see that a virtual function table or interface can also be likened to an array of function pointers, with each member
  of the array holding a pointer to a function.  In actual practice though arrays are seldom used to construct virtual
  function tables, because with present day compilers and IDEs type and parameter checking doesn't work as well as when
  structs are used.

  In a ceetain sense one could really say there are three categories of memory needing to be allocated for a COM object,
  i.e., 1) the base allocation for the object itself; 2) the VTables or interfaces; and 3) the interface functions
  themselves the addresses of which are stored in the VTables.  However, the writer/creator of a COM object only needes
  to concern him/herself with the first two; the compiler takes care of the third (that's why most of us program in a
  high/mid level language as opposed to assembler or machine code).

  So lets take a look at the code below.  Its a very simplified version of a COM object whose only purpose is to elucidate
  the memory foot print of a real COM object.  To begin with, note the interface #define.  An interface is just a specific
  kind of struct.  This program doesn't include ObjBase.h, so to use the interface keyword I had to include the define...

  #define interface struct

  which actually is in ObjBase.h.  Secondly, all COM interfaces inherit a COM System defined interface known as IUnknown.
  This is tremendously significant.  Whenever you have a pointer to a COM object - no matter what it is, you have an
  IUnknown*, and can query that object for other interfaces. You'll note that the IUnknown VTable contains three
  functions, which in C++ syntax are described as pure virtual functions the real declarations of which are in Unknown.h.
  And they actually resolve to function pointers themselves.  If you've gotten this far it might have dawned on you that
  COM memory layout isn't introductory material.  You'll need some understanding of pointers, memory allocations, and
  function pointers to grasp it.  But if you are interested in this material and are having a hard time, please read on.
  I am going to do my best to explain at least some part of it, and it is my hope that if you see the actual memory
  'dumped' as I'm going to do in this example, you will begin to grasp what is going on, and see the actual elegance of
  it!

  There is another issue I might mention too.  C++ in some ways obscures an understanding of what is really going on in
  memory with a COM object.  To really fully understand it and see its brutally naked structure you almost have to do
  it in C, and I might just do that for you!  But for now let's continue with C++, pure virtual functions, and the
  works!

  You see we have the IUnknown interface/struct which in a 32 bit operating system is going to create a memory block of
  12 bytes, i.e., four bytes for each function pointer.  Then we have an IX interface/struct and an IY interface/struct
  each of which inherits IUnknown.  What that means is that when a COM object is instantiated which supports interfaces
  IX and IY, the virtual function tables for each of these interfaces is going to have to supply room for five function
  pointers each - three for the IUnknown and two more for the supported functions Fx1(), Fx2(), or Fy1() and Fy2().  So
  the VTable for each will look like this...

  struct IX            //Offset from base allocation of VTable allocation (double these numbers for x64)
  {
   QueryInterface();   // 0
   AddRef();           // 4
   Release();          // 8
   Fx1();              // 12
   Fx2();              // 16
  };                   // =======
                       // 20 bytes
  or

  struct IY            //Offset from base allocation of VTable allocation (double these numbers for x64)
  {
   QueryInterface();   // 0
   AddRef();           // 4
   Release();          // 8
   Fx1();              // 12
   Fx2();              // 16
  };                   // =======
                       // 20 bytes

  Finally, we have a class 'Class A', i.e., 'CA', that publically and multiply inherits interfaces IX and IY.  Since we
  hope to create an instance of Class A in this program we're going to have to implement all the pure virtual functions of
  the interfaces, and we do that by simply providing cstdio puts or printf statements that they were called.  Take a look
  at class CA.  Having done that we can now instantiate class CA in main and locate the class on the heap so as to obtain
  a pointer to it, e.g., pCA...

  CA* pCA=new CA;

  Of course, we could call the IX and IY interface functions like this if we wanted to...

  pCA->Fx1();
  pCA->Fx2();
  pCA->Fy1();
  pCA->Fy2();

  And if we did we'd get this output...

  Called Fx1()
  Called Fx2()
  Called Fy1()
  Called Fy2() 


continued...
Last edited on
...continued from above...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  But what we want to do is a lot cuter than that!  We want to see the actual memory addresses and blocks of memory where
  these arcane creations really live, and for that we are going to have to get pretty cute ourselves!  And with pointers
  and function pointers involving several levels of indirection to boot!  Are we having fun yet?  If so, lets continue by
  creating our CA object and seeing how big it is.  The second statement in main outputs the sizeof(CA) and we see its 8
  bytes (16 for x64)....

  sizeof(CA)              = 8       : An IX VTBL Ptr And A IY VTBL Ptr

  The above line is the first line of output from the program below.  Please examine that output, for it is that we are
  going to explain.  The reason the sizeof CA is eight bytes is that the C++ compiler, when creating the object, saw that
  it contained no instance data, but that the class multiply inherited from two abstract base classes, i.e., the interface
  classes, and therefore it created two memory slots of four bytes each for virtual function table pointers to the instan-
  tiations of the respective interfaces/vtables.  So that I could iterate through these various structures/classes/vtables
  whatever and output addresses I declared two variables as size_t pointers, i.e.,

  size_t* pVTbl=0;  // pointer to VTable
  size_t* VTbl=0;   // VTable

  which would allow me to treat everything more or less as an integral pointer of some flavor or other, which is useful in 
  for loop iterations or address output statements.  So first off we set our pVtbl pointer equal to the base address of our 
  8/16 byte CA instantiation and output that address...

 pVTbl=(size_t*)pCA;
 printf("pVTbl\t\t\t= %u : Ptr To IX VTBL\n",(unsigned int)(size_t)pVTbl);

 And the output...

 pVTbl = 7744160 : Ptr To IX VTBL

 So in x86 mode our object CA starts at 7744160 and ends at 7744167, i.e., 8 bytes later.  To repeat, at the first four bytes 
 of that 8 byte memory block is a pointer to the 20 byte allocation for the IX VTable, and at the last four bytes of that 
 allocation is a pointer to the 20 byte allocation for the IY VTable.  Using array subscript notation we have then this for 
 the memory run below....

 &pVTbl[0] = 7744160
 &pVtbl[1] = 7744164

 If you look at our tabular output below you'll see those numbers listed in the first column of output.  These numbers are
 referred to as VTable or interface pointers.  They are not VTables or interfaces but rather point to VTable\interfaces.
 In the code in main() you'll see two for loops; the j loop nested within the outer i loop.  The outer i loop simply
 iterates between 0 and 1 to catch the two VTable pointers listed above, i.e., our IX VTable pointer at 7774160 (&pVtbl[0])
 and our IY VTable pointer at 7774164 (&pVtbl[1]).  Within the body of the outer i for loop and just above the j loop you'll
 see this statement...

 VTbl=(size_t*)pVTbl[i];

 This VTbl number will actually be the starting address or base allocation for each of the two VTables/interfaces.  When i
 equals zero it will be the base allocation for the IX VTable, and when i is one it will be the base allocation for the IY
 VTable.  What the j for loop does then is treat this address as an int pointer and uses array subscript notation to iterate
 through the five function pointer slots in each VTable, and output 1) the address within the VTable ( column 2 ); the
 function pointer address held at that aforementioned location ( column 3 ); and 3) the cstdio output statement generated by
 a function pointer call through pFn at the addresses in column 3.  These later function pointer calls are seen in column 4.
 A function pointer is declared in main like so...

 void (__stdcall* pFn)(size_t*);

 What that means is that pFn is a symbol standing for a function pointer which can be used to call a function through its
 address rather than through its name, and the function that it can call must use __stdcall stack frame setup, must return
 nothing, and must take one parameter of type size_t pointer.  And the reason for the one integral parameter when, if you've 
 noticed, my IUnknown, IX, and IY interface member functions do not appear to have any parameters, is an extra credit question!

 I'll keep you in suspence no longer.  It is the 'hidden' this pointer that is an artifact of the mysterious alchemy that 
 must take place when the C++ compiler builds the VTables and sets the addresses within them of the implementations of the
 actual interface member functions.  Remember, interfaces are stateless.  However, they must have the address of a memory
 allocation where the object's instance data is located.  The hidden this pointer is how they get it.  


and now the program ...

Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//cl main.cpp /O1 /Os /MT /FeLearnCom.exe
#include <cstdio>
#define interface struct

interface IUnknown
{
 virtual void __stdcall QueryInterface() = 0;
 virtual void __stdcall AddRef()         = 0;
 virtual void __stdcall Release()        = 0;
};

interface IX : IUnknown
{
 virtual void __stdcall Fx1()            = 0;
 virtual void __stdcall Fx2()            = 0;
};

interface IY : IUnknown
{
 virtual void __stdcall Fy1()            = 0;
 virtual void __stdcall Fy2()            = 0;
};

class CA : public IX, public IY
{
 public:
 virtual ~CA(){}
 virtual void __stdcall QueryInterface(){puts("Called QueryInterface()");}
 virtual void __stdcall AddRef(){puts("Called AddRef()");}
 virtual void __stdcall Release(){puts("Called Release()");}
 virtual void __stdcall Fx1(){printf("Called Fx1()\n");}
 virtual void __stdcall Fx2(){printf("Called Fx2()\n");}
 virtual void __stdcall Fy1(){printf("Called Fy1()\n");}
 virtual void __stdcall Fy2(){printf("Called Fy2()\n");}
};

int main(void)
{
 void (__stdcall* pFn)(size_t*);
 size_t*       pVTbl = 0;
 size_t*       VTbl  = 0;
 unsigned int  i     = 0;
 unsigned int  j     = 0;
 CA*           pCA   = 0;

 pCA=new CA;
 printf("sizeof(CA)\t\t= %u\t  : An IX VTBL Ptr And A IY VTBL Ptr\n",(unsigned int)sizeof(CA));
 pVTbl=(size_t*)pCA;
 printf("pVTbl\t\t\t= %u : Ptr To IX VTBL\n",(unsigned int)(size_t)pVTbl);
 printf("&pVTbl[0]=%u\t< at this address is ptr to IX VTable\n",(unsigned int)(size_t)&pVTbl[0]);
 printf("&pVTbl[1]=%u\t< at this address is ptr to IY VTable\n",(unsigned int)(size_t)&pVTbl[1]);
 printf("\n");
 printf("&pVTbl[i]\t&VTbl[j]\tpFn=VTbl[j]\tpFn() Function Pointer Call\n");
 printf("===========================================================================\n");
 for(i=0;i<2;i++)
 {
     VTbl=(size_t*)pVTbl[i];
     for(j=0;j<5;j++)
     {
         printf
         (
          "%u\t\t%u\t\t%u\t\t",
          &pVTbl[i],
          &VTbl[j],
          VTbl[j]
         );
         pFn=( void (__stdcall*)(size_t*))VTbl[j];
         pFn(&pVTbl[i]);
     }
     printf("\n");
 }
 delete pCA;
 getchar();

 return 0;
}


The above program has close parallels to Dale Rogerson's book "Inside COM". In that book, as in other books of the type, they draw diagrams and pictures and such of virtual function tables and classes and such, but for me there is nothing like getting one's hands dirty with a little code. The above program actually shows you numbers of where everything's at. Here's what a typical run looks like on an x86 build on my Win7 x64 box...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
Output From Above Program From Win 7 x64 Compiled as 32 bit exe...

sizeof(CA)              = 8       : An IX VTBL Ptr And A IY VTBL Ptr
pVTbl                   = 7744160 : Ptr To IX VTBL
&pVTbl[0]=7744160       < at this address is ptr to IX VTable
&pVTbl[1]=7744164       < at this address is ptr to IY VTable

&pVTbl[i]       &VTbl[j]        pFn=VTbl[j]     pFn() Function Pointer Call
===========================================================================
7744160         4224808         4214012         Called QueryInterface()
7744160         4224812         4214132         Called AddRef()
7744160         4224816         4214156         Called Release()
7744160         4224820         4214036         Called Fx1()
7744160         4224824         4214060         Called Fx2()

7744164         4224844         4214512         Called QueryInterface()
7744164         4224848         4214548         Called AddRef()
7744164         4224852         4214560         Called Release()
7744164         4224856         4214524         Called Fy1()
7744164         4224860         4214536         Called Fy2()
*/

Last edited on
This works for x64 (the ones above do too)...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//cl main.cpp /O1 /Os /MT /FeLearnCom.exe
#include <cstdio>
#define interface struct

interface IUnknown
{
 virtual void __stdcall QueryInterface() = 0;
 virtual void __stdcall AddRef()         = 0;
 virtual void __stdcall Release()        = 0;
};

interface IX : IUnknown
{
 virtual void __stdcall Fx1()            = 0;
 virtual void __stdcall Fx2()            = 0;
};

interface IY : IUnknown
{
 virtual void __stdcall Fy1()            = 0;
 virtual void __stdcall Fy2()            = 0;
};

class CA : public IX, public IY
{
 public:
 virtual ~CA(){}
 virtual void __stdcall QueryInterface(){puts("Called QueryInterface()");}
 virtual void __stdcall AddRef(){puts("Called AddRef()");}
 virtual void __stdcall Release(){puts("Called Release()");}
 virtual void __stdcall Fx1(){printf("Called Fx1()\n");}
 virtual void __stdcall Fx2(){printf("Called Fx2()\n");}
 virtual void __stdcall Fy1(){printf("Called Fy1()\n");}
 virtual void __stdcall Fy2(){printf("Called Fy2()\n");}
};

int main(void)
{
 void (__stdcall* pFn)(size_t*);
 size_t*       pVTbl = 0;
 size_t*       VTbl  = 0;
 unsigned int  i     = 0;
 unsigned int  j     = 0;
 CA*           pCA   = 0;

 pCA=new CA;
 printf("sizeof(CA)\t\t= %u\t  : An IX VTBL Ptr And A IY VTBL Ptr\n",(unsigned int)sizeof(CA));
 pVTbl=(size_t*)pCA;
 printf("pVTbl\t\t\t= %u : Ptr To IX VTBL\n",(unsigned int)(size_t)pVTbl);
 printf("&pVTbl[0]=%u\t< at this address is ptr to IX VTable\n",(unsigned int)(size_t)&pVTbl[0]);
 printf("&pVTbl[1]=%u\t< at this address is ptr to IY VTable\n",(unsigned int)(size_t)&pVTbl[1]);
 printf("\n");
 printf("&pVTbl[i]\t&VTbl[j]\t\tpFn=VTbl[j]\t\tpFn() Function Pointer Call\n");
 printf("===========================================================================================\n");
 for(i=0;i<2;i++)
 {
     VTbl=(size_t*)pVTbl[i];
     for(j=0;j<5;j++)
     {
         printf
         (
          "%u\t\t%u\t\t%u\t\t",
          &pVTbl[i],
          &VTbl[j],
          VTbl[j]
         );
         pFn=( void (__stdcall*)(size_t*))VTbl[j];
         pFn(&pVTbl[i]);
     }
     printf("\n");
 }
 delete pCA;
 getchar();

 return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
  From VC9 Command Line Compile...


C:\Code\VStudio\VC++9\64_Bit\COM>cl main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
*/

sizeof(CA)              = 16      : An IX VTBL Ptr And A IY VTBL Ptr
pVTbl                   = 3369200 : Ptr To IX VTBL
&pVTbl[0]=3369200       < at this address is ptr to IX VTable
&pVTbl[1]=3369208       < at this address is ptr to IY VTable

&pVTbl[i]       &VTbl[j]                pFn=VTbl[j]             pFn() Function Pointer Call
===========================================================================================
3369200         1073787640              1073745920              Called QueryInterface()
3369200         1073787648              1073745932              Called AddRef()
3369200         1073787656              1073745944              Called Release()
3369200         1073787664              1073745956              Called Fx1()
3369200         1073787672              1073745968              Called Fx2()

3369208         1073787592              1073746068              Called QueryInterface()
3369208         1073787600              1073746056              Called AddRef()
3369208         1073787608              1073746080              Called Release()
3369208         1073787616              1073745980              Called Fy1()
3369208         1073787624              1073745992              Called Fy2()


And run from Mingw x64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
sizeof(CA)              = 16      : An IX VTBL Ptr And A IY VTBL Ptr
pVTbl                   = 3306880 : Ptr To IX VTBL
&pVTbl[0]=3306880       < at this address is ptr to IX VTable
&pVTbl[1]=3306888       < at this address is ptr to IY VTable

&pVTbl[i]       &VTbl[j]        pFn=VTbl[j]     pFn() Function Pointer Call
===========================================================================
3306880         4373024         4275968         Called QueryInterface()
3306880         4373032         4276048         Called AddRef()
3306880         4373040         4276064         Called Release()
3306880         4373048         4275984         Called Fx1()
3306880         4373056         4276000         Called Fx2()

3306888         4373112         4280576         Called QueryInterface()
3306888         4373120         4280624         Called AddRef()
3306888         4373128         4280640         Called Release()
3306888         4373136         4280592         Called Fy1()
3306888         4373144         4280608         Called Fy2() */


Last edited on
Topic archived. No new replies allowed.