Small is beautiful
At the very beginning of this news-letter, we had denounced the way the samples were written. Moreover, throughout this issue we have time and again expounded our faith in short programs as a teaching aid. (You must be tired of this rhetoric by now). We believe that when we criticize somebody we must have something better. This article is a representative of the code we use to learn. Bill Gates wouldn't own up to such code. Just imagine no error checks, no Hungarian, no UI....
We came to these "to the point" programs by the method of systematic elimination. We had two Visual C++ samples that we could start with: SIMPCNTR, the simplest container or SIMPSVR, the simplest server. We decided to begin with the container. Why? Don't ask us. It wasn't meant to be a choice based on logic.
We kept SIMPSVR as is. In every function of SIMPCNTR, we used file(), our by now famous ide-de-camp. file() would merely write the name of the function onto a file on disk. We ran SIMPCNTR and SIMPSVR together testing no more than one feature at a time. The first time all we did was to get in SIMPSVR into our container.
If file() was our beacon, then this file on disk with the listing of all functions that are executed in the process was our blue-print. We used it to trace the path of program execution. Having determined the necessary functions, we create a new file which contains only these functions. This file now acts as our container.
Just because a function is called does not mean that all code in it gets executed. So we go through the code with a fine tooth comb, eliminating step by painful step every bit of superfluous code cutting the code to a fraction of it's original size. It merely brought in SIMPSVR as an embedded object. Even this shortened version of the program wasn't the easiest to understand. It was full of helper functions. The problem with helper functions is that they in turn call several functions.
For example, consider OleUIInsertObject(). This is a UI helper function written to ease programming and not an OLE API. It does a whole lot of things, all of which will remain transparent to the user. This function is not even documented in the help. We went through it's source code and realised that the function of interest to us is an OLE API OleCreate().
Alas! OleCreate() calls as many as five other functions. Of these only the use of the OLE API function CoCreateInstance() is critical. On reading the help, much to our dismay, we realised that this too was a helper function. The OLE 2.0 documentation says that CoCreateInstance() can be split into CoGetClassObject() + CreateInstance() combination.
So there we went, back to the drawing board, to replace CoCreateInstance() with these two. At long last, we had our shortest container devoid of all UI and helper functions that would bring in SIMPSVR as an embedded object.
Next, we turned our attention to SIMPSVR. We repeated the entire process for the server. This time the full-featured container SIMPCNTR is retained as is and SIMPSVR is cut down progressively to get to the heart of the matter. Eventually, we came to the shortest server that will work with SIMPCNTR.
At this point, we realised that more code could be purged on either side if we used our container with our server. So back to the job it was. By now we were past masters at cutting down code. With constant trimming, we came to the shortest possible programs. Horror of horrors, the whittled down container does not have a single interface or class implementation.
The Container
#include < windows.h>
#include < ole2.h>
WNDCLASS a;
UINT b;
MSG c;
IOleObject* j;
CLSID g;
IClassFactory *f;
long _pascal _export zzz(UINT w,UINT x,UINT y,long z)
{
if(x == WM_COMMAND && y == 100)
{
CLSIDFromString("SIMPSVR",&g);
CoGetClassObject(g,4,0,IID_IClassFactory,(void**)&f);
f->>CreateInstance(0,IID_IOleObject,(void**)&j);
j->>DoVerb(0,0,0,0,0,0);
}
if(x == WM_DESTROY)
PostQuitMessage(0);
return DefWindowProc(w,x,y,z);
}
int _pascal WinMain(UINT i,UINT j,char* k,int l)
{
OleInitialize(0);
a.lpfnWndProc = zzz;
a.hInstance = i;
a.hbrBackground = GetStockObject(WHITE_BRUSH);
a.lpszMenuName = "mmm";
a.lpszClassName = "abc";
RegisterClass(&a);
b=CreateWindow("abc","Hello",WS_OVERLAPPEDWINDOW,5,5,200,300,0,0,i,0);
ShowWindow(b,3);
while(GetMessage(&c,0,0,0))
DispatchMessage(&c);
OleUninitialize();
return (c.wParam);
}
The container will need absolutely no explanation to those who have been through the first OCX container program. What's not to understand in this program.
The Server
#include < windows.h>
#include < ole2.h>
class iii
{
public:
virtual void* _export _cdecl QueryInterface(REFIID,void**);
virtual DWORD _export _cdecl i2(){return 0;}
virtual DWORD _export _cdecl i3(){return 0;}
virtual void*_export _cdecl i4 (){return 0;}
virtual void i5(){}
virtual void i6(){}
virtual void i7(){}
virtual void i8(){}
virtual void i9(){}
virtual void i10(){}
virtual void i11(){}
virtual void* _export _cdecl DoVerb(long,MSG*, IOleClientSite*,long,UINT,RECT*);
virtual void i13(){}
virtual void* _export _cdecl i14(){return 0;};
virtual void i15(){}
virtual void i16(){}
virtual void i17(){}
virtual void i18(){}
virtual void i19(){}
virtual void * _export _cdecl i20(){return 0;}
};
class ccc
{
public:
virtual void* _export _cdecl QueryInterface(REFIID,void**);
virtual DWORD _export _cdecl c2(){ return 0;}
virtual DWORD _export _cdecl c3(){return 0;}
virtual void* _export _cdecl CreateInstance(IUnknown*,REFIID, void**);
};
WNDCLASS a;
UINT b;
MSG c;
DWORD e;
int x1,y1;
iii *i;
ccc *d;
CLSID g;
void* _export _cdecl iii::QueryInterface(REFIID r,void**p)
{
if(r==IID_IOleObject)
{
*p=this;
return 0;
}
return ResultFromScode(E_NOINTERFACE);
}
void* _export _cdecl iii::DoVerb(long,MSG*,IOleClientSite*,long,UINT,RECT*)
{
ShowWindow (b,1);
return 0;
}
void* _export _cdecl ccc::QueryInterface(REFIID r,void** p)
{
if(r == IID_IClassFactory)
{
*p=this;
return 0;
}
return ResultFromScode(E_NOINTERFACE);
}
void* _export _cdecl ccc::CreateInstance(IUnknown*,REFIID,void **p)
{
i=new iii;
*p=i;
return 0;
}
long _export _pascal zzz(UINT w,UINT x,UINT y,long z)
{
if(x==WM_LBUTTONDOWN)
{
x1=LOWORD(z);
y1=HIWORD(z);
InvalidateRect(w,0,1);
}
if(x==WM_PAINT)
{
PAINTSTRUCT p;
HDC h = BeginPaint(w,&p);
TextOut(h,x1,y1,"Hello",5);
EndPaint(h,&p);
}
if(x==WM_DESTROY)
PostQuitMessage(0);
return DefWindowProc(w,x,y,z);
}
int _pascal WinMain(UINT i,UINT j,char* k,int l)
{
OleInitialize(0);
a.hInstance=i;
a.lpfnWndProc=zzz;
a.lpszClassName="abc";
a.hbrBackground=GetStockObject(WHITE_BRUSH);
RegisterClass(&a);
d=new ccc;
CLSIDFromString("SIMPSVR",&g);
CoRegisterClassObject(g,(IUnknown*)d,4,0,&e);
b=CreateWindow("abc","Server",WS_OVERLAPPEDWINDOW,0,0,400,400,0,0,i,0);
while(GetMessage(&c,0,0,0))
DispatchMessage(&c);
OleUninitialize();
return(c.wParam);
}
Very little of the server requires some explanation. WinMain() as we know is the start of all Windows EXEs. The only function that requires explanation is the OLE API CoRegisterClassObject(). This function is used by every EXE server to indicate which pointer is to be returned to the container's call of CoGetClassObject(). The first parameter to CoRegisterClassObject() is the CLSID that identifies the server. The second is the pointer to be returned to the container. We pass the pointer to the class ccc; our IClassFactory stand-in as this parameter. The third parameter indicates that this is an EXE server while the fourth stipulates that only one user can access the server at a time. The fifth and final parameter is of no importance to us at this point in time.
CoGetClassObject() of the container will load and run the server. Thus, WinMain() of the server will get executed. Here we create a window in memory, but no ShowWindow() is used. The container uses the pointer returned to it by CoGetClassObject() to call the method CreateInstance() in the server. In this function, we create an instance of the class iii, our IOleObject stand-in, and return it's pointer to the container.
The container uses this pointer to call DoVerb(). In our implementation of the function DoVerb(), we do not refer to any of the parameters passed. Thus, from the container's side we set all parameters to 0. DoVerb() of the server does no more than a ShowWindow() on the window created in WinMain(). Hey Presto! The server appears "embedded" in the container. Simple. But this never comes across in the Microsoft samples.
These are the programs that taught us the fundamentals of OLE. These programs will work only with each other. But aren't they easy on the eye? Anyone who looks at this code will never feel intimidated. Most of it is just basic C code. In arriving at this code, progress at times was slower than snail's pace but finally we had the smallest program. A program infinitely more simpler to understand.
OLE 2.0 is a complex technology. Not surprising, considering all that it is meant to achieve. In learning OLE 2.0, one has to grasp it's essence. Once the concepts are clear, coding to implement a feature is incredibly facile.
No one can learn OLE 2.0 from the Microsoft samples unless they are willing to forgo all else in life for a long time. Like we did. It took us nearly six months to understand OLE 2.0 inside-out. In this time we breathed, ate, drank, slept, dreamt OLE. We just stopped short of using Olé as a greeting or did we?