This page is in Pre Alpha. That means no grammer checks, no editting and speeling mistakes galore! The explanations and the final version will come soon. Check it out at your own risk! OLE Programming-The first lessons (Preview Release-I) Smooth seas do not make skillful sailors -African Proverb In the complex world of computer programming, the quest for a technique which will don the mantle of the most complicated of them all is always on. Microsoft, as a company has always believed in being the best. This time was no exception. Microsoft introduced OLE programming, which strikes fear in every programmers heart. Being told to work with OLE was equated to being handed a 'punishment transfer'. The reasons for these feelings was not difficult to comprehend. OLE is extremely difficult to understand, programming in it is, but a secondary issue. The moral of the story being that, to make skillful sailors in the vast oceans of software try your hand at OLE programming. We decided to make a sincere effort to demystify some of the aspects relating to OLE programming. So lets start with the basics. The first and the foremost trivia that we can provide you with at this juncture is that the next time you walk into a party, please don't tell anyone that you are an O-L-E programmer. Microsoft does not refer to O-L-E as O-L-E but as OLE (pronounced as Olay) We hope this trivia will help you get instant recognition at any party. As the next step to the world OLE let us befriend some of the 'friendly' concepts/jargon used in OLE. The first jargon that we encounter in the OLE jungle is the "OLE server". The "OLE server" is used to denote any object that contains the actual code. The "OLE server" can be in the form of a dll, ocx or an exe file. The "OLE server" can be manipulated by using an "OLE container". The "OLE container" can be visualized as a simple container that is used to hold and manipulate the "OLE server". The "OLE container" and the "OLE server" follow the Component Object Model or COM. This model allows the effective communicate between the container and the server. In due course of time we will be providing a short write-up on the differences between OLE,OCX, ActiveX, COM and the likes. However till that time, we will be using these terms interchangeably. The best way to learn OLE programming is to follow what we will call the 'Vijay Mukhi Doctrine' i.e. we will build the client and the server 'one-line-at-a-time'. This is the general approach that is followed in all the tutorials on this site. This also enables one to gain a clear understanding of the underlying concepts. Coming back to the world of OLE programming, we will built the server and the container (henceforth called as the client) from scratch. In the absence of such an approach you would have spent the rest of the day, scratching your head. So here are the first programs. These programs can be constructed as simple applications using MS-DEV. CLIENT.CPP #include #include long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) MessageBox(0,"hi","hi",0); if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The program is used to display a window on the screen. In case you have difficulty in comprehending the program then we recommend that you check our tutorial on coding in C under Windows SERVER.CPP #include #include void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if(x == WM_LBUTTONDOWN) MessageBox(0,"hi","hi",0); if (x == WM_CLOSE) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { abc("Start"); a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Title",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The function abc(char *s) is our in-house helper function and will be used to record the cross-fires taking place during the session. As far as the rest of the program is concerned, you don't have to be an ornithologist to observe that both the programs given above are the same. We let you into our secret, we copied the first program and pasted it into the space provided for the server program. We are about to step into the world of OLE programming, so please tighten your seat-belts and check your supply of coffee. .....It's going to be a long day... The server program should only be built, please Do Not execute the server program. When we built the above mentioned programs, we find that we are sitting on two sub-directories viz. client and a server. It will be our attempt to call code in the server program by using the client program. It's time that we got involved with the coding. We will first target the server program. Unlike most software that is written on this planet OLE stands out on one count, it has no version number. This may sound outlandish but one cannot take Microsoft to court, disputing their logic behind such a move. However Microsoft, with their ingenious style of working has found a way of circumventing this problem. They recommend (read force) that we use a keyword 'OleInitialize'. This according to the cryptic documentation provided by Microsoft is used for initializing OLE. The reason behind initializing OLE, or initializing the internal data structures (Microsoft's words) is easy to comprehend. OLE is available under Windows 3.1, Windows 95, WindowsNT and unbelievable but true, OLE is available under UNIX too. A method, to allow OLE to distinguish between the installed operating system has to be devised. This is the rationale behind using the keyword 'OleInitialize' The server program has been modified to contain the word 'OleInitialize' and is shown below. Server.cpp #include #include void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { OleInitialize(0); a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Title",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } After the server program, it is the client program that has to undergo some modifications. This is done by inserting a function called the 'CLSIDFromString'. As it's name suggest this function is used to convert a string to a CLSID. Most of us have reconciled ourselves to the fact that a string is a 'char *', however the term CLSID seems to be some esoteric concept from Mars. We will get into the mysteries of the CLSID after we have seen the modified client program Client.cpp #include #include CLSID cc; char * m = "ABC"; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) { CLSIDFromString(m,&cc); MessageBox(0,"hi","hi",0); } if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The ideology behind OLE is that the client should be able to call code in another program called the server. There has to be a method by which the client should be able to call the server. Using the name of the server program is, but an amateurish idea. The reasons are aplenty, for example what would happen we decide to call our server as 'India.exe' and someone on this planet decides to call his server by the same name and you want to install that program on your system. This would mean overwriting the installed file, even otherwise if both the programs are installed on different sub-directories, there would be a resultant name clashing. To alleviate such a situation a simple mechanism was devised. According to this the programs were to be called by numbers. Since new solutions give rise to new problems, the size of these numbers became a matter of debate. If the programs were assigned a long number each then the world would have been a better place to live in because we could have to deal with only four billion OLE servers. Though this seems to be a utopian idea, it is not a practical one as four billion is too small a number. This is a classical problem, similar to the one that we are facing in the case of IP address. We simply need a large enough number to take care of the needs of the world and for the worlds beyond, surely Microsoft would like to colonize desktops beyond the solar system too. DCE or the Distributed Computing Environment was a concept pioneered by Digital Corporation. The think-tanks at Digital decided to develop a new concept wherein a number would be used to represent a program (same as in OLE) According to them, such a number had to be unique over time and space. When we say that a number should be unique over time, we means that each time the number is generated, it should be different from the previous one. So these numbers are calculated depending upon the time of the day, plus something unique about my computer, for example if you posses an Ethernet Card, then Ethernet address, or IP address or maybe the amount of disk space which is free at that time could be used for calculating the number. This could then be multiplied by the amount of memory that you have got. Though this is not a tutorial on how the unique number is generated, we have provided the above statements to help you visualize the dynamics behind trying to generate the unique number. The nerds at Digital, who were good at mathematics had by then reached a conclusion that a number which is 128 bits wide, would be guaranteed to be unique over time and space. As in the previous case, this solution gave rise to newer problems. It is plain knowledge that the 'C' language- the lingua franca of programmers, can support a number which is as large as 2 raised to 32 but it cannot support which is as huge as 2 raised to 16 i.e. a 128 bits wide number. Hence our nerds teamed up with some more nerds and decided to store 128 bits in a structure, the size of which would be 16 bytes. This structure could also have an array, that is the way they said that they would divide this number. The storing convention being decided, it was time to name this baby. The baby being a Unique ID, they decided to call it a UUID - Universally Unique IDentifier. Cooking up esoteric names to represent simple terms is not the preoccupation of Microsoft alone, the whole software industry specializes in it. Hence names like GUID - Globally Unique IDentifier, and CLSID - CLaSs IDentifiers are used to mean a UUID. We have declared globally that cc looks like CLSID, but what is CLSID, it's a structure tag in 'Ole2.h' which is invoked by 'Windows.h'. We want to store a 128 bit in the structure cc. The other variable that we are declaring is 'm' which is a 'char *' and currently assigned the value 'ABC'. When we attempt to built the program we get an error message. The error message has been reproduced below for your convenience. Error Statement : C:\client\client.cpp(19) : error C2664: 'CLSIDFromString' : cannot convert parameter 1 from 'char *' to 'unsigned short *' (new behavior; please see help) Suddenly the whole thing strikes us like a lightening bolt. Even though we said 'char *' as the first parameter VC++ tells us that it does not want a 'char *' but it is more interested in an 'unsigned short' which implies that it wants a datatype which is two memory locations long. Usage of 'char *' is fraught with a simple yet basic problem. A 'char *' can be used to represent only 255 characters. English fortunately, has a very small character set and can be easily represented using a 'char *'. However OLE was designed not only for the Englishman but also for the alien who would like to get into the software business. Traditional languages like Chinese, Japanese and Hindi were blessed with a larger character set, there are too many individual characters. Hence there was an urgent need for a standard which would form the greatest common factor among these languages. This lead to the introduction of the Unicode. Unicode is a 16 bit character set that is capable of encoding of all known character sets and used the as a worldwide character encoding standard. OLE standards specify that a user cannot use a 'char *' to represent a string, he has to use a unicode string. Now Microsoft, is not as lead-headed as we think, it is indeed a caring company and hence it provides us with a method to deal with our requirements. Our current requirement is to convert a given string into the unicode format. This is possible by using the function 'MultiByteToWideChar'. The client program possessing this function is shown below. Client.cpp : #include #include CLSID cc; unsigned short m[128]; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) { MultiByteToWideChar(0,1,"ABC",-1,m,sizeof(m)/sizeof(WCHAR)); CLSIDFromString(m,&cc); MessageBox(0,"hi","hi",0); } if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } 'MultiByteToWideChar' is used to convert the ASCII string 'ABC' into the unicode format. This is stored in 'm' which is an 128 bit large array. Since we have to specify the length of the array, we do it as the last parameter of the function 'MultiByteToWideChar'. Every character is stored as two bytes. An interesting experiment would involve attempting to find the storage pattern of the unicode string. A program that attempts that is given below. Client.cpp : #include #include char aa[100]; unsigned short m[128]; CLSID cc; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) { MultiByteToWideChar(0,1,"ABC",-1,m,sizeof(m)/sizeof(WCHAR)); char *p; p=(char *)m; int s=0; for (s = 0; s < 6; s++) { sprintf (aa, "%d..%x",*p,*p); MessageBox(0,aa,aa,0); p++; } CLSIDFromString(m,&cc); MessageBox(0,"hi","hi",0); } if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The series of message boxes that are displayed vindicate our theories. We hope that by now you will be well-versed with the idea of the Unicode. So we decided that time was ripe for the introduction of another concept, the Windows Registry. The Windows Registry : Every organization adopts a method of documenting it's assets. The computer being an advanced form of an organization, is no exception. The computer has a central repository where details regarding various programs are stored. This 'cellar' of the computer is called as the Windows Registry. It is used to store details like the CLSID, InProc Server etc. As a specific example if SIMPSVR is a string and we desire to know its CLSID i.e. we want to know the number that it is registered with. So we can have a simple text file on disk that says SIMPSVR is known by this 128 bit number. But if we have it as a single file on disk, then there are two problems. It could be accidentally deleted and the second issue involved is that if we have 500 combinations, of names and CLSID then accessing them becomes a problem. During the pre-historic days of Windows 3.1 , all configuration, and details about the computer was stored in the form of ASCII files. Now Microsoft realized it's folly and decided that henceforth all system data will be stored in what is called the Registry. The Registry is nothing but a file on disk but the data is stored in such a way that it can be quickly accessed. Internally the Registry may take the form of a linklist. Linklist are simple but they can assume more complicated form, so that retrieval becomes faster. If a program desires that it should be accessed by other programs then it must be registered with the Windows Registry. The Registry can be accessed by typing the words 'regedit' at the 'RUN' prompt in the 'START' menu. A word of warning, do not fiddle around with the entries in the Registry. The Registry is not a place where you can take your date, it is off-limits to most of us. But this should not stop us from learning some rudimentary concepts regarding the Registry. Well if you can't take your date there, you can atleast impress her with your knowledge about the same. Computers have always for long been visualized as number crunching, human displacing creatures. This may not be without reason. Though we do not subscribe to the human displacing aspect of the story, it is true that numbers act as ambrosia to the computer. A computer cannot understand names, they have been fed on a diet of numbers and hence refer to each program in it's stable with a unique number. This number as was preciously noted, is called as the CLSID of the program. A CLISD being a 128-bit number, is unique over time and space. A program that is willing to allow others to access it, may be in the form of an executable file (as in our case) or may be an OCX (we will deal with such cases in our future tutorials). Hence there must be a way that the computer can differentiate between the two. The term, 'InProc Server' is used to denote the presence of a OCX/DLL as a sharable file. This is against the usage of the term 'Local Exe' which is used to represent an executable file on a local machine. The name of the server is associated with another jargon, the ProgID. Our server program falls into the category of programs that would like to allow other programs to access them. Hence we have to 'register' our server. There are two ways to do it, one is do it from the code which we don't want to do, the second way is to do it manually. It is preferred programming practice to register the server from the code because tomorrow when we install PC Paintbrush, during the installation process PC Paintbrush tells the Windows Registry that I am an OLE server. So that server.exe that we wrote is going to be the OLE server, so that client can call code in it. This drives us to designing a file called 'aaa.reg'. Most of the surfers reading this tutorial fall into the category of people who are a rebel at heart and do not like to conform to rules. This is because we consider rules as stupid. Writing a reg file imposes a lot of stupid rules on us. The first stupid rule that we encounter is that the first line in the reg file has to begin with REGEDIT. REGEDIT is a reserved word. The rationale behind using the REGEDIT is unknown, it is considered as part of some (stupid) rule. Then we write HKEY_CLASSES_ROOT, and put a backslash. REGEDIT HKEY_CLASSES_ROOT\ The Registry contains information that has been classified under various headings. One of the sub-headings reads 'HKEY_CLASSES_ROOT'. When we click on it, it opens up to reveal a lot of cryptic data. The 'HKEY_CLASSES_ROOT' in my registry file is used to indicate to the Registry where it has to put the data provided by me. The first line of the 'aaa.reg' reads as follows REGEDIT HKEY_CLASSES_ROOT\SIMPSVR\CLSID = {BCF6D4A0-BE8C-1068-B6D4-00DD010C0509} The word 'SIMPSVR' used after the backslash is used to indicate the sub-heading under which our server has to be registered. The final keyword 'CLSID' is used to denote the 'CLSID' of the server. In this particular instance we are saying that the CLSID of the server program is BCF6D4A0-BE8C-1068-B6D4-00DD010C0509. We did not conjure up this CLSID from mid-air. Even though magicians never tell the tricks of their trade, we will let you into our secret. Microsoft provides us with a utility called GUIDGEN which can be obtained from the msdev\bin directory. It acts as the magic wand which can be used to generate a CLSID. It's time that we built the complete reg file. The contents of the reg file are given as under aaa.reg REGEDIT HKEY_CLASSES_ROOT\SIMPSVR\CLSID = {BCF6D4A0-BE8C-1068-B6D4-00DD010C0509} HKEY_CLASSES_ROOT\CLSID\{BCF6D4A0-BE8C-1068-B6D4-00DD010C0509}\LocalServer = c:\server\debug\server.exe HKEY_CLASSES_ROOT\CLSID\{BCF6D4A0-BE8C-1068-B6D4-00DD010C0509}\ProgID = SIMPSVR The LocalServer is another keyword which is used to provide the full path of the exe file that the CLSID is pointing to. In case of the client programs presented above the CLSID is represented by a structure named 'cc'. This structure is filled up by the function 'CLSIDFromString'. Now the time is ripe to add the details about our server into the registry. To do this, start off the registry and click on the menu option called 'Import Registry File'. Provide the details of the file aaa.reg. Windows is smart enough to incorporate the details within the Registry automatically. Returning to the herd : After you have added these four lines to the Registry, you can execute the client program given below. Client.cpp : #include #include unsigned short m[128]; CLSID cc; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) { MultiByteToWideChar(0,1,"SIMPSVR",-1,m,128); CLSIDFromString(m,&cc); MessageBox(0,"hi","hi",0); } if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The function 'CLSIDFromString' will now be able to obtain the CLSID of our server. If you have any doubts regarding our claims, we encourage you to insert a series of MessageBox's and view the output. Time to move on and add another line to the client program. The latest version of the program is given below. Client.cpp : #include #include CLSID cc; unsigned short m[128]; IClassFactory *f; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) { MultiByteToWideChar(0,1,"SIMPSVR",-1,M,sizeof(m)/sizeof(WCHAR)); CLSIDFromString(m,&cc); CoGetClassObject(cc,4,0,IID_IClassFactory,(void **)&f); MessageBox(0,"hi","hi",0); } if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } We encounter a new function called the 'CoGetClassObject'. We will dispel some of the enigma surrounding this function. 'CoGetClassObject' is a part of the OLE API. An API is an acronym for the Application Programmers Interface. API means functions, for example printf, is called as a 'C' - DOS API because printf is a C-DOS function. Similarly 'CoGetClassObject', it is an OLE function. If everything has worked as planned then we will be sitting on a pile of files in the server directory. There exists a sub-directory called debug, under which can be found a file called 'server.exe'. The first line of code after WinMain in this file is abc("Start"). When you execute the client program given above, you will see a window. On clicking in this window the 'CoGetClassObject' gets called. End of program, or so you might say. Wrong!!. Check for the existence of a file called 'Z.TXT' in the client sub-directory. You will be surprised by it's contents. have been reproduced as under. Z.txt : Start The existence of these words hold manifold significance to us. This file was not brought in by a stroke. It was generated by the server. Or in more layman terms the client was able to invoke the code in the server. But the mystery deepens as to how the client could do it without knowing the name of the server (there is no mention of the server's name in the client program) So before you call the FBI we will tell you how the client could invoke code in the server. The first parameter of 'CoGetClassObject' is 'cc' which is used to store the CLSID of the server. Because of it's awareness of the CLSID, the 'CoGetClassObject' can access the Registry to obtain the full path of the associated server. Since the second parameter is '4', the 'CoGetClassObject' function knows that the associated server is an exe file. In the next parameter, we will have to say something about the machine. for e.g. it's IP address, name of machine 'www.abc.com'. The whole idea being that when that server starts, we don't want the server to start, we want a pointer from that server. This will enable us to execute code in that server. One of the basic question that all first timers to the world of programming ask is why do you have structures? Life is so easy without structures, so why complicate life. The answer to such a question is easy. It so happens that it is easier to deal with a group of variables than a single variable. Life becomes easier, when you have structures. So the whole idea is that we want to execute code from a server, So what we will do is, instead of having a structure which is a collection of variables, we will adopt a different approach.. We will use a class. But the what is a class? Though answer to this question can be obtained from our tutorials on C++, we will brush up on the fundamentals once again. An example of a class can be stated as under class zzz { void abc(); void pqr(); void xyz(); }; A class can be defined as a collection of variables as well as functions. But a collection of variables, is called a structure. When a structure is spiced by adding functions to it, we call it a class. So now don't we want a pointer to something from a server. Because if we don't have a pointer, then we can't execute any code in the server. Now we have faced with a new dilemma, we want a pointer but what do we want a pointer to? Is it an int, a char, a long or the class 'zzz'. Unless we specify what type of pointer we want, we cannot proceed in our quest to learn OLE. The most obvious choice of the pointer type would be a pointer of type 'zzz'. But we are aware that the computer is too sophisticated, to understand alphabets. The only option left with us is to use numbers.So now instead of saying 'zzz', we should use a number. By now most of us are aware of the limitations imposed by long number and the usefulness of using a128-bit number. This number cannot be called as the CLSID, because CLSID is the number which stands for the server number. Since this number is used to indicate the name of the class, it is only logical that this number is called as it's IID or an Interface IDentifier. The CLSID and the IID belong to the same family i.e. they are both 128-bit numbers. From the 'C' point of view both are the same. The 'CoGetClassObject' uses the CLSID to identify the name of the server which has to be loaded into memory. It then uses the IID to identify the class which has to be accessed. In the specific instance of the above program the client program tries to invoke a class called as the IClassFactory. So what is this IClassFactory? A factory is a place where goods are produced for mass consumption. Factories are very important for the health of a nation. It is little wonder that the Industrial Revolution has changed the course of world history. Well before we start sounding like economists and historians we will get back to the gentle quick-sands of OLE. The IClassFactory is similar to a normal factory, it is used to produce objects. These objects are instances of the classes. When we used the term IID_IClassFactory, we were providing (or asking) for the IID of the IClassFactory. Assuming the IID is 420, so we are saying that we want a pointer to an Interface whose number is 420. And the last parameter in CoGetClassObject is a pointer to a pointer. (what is 'f', it is a pointer, when you say (& of a pointer), what does it become, a pointer to a pointer.)That means the guy at the other end will give us a pointer in this f. Because we have passed the address. If tomorrow, we declare int *i, and then say abc(&i), we will create int **j. That means now i's value filled up will be pointer. So now the j will now be a pointer to a class, structure, call it whatever you like. So, if I say j->xyz,will I not call the function xyz at the other end. Microsoft demands that if a client wants to call code in a server, he has to follow certain rules. So if everybody follows those rules every client can talk to every server and the world will be a better place to live in (besides Microsoft getting the Nobel peace prize). So Microsoft decided that when you want to call a server, the client will first ask for the address of a class called the IClassFactory. We have two executable files using the computers memory. In the true sense of multitasking environment provided by Windows 95, both the files do not know the existence of the other and both these files are not allowed to write into the memory spaces allocated to the other program. But we have demonstrated that one exe file (client) was able to call code in the other (server). This is the beauty of OLE. If the server creates an object or a structure that looks like 'zzz', and that structure begins at 100, and if the server returns to the value 100 to the client, it makes no sense to the client. An analogy can be drawn from real life. It is like two people talking, one is speaking English, the other speaking French. They are trying to strike a conversation but what one says does not make sense to the other guy. So we need the presence of a translator who is aware of both English as well as French. It is the duty of the translator to convey the words of the Englishman to the French guy. In case of the client and the server we need a 'translator' which will provide the client with the implications of the memory location used by the server. When the client says 100->abc, the 'translator' should convert the 100 to the corresponding '100' in the servers memory space. The process of translating is technically called as 'marshalling'. What does 'marshalling mean? A pointer does not make sense to the client because it a pointer provided by the server, so when the client call code at 100, what finally gets called is the code in the server address space. The person who does marshalling is called COM - the Component Object Model A Quagmire called OLE programming: Now that we have got our feet wet with tons of theory, let's get our hands dirty with some code. As we have attempted to obtain the IID of the IClassFactory, a non-existent commodity in the server, we will add this class to the server program. The modified server program is shown as under. Server.cpp : #include #include void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } class ccc: public IClassFactory {}; ccc *d; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if (x==WM_LBUTTONDOWN) MessageBox(0,"hi","hi",0); if (x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; CLSID cc; unsigned short M[128]; DWORD e; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { abc("Start "); OleInitialize(0); d= new ccc; a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Server",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } Let's rip apart the program given above. The need of the OleInitialise needs no further emphasis. The piece of code d = new ccc will create an object like ccc and store the pointer within d. But what is ccc? When we say class ccc, for the uninitiated, a class is another word for structure tag. So we are saying that ccc is derived from IClassFactory. We mean ccc has everything that IClassFactory has. Henceforth the IClassFActory will be referred to as an interface. An interface is a glorified name for a class and contains only functions and no variables. These functions have a rare parentage due to which they are virtual functions equal to 0. On building the above program, we get more than what we had bargained for. We are inundated by error messages. These messages have been reproduced below for the 'convenience' of the reader. c:\server\server.cpp(40) : error C2259: 'ccc' : cannot instantiate abstract class due to following members: c:\server\server.cpp(40) : warning C4259: 'long __stdcall IUnknown::QueryInterface(const struct _GUID &,void ** )' : pure virtual function was not defined c:\server\server.cpp(40) : warning C4259: 'unsigned long __stdcall IUnknown::AddRef(void)' : pure virtual function was not defined c:\server\server.cpp(40) : warning C4259: 'unsigned long __stdcall IUnknown::Release(void)' : pure virtual function was not defined c:\server\server.cpp(40) : warning C4259: 'long __stdcall IClassFactory::CreateInstance(struct IUnknown *,const struct _GUID &,void ** )' : pure virtual function was not defined c:\server\server.cpp(40) : warning C4259: 'long __stdcall IClassFactory::LockServer(int)' : pure virtual function was not defined The genesis of these error messages are not hidden from anyone. The reason is very simple. You see that IClassFactory has functions which are virtual = 0. So unless we don't have the same functions ourselves, we will get an error. And what will the error say, that these functions are needed in 'ccc'. The error messages will tell you what those functions are. So insert the functions into the class 'ccc'. Here's a 'cheat-code' on how to go about doing this. Since we belong to the class of people who believe in the axiom I'm lazy. But it is the lazy people who invented the wheel and the bicycle because they didn't like walking or carrying things -(To lazy to remember the author) we simply copied the error messages into the code window and stripped the keywords off the messages. The modified server code is given below. Server.cpp : #include #include void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } class ccc: public IClassFactory { long __stdcall QueryInterface(const struct _GUID &,void ** ) {abc("1");return 0;} unsigned long __stdcall AddRef(void) {abc("2");return 0;} unsigned long __stdcall Release(void) {abc("3");return 0;} long __stdcall CreateInstance(struct IUnknown *,const struct _GUID &,void ** ) {abc("4");return 0;} long __stdcall LockServer(int) {abc("5");return 0;} }; ccc *d; CLSID cc; unsigned short m[128]; DWORD e; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if(x == WM_LBUTTONDOWN) MessageBox(0,"hi","hi",0); if (x == WM_CLOSE) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { abc("Start"); OleInitialize(0); a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Title",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); d= new ccc; MultiByteToWideChar(0,1,"SIMPSVR",-1,m,sizeof(m)/sizeof(WCHAR)); CLSIDFromString(m,&cc); CoRegisterClassObject(cc,(IUnknown *)d,4,0,&e); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } The above program builds without a glitch. This is due to the presence of all the required functions in the 'ccc'. These functions are present in IClassFactory and they are virtual functions = 0. That means the same functions which are there in IClassFactory, we have to write with an open { and close }. C++ being very finicky, demands that if a function is defined such that it is slated to return a value and we don't return the value, then C++ will create a lot of problems. We being smarter of the lot, choose to return 0, because 0 fits in for everything be it pointers or numbers. It is as versatile as plasticity, though it can be considered as more eco-friendly than plastic. Every function has a name - 'QueryInterface', then it has a return value. For the once who want to know what is a REFIID, a REFIID is nothing but a structure which is passed by reference. Reference means what , its address goes on the stack , the actual structure doesn't go on the stack, though we assume that the actual structure has gone on the stack. On building the above program we find that we will not get any compiler or linker errors We have added a healthy dose of the helper function. This will help us keep track of the flow of the program. The client will execute the 'CoGetClassObject', whereas the server has to run the 'CoRegisterClassObject'. Microsoft's wisdom for laying down this rule cannot be challenged and hence we have to meekly tow the line and include the 'CoRegisterClassObject' in our server code. The 'CoRegisterClassObject' is used to state who you are, that is you have to state your CLSID, the '4' here means that I am an exe file, the '0' comes into play if we are trying to implement the server across a network, this being a long number is used for providing the IP address - a issue which should not hold any significance for understanding the topic at hand and finally d is a pointer. The reason why we are saying (IUnknown *) should be ignored for the time being we will simply reconcile ourselves to the fact that we are casting it. Casting doesn't change anything. It's time that we compiled the server, and then run the client. The client will call the server. On analyzing the contents of the output file we will realize that some of these functions get called repeatedly. This is the case with functions 1 and 2. The first function happens to be 'QueryInterface' whereas the second function is the 'AddRef' function. We will first learn the rudimentary concepts related to the 'AddRef' function. Microsoft's philosophy is that the big applications have already been written. The spreadsheets, the databases, the word-processors have already been written, what we need are smaller applications which will act as value additions to these individual applications. Now there are one million and one things that Oracle can't do. So if we could write an application that enhances Oracle, both of us benefit. Oracle benefits because now they can say that they also support this feature, we will benefit because we can make our pot of gold. But if we did that only with Oracle, then Sybase or someone else can't use that additional feature because it works only with Oracle, it is too closely tied with Oracle. Microsoft now propounds the philosophy that we should create our application as 'objects'. Let us consider a specific example of a simple SpellChecker. The SpellChecker takes a group of words and tells us whether the spelling is right or wrong. Now these group of words don't have to be in a word processor. It could be in an e-mail client or it could be in an Oracle database. So wherever we have to play around with words we need a SpellChecker. Now if we could build this SpellChecker in such a way that it could work under any application then wouldn't the demand for our SpellChecker grow. We now posses the ability to invoke the SpellChecker from any container. This can be effected only if we converted it into a COM object - Component Object Model, - anyone would be able to access my code so that any example in the world could be free of any spelling errors. To do this we need a set of rules, a specification. If the specification, are provided in English i.e. using a source code level - programming level then we are back to square one as it raises the issue of compatibility. Hence we have to talk in terms of the source code at the binary level. Binary level means in the actual bits and bytes. Because when we finally convert a file to an exe file, there is no programming language associated with that exe file. One cannot look at an exe file and find out whether it is a C exe file or a PASCAL exe file. Anyone claiming otherwise, please let us know. COM or the Component Object Model is called a binary standard. When you say binary standard it means there actually has to be a standard which is concerned about the bits and bytes otherwise Visual Basic will not be able to pick up a COM object nor will Oracle be able to pick up a COM object. So COM talks about how a container and an object implementation can interact with each other at the binary level. Let us assume that we were able to successfully build a SpellChecker and were able to have an installed base of a million desktops. Visual Basic and PowerBuilder are two of the products that use our SpellChecker. (Please don't start dreaming about Microsoft buying out the company) It may so happen that both VB and PB would want to access the SpellChecker at the same time. When not in use the SpellChecker has to be unloaded from memory, because memory seems to be the most precious commodity after oxygen and coffee. So we would like to have a system by means of which an object gets loaded in the memory and then it downloads itself. This is called as 'LifeCycle Management'. If nobody is using the object, it should destroy itself. The most pertinent question is how do we find out how many people are using it , and when they have stopped using the object how many are not using it. The method adopted involves the user notifying the COM object that he is using it. And the minute he stops using the object he should inform it about the changed status. So OLE which sits on top of COM provides us with two functions one called 'AddRef', and the other called as 'Release'. Everytime you use an object you invoke AddRef and the moment you stop using the object, you invoke the 'Release' function. It can be visualized as AddRef containing a variable which will increase by 1 whenever the object is loaded whereas by invoking the Release function, the variable is reduced by 1. If the value of this variable reaches 0, the object will destroy itself. In our programs, we are the only people that are interested in the server program and hence we have no reason to worry about any form of 'Life Cycle Management'. In a worst come true situation, the object will resides in memory. At this juncture we will simply shutdown the machine. This may be the most primitive way of doing things but is the best form of Life Cycle Management. The 'AddRef' and 'Release' mechanism, as you will realize are functions which can be learnt at leisure and holds no special significance at this juncture. The 'LockServer' is used to inform Windows that the program is very important , and hence should be kept in memory. By design Windows, when it finds too many programs in memory it saves them onto the disk. So each time you desire to access the object, it has to be loaded from the disk. This decreases the response time. Such a situation is avoided by using the 'LockServer' function. However since we are the only users and not care for the extra twelve and a half second required to load the object, knowing more about the 'LockServer' can be relegated to our leisure time activities. Today we are talking in terms of having the client and the server on the same machine, this is COM. However the server can be moved to a different machine and the client should still be able to access it. This is DCOM- Distributed COM-which by Microsoft's own admission is nothing but COM over a larger wire. To allow our programs to follow the DCOM model we have to make changes to the 'CoGetClassObject'. The server remains the same. However learning DCOM can be relegated to a later day as and when we put up a tutorial on the same. Since we are not bothered about the function # 2,3 or 5 being invoked, we will turn our energies to the function # 1 which gets called a number of times. This function is called as the 'QueryInterface' and is the heart and soul of OLE. In this age of politically correct statements being the name of the game, we have to be very guarded when we try befriending a stranger. Innocuous questions like "What is your name ?" and "Where do you work?" can be used to break the ice, though they are not the best pick-up lines. Technically this exercise can be termed as 'querying' the person. When this logic is extended to include an OLE object or an OLE server. The container would like to ask the object different types of questions like for example - do you support serialization i.e. do you posses the ability to write to the hard disk or do you support the facility of 'Drag and Drop'. The OLE container would like to know if the server supports a particular class. This querying is effected by using a function called the 'QueryInterface'. As was previously stated the classes are represented by number called their IID's. Hence the 'QueryInterface' has 2 parameters. One is an IID - the 128 bit number representing the class. The second parameter is the address of a pointer. So here we have to say '**' , it takes the address of a pointer. 'CoRegisterClassObject' has been given the address of your class. Now his QueryInterface will be called (ccc). Since the first parameter states the IID of the desired class the 'CoRegisterClass' call QueryInterface and queries it to the effect that 'do you have classes which these numbers represent?' For those who despise technical talks, we will put the aforementioned statements in plain English. It simply means that when 'QueryInterface' is called, the person who is calling the 'QueryInterface' is asking the object as to what features does the object support. The object may reply to this by stating a 'yes' in such a situation it returns 0, however if another value is returned it may used to signal that the class is not supported by the object. If the object chooses to return 0, i.e. signifying support, then p should contain the address of the interface that is being queried for. For example assume that we are interested in an interface which is represented by the number 20. If the object supports this interface then it returns a '0' and provides the address of the memory location where the class starts, this is provided using the pointer 'p'. In case the object does not support the interface, it will return a non-zero result. We will work in the way as we worked with 'Windows Programming' (check out our tutorials on the same) , we will allow the non-supported messages be handled by the default method, and we answer for those interface that are supported by us. As far as the rest of the pack is concerned, we will say that we do not support the. For such non-supported interface we will return a ResultFromScode(E_NOINTERFACE). Now at this point in time, the class 'ccc' which is derived from IClassFactory supports 2 interfaces. Did we see you smirk when we mentioned two interfaces, we will tell you how we stumbled onto these figures. To be an OLE class, a class has to be derived from an interface called IUnknown. So the name of the interface is IUnknown, and its number is IID_IUNKNOWN. This interface has only 3 members - QueryInterface, AddRef, Release. Since every OLE class has to be derived from this interface it is very easy for us to ask - we need a pointer to an OLE object. Once we have that pointer, we can simply call its QueryInterface and ask for 'Do you support this interface number?' And if it supports it, it returns a true otherwise it will say that interface not supported. So this ResultFromScode is nothing but a long number where the bytes and bits are in such a way that a certain number means that this interface is not supported. So in our case, we have to support two interfaces, one them is IID_IUnknown which stands for IUnknown which has 3 function and the second is IClassFactory because 'ccc' is derived from IClassFactory. The server code containing the code for stating the support for various interface is given below. Server.cpp : #include #include void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } class ccc: public IClassFactory { virtual long _stdcall QueryInterface(REFIID r,void **p) { if (r==IID_IUnknown || r==IID_IClassFactory) { *p=this; return 0; } else return ResultFromScode(E_NOINTERFACE); } unsigned long __stdcall AddRef(void) {abc("2");return 0;} unsigned long __stdcall Release(void) {abc("3");return 0;} long __stdcall CreateInstance(struct IUnknown *,const struct _GUID &,void ** ) {abc("4");return 0;} long __stdcall LockServer(int) {abc("5");return 0;} }; ccc *d; CLSID cc; unsigned short m[128]; DWORD e; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if(x == WM_LBUTTONDOWN) MessageBox(0,"hi","hi",0); if (x == WM_CLOSE) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; HWND b; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { abc("Start"); OleInitialize(0); a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Title",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); ShowWindow(b,3); d= new ccc; MultiByteToWideChar(0,1,"SIMPSVR",-1,m,sizeof(m)/sizeof(WCHAR)); CLSIDFromString(m,&cc); CoRegisterClassObject(cc,(IUnknown *)d,4,0,&e); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } For the people who do not like the sight of the word 'this' we will explain what it means. 'this' is a number available to every function that tells where the object starts in memory. So when you say new 'ccc', that number and 'this' are the same. So now what am we doing, we are returning 0 saying yes we support IID_IClassFactory, and we also support IID_IUnknown. However for the rest of them which we don't support we will be send back a number defined by ResultFromSCode. The address locations that are sent back are also in the form of number, however the numbers that are finally reach the destination i.e. the client are not the numbers that we send. This is what we mean by Marshalling. The server may return back 300 but the client may receive a 100 because at 100 in his memory location there will be the code which will say 300 in the other guy. So once we get CoGetClassObject returning 0, we know that our OLE server has been loaded into memory and we have a pointer to it. That means now I will have a pointer to an IClassFactory . Once I have a pointer to the IClassFactory , can't I call the other functions in it. So at the next round what will we do, we will call other functions in IClassFactory which in turn will give us other pointers Till this point in time we have passed through the charted waters of OLE programming as this part has been standardized. There are two things that are required to make a standard. The first thing is that OLE standard says that there is a class called IUnknown or an interface called IUnknown, and every class which is an OLE class has to be derived from IUnknown. IUnknown being made up of the QueryInterface, AddRef and the Release functions, every OLE class has to have a QueryInterface. And it is through QueryInterface that we can ask what are the other interfaces that the object can have. Then there is an AddRef, and Release. Now these are rules. These rules make up OLE. On the other hand when you finally get an object, an OLE server, it could be written in PASCAL, or Visual Basic or any other programming language. So at that point in time we have objects interacting but at the binary level so now we also need a standard for that binary interaction. Such a standard is called COM or the Component Object Model. Marshalling -the movement of pointers across program boundaries is handled by the Component Object Model. So now we tell ourselves that see today we can't live without understanding all this. Remember that this is the smallest program so far. Once the basics become clear then everything becomes clear. By now you would have realized that this is an extremely difficult subject to master. The whole idea behind ActiveX says that if you write your code and make it into an OLE server, then anyone else can call that OLE server irrespective of whether it is C, C++, Visual Basic, the application, does not matter because COM is a binary standard. After the IClassFactory has been incorporated in the server program, when we execute the client program we observe that the client will ask for different interface. As a specific example we will consider the case of the server which has an interface called the IOleInterface. This example was chosen at random and does not imply that all servers have to support this interface. A server program supporting the IOleInterface is shown below. Server.cpp : #include #include unsigned char kk[1000],ll[1000]; void abc(char *p){FILE *fp=fopen("c:\\client\\z.txt","a+");fprintf(fp,"%s\n",p);fclose(fp);} WNDCLASS a;HWND b;MSG c;char aa[200]; long _stdcall zzz (HWND w,UINT x,WPARAM y,LPARAM z) { if ( x == WM_LBUTTONDOWN) { MessageBox(0,aa,"hi",0); } if ( x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } class iii: public IOleObject { virtual long _stdcall QueryInterface(REFIID r,void **t) { sprintf(aa,"iii Qi %ld..",r); abc(aa); if ( r == IID_IUnknown) { sprintf(aa,"iii Qi IUnknown %ld..",r); abc(aa); *t=this; return 0; } if ( r == IID_IOleObject) { sprintf(aa,"iii Qi IID_IOleObject %ld..",r); abc(aa); *t=this; return 0; } return ResultFromScode(E_NOINTERFACE); } virtual unsigned long _stdcall AddRef() {abc("iii Addref"); return 0;} virtual unsigned long _stdcall Release () {abc("iii Release"); return 0; } long __stdcall SetClientSite(struct IOleClientSite *){ abc("iii 1");return 0;} long __stdcall GetClientSite(struct IOleClientSite ** ){ abc("iii 2");return 0;} long __stdcall SetHostNames(const unsigned short *,const unsigned short *){ abc("iii 3");return 0;} long __stdcall Close(unsigned long){ abc("iii 4");return 0;} long __stdcall SetMoniker(unsigned long,struct IMoniker *){ abc("iii 5");return 0;} long __stdcall GetMoniker(unsigned long,unsigned long,struct IMoniker ** ){ abc("iii 6");return 0;} long __stdcall InitFromData(struct IDataObject *,int,unsigned long){ abc("iii 7");return 0;} long __stdcall GetClipboardData(unsigned long,struct IDataObject ** ){ abc("iii 8");return 0;} long __stdcall DoVerb(long,struct tagMSG *,struct IOleClientSite *,long,void *,const struct tagRECT *){ abc("iii DoVerb");return 0;} long __stdcall EnumVerbs(struct IEnumOLEVERB ** ){ abc("iii 9");return 0;} long __stdcall Update(void){ abc("iii 10");return 0;} long __stdcall IsUpToDate(void){ abc("iii 11");return 0;} long __stdcall GetUserClassID(struct _GUID *){ abc("iii 12");return 0;} long __stdcall GetUserType(unsigned long,unsigned short ** ){ abc("iii 13");return 0;} long __stdcall SetExtent(unsigned long,struct tagSIZE *){ abc("iii 14");return 0;} long __stdcall GetExtent(unsigned long,struct tagSIZE *){ abc("iii 15");return 0;} long __stdcall Advise(struct IAdviseSink *,unsigned long *){ abc("iii 16");return 0;} long __stdcall Unadvise(unsigned long){ abc("iii 17");return 0;} long __stdcall EnumAdvise(struct IEnumSTATDATA ** ){ abc("iii 18");return 0;} long __stdcall GetMiscStatus(unsigned long,unsigned long *){ abc("iii 19");return 0;} long __stdcall SetColorScheme(struct tagLOGPALETTE *){ abc("iii 20");return 0;} }; iii *i; class ccc : public IClassFactory { virtual long _stdcall QueryInterface(REFIID r,void **t) { sprintf(aa,"ccc Qi %ld..",r); abc(aa); if ( r == IID_IUnknown) { sprintf(aa,"ccc QueryInterface IUnknown %ld..",r); abc(aa); *t=this; return 0; } if ( r == IID_IClassFactory) { sprintf(aa,"ccc QueryInterface IClassFactory %ld..",r); abc(aa); *t=this; return 0; } else return ResultFromScode(E_NOINTERFACE); } virtual unsigned long _stdcall AddRef() {abc("ccc Addref"); return 0;} virtual unsigned long _stdcall Release () {abc("ccc Release"); return 0; } virtual long _stdcall CreateInstance(IUnknown *,REFIID r,void **t) { sprintf(aa,"ccc CreateInstance %d",r); abc(aa); i = new iii; *t = i; return 0; } long __stdcall LockServer(int) { abc("ccc LockServer");return 0;} }; ccc *d;CLSID cc;unsigned short M[128];DWORD e; int _stdcall WinMain(HINSTANCE i,HINSTANCE j,char *k,int l) { abc("Start of server"); a.lpszClassName="a11"; a.hInstance=i; a.lpfnWndProc=zzz; a.hbrBackground=GetStockObject(WHITE_BRUSH); RegisterClass(&a); OleInitialize(0); d = new ccc; MultiByteToWideChar(0,1,"SIMPSVR",-1,M,sizeof(M)/sizeof(WCHAR)); CLSIDFromString(M,&cc); CoRegisterClassObject(cc,(IUnknown *)d,4,0,&e); b=CreateWindow("a11","client",WS_OVERLAPPEDWINDOW,1,1,10,20,0,0,i,0); ShowWindow(b,3); while ( GetMessage(&c,0,0,0) ) DispatchMessage(&c); return 1; } Having a powerful server, but not having a client with the ability to invoke code in the same is akin to having no server at all. Hence we present below a client program which has the ability to invoke code in the IOleObject. This program incidentally happens to be the last client program in this tutorial. So go ahead and enjoy. client.cpp : #include #include IClassFactory *f;unsigned short m[128]; IOleObject *g; WNDCLASS a;HWND b;MSG c;char aa[200];CLSID cc; void abc(char *p){FILE *fp=fopen("c:\\client\\z.txt","a+");fprintf(fp,"%s\n",p);fclose(fp);} long _stdcall zzz (HWND w,UINT x,WPARAM y,LPARAM z) { if ( x == WM_LBUTTONDOWN) { MultiByteToWideChar(0,1,"SIMPSVR",-1,m,sizeof(m)/sizeof(WCHAR)); CLSIDFromString(m,&cc); CoGetClassObject(cc,4,0,IID_IClassFactory,(void **)&f); sprintf(aa,"f=%p",f); MessageBox(0,aa,"hi",0); f->CreateInstance(0,IID_IOleObject,(void **)&g); g->DoVerb(0,0,0,0,0,0); } if ( x == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } int _stdcall WinMain(HINSTANCE i,HINSTANCE j,char *k,int l) { a.lpszClassName="aaa"; a.hInstance=i; a.lpfnWndProc=zzz; a.hbrBackground=GetStockObject(WHITE_BRUSH); RegisterClass(&a); OleInitialize(0); b=CreateWindow("aaa","Client",WS_OVERLAPPEDWINDOW,1,1,10,20,0,0,i,0); ShowWindow(b,3); while ( GetMessage(&c,0,0,0) ) DispatchMessage(&c); return 1; } The last parameter in the CoGetClassObject is 'f' and is used to provide the address of some memory location. This enables us to use f->CreateInstance to obtain a pointer to different interface, in this case the CreateInstance is used to obtain a pointer to the interface IOleObject. The last parameter of CreateInstance i.e. 'g' is used to provide the address location of the IOleObject. This 'g' can further be used to invoke the desired function in the server program, in this case the DoVerb. In the server program which has been reproduced below and happens to be the last in it's category we have a piece of code in the DoVerb function. The server program contains the code to display a window on the screen. The parameters of this window have been changed so that we can remove every speck of doubt from your mind regarding our ability to call code in the DoVerb. Server.cpp : #include #include HWND b; BSTR bstr; void abc(char *s) { FILE * fp; fp = fopen("c:\\client\\z.txt","a+"); fprintf(fp,"%s..\n",s); fclose(fp); } char * yyy(unsigned short * b) { char *c,*a; c=(char *)malloc(1000); a=c; while(*b) { *c=*b; c++; b++; } *c=0; return a; } class iii: public IOleObject { long _stdcall QueryInterface(REFIID riid,void **t) { abc("CIOleObject::QueryInterface"); if ( riid == IID_IUnknown) { *t=this; return 0; } if ( riid == IID_IOleObject) { abc("IID_IOleObject"); *t=this; return 0; } return ResultFromScode(E_NOINTERFACE); } unsigned long _stdcall AddRef() { abc("12"); return 0;} virtual unsigned long _stdcall Release () { abc("12"); return 0; } long __stdcall SetClientSite(struct IOleClientSite *) { abc("13");return 0;} long __stdcall GetClientSite(struct IOleClientSite ** ) { abc("14");return 0;} long __stdcall SetHostNames(const unsigned short *,const unsigned short *) { abc("15");return 0;} long __stdcall Close(unsigned long) { abc("16");return 0;} long __stdcall SetMoniker(unsigned long,struct IMoniker *) { abc("17");return 0;} long __stdcall GetMoniker(unsigned long,unsigned long,struct IMoniker ** ) { abc("18");return 0;} long __stdcall InitFromData(struct IDataObject *,int,unsigned long) { abc("19");return 0;} long __stdcall GetClipboardData(unsigned long,struct IDataObject ** ) { abc("110");return 0;} long __stdcall DoVerb(long,struct tagMSG *,struct IOleClientSite *,long,void *,const struct tagRECT *) { abc("111"); ShowWindow(b,1); return 0; } long __stdcall EnumVerbs(struct IEnumOLEVERB ** ) { abc("112");return 0;} long __stdcall Update(void) { abc("113");return 0;} long __stdcall IsUpToDate(void) { abc("114");return 0;} long __stdcall GetUserClassID(struct _GUID *) { abc("115");return 0;} long __stdcall GetUserType(unsigned long,unsigned short ** ) { abc("116");return 0;} long __stdcall SetExtent(unsigned long,struct tagSIZE *) { abc("117");return 0;} long __stdcall GetExtent(unsigned long,struct tagSIZE *) { abc("118");return 0;} long __stdcall Advise(struct IAdviseSink *,unsigned long *) { abc("119");return 0;} long __stdcall Unadvise(unsigned long) { abc("120");return 0;} long __stdcall EnumAdvise(struct IEnumSTATDATA ** ) { abc("121");return 0;} long __stdcall GetMiscStatus(unsigned long,unsigned long *) { abc("122");return 0;} long __stdcall SetColorScheme(struct tagLOGPALETTE *) { abc("123");return 0;} }; iii * i; class ccc : public IClassFactory { long __stdcall QueryInterface(const struct _GUID & riid,void **p ) { abc("CIClassFactory::QueryInterface"); if(riid == IID_IClassFactory) { abc("IID_IClassFactory"); *p=this; return 0; } if(riid == IID_IUnknown) { abc("IID_IUnknown"); *p=this; return 0; } return ResultFromScode(E_NOINTERFACE); } unsigned long __stdcall AddRef(void) {abc("2"); return 0;} unsigned long __stdcall Release(void) {abc("3"); return 0;} long __stdcall CreateInstance(struct IUnknown *,const struct _GUID & riid,void **p ) { abc("CreateInstance"); StringFromCLSID(riid,&bstr); abc(yyy(bstr)); i = new iii; *p = i; return 0; } long __stdcall LockServer(int) {abc("5");return 0;} }; ccc *d; CLSID cc; unsigned short m[128]; DWORD e; long _stdcall zzz(HWND w, UINT x, WPARAM y, LPARAM z) { if(x == WM_LBUTTONDOWN) MessageBox(0,"hi","hi From Server",0); if (x == WM_CLOSE) PostQuitMessage(0); return DefWindowProc(w,x,y,z); } WNDCLASS a; MSG c; int _stdcall WinMain (HINSTANCE i, HINSTANCE j, char * k, int l) { abc("Start"); OleInitialize(0); a.hInstance = i; a.hbrBackground=GetStockObject(WHITE_BRUSH); a.lpszClassName="aaa"; a.lpfnWndProc=zzz; RegisterClass (&a); b=CreateWindow ("aaa","Title",WS_OVERLAPPEDWINDOW,1,1,100,100,0,0,i,0); d= new ccc; MultiByteToWideChar(0,1,"SIMPSVR",-1,m,128); CLSIDFromString(m,&cc); CoRegisterClassObject(cc,(IUnknown *)d,4,0,&e); while(GetMessage(&c,0,0,0)) DispatchMessage(&c); return 1; } So the whole idea of OLE is that you do a 'CoGetClassObject' at the client , and obtain a pointer to an IClassFactory, then call the CreateInstance of the IClassFactory so that you can create the actual object that you are interested in. So tomorrow if you were doing the Sound or SpeechOLE classes, then you will ask for something else in place of IOleObject and then you will say -> and call the function of the object. If you have understood the (wh)OLE concept then we can say that you are standing on the pious altar of the temple called OLE programming. "Success going to the head .... is failure ensured" It was our effort to document some of the less-known aspects of OLE programming and provide the first steps towards de-glamorizing ActiveX. We definitely do not intend to sit pretty on our achievements, we will be providing a comprehensive set of tutorials that would make the learning of ActiveX a simpler task. It is our endeavor to make this site a referral center for ActiveX. This is not possible without your valued feedback. So if you have any comments, suggestions (or hate mail) about the way this tutorial has been structured, please let us know. This will enable us to release an enhanced version of this tutorial.