Perhaps one of the most advanced features of OLE, a feature that's just coming into widespread use, is automation. Because OLE automation enables one application to control or use the services of another, it is the crux of component programming and component reusability. For example, using OLE automation, you can obtain spell checker services simply by controlling another application's spell checker. In this way, applications can take advantage of capabilities already present in the user's system, without having to build common functions like spell checking into their own applications. Programmers are now free to concentrate on an application's specifics, rather than wasting their time reinventing the wheel. In this chapter, you learn to take advantage of OLE automation.
An OLE automation server is an application that provides components that other applications can use. For example, a word processor that provides a spell checker component is an automation server if other applications can use the spell checker's services through OLE automation.
If you had to program an OLE automation server from scratch, you'd need a shelfful of manuals and a year or two of intense OLE training. Although OLE is extremely powerful, nobody ever promised that it would be easy to program! Luckily, Microsoft has built most of the OLE capabilities your application is likely to need right into MFC. Because of this, writing an automation server is easier than you might believe. To create an automation server, you must complete the following steps:
1. Use AppWizard to create an OLE automation skeleton application.
2. Complete the stand-alone part of the application, just as you would with any other MFC application.
3. Create properties and methods to provide an automation interface for the application.
In the following sections, you'll complete the previous steps as you create AutoServer, a basic automation server application. Although AutoServer is a very simple application, it demonstrates how to create a server application's automation interface. You can then apply these basics toward the creation of more sophisticated applications.
The first step in creating an automation server is to use AppWizard to create a skeleton application that supports OLE automation. This is as easy as selecting a single option in the AppWizard dialog boxes. The second step, after creating the skeleton application, is to complete the program so that it can run as a stand-alone application. Complete the following steps to create the AutoServer stand-alone application. Later, in the section "Adding Properties and Methods to the Automation Server," you'll create the application's OLE automation interface.
ON THE WEB
http://www.quecorp.com/semfc The complete source code and executable file for this version of the ActiveXServ application is located at this books Web site.
1. Start a new AppWizard project workspace called AutoServer.
2. Give the new project the following settings in the AppWizard dialog boxes. The New Project Information dialog box should then look like Figure 28.1.
Notice that all you need to do to create a skeleton automation server is to choose the OLE Automation option in the Step 3 dialog box.
Step 1:
Single documentStep 2: Default settings
Step 3: Select O
LE AutomationStep 4: Default settings
Step 5: Default settings
Step 6: Default settings
These are the project settings for the AutoServer application.
3. Load the AutoServerDoc.h header file, and add the following line to the class's Attributes section, right after the public keyword:
UINT m_x;
This line defines the integer that holds the editable data for an AutoServer document.
4. Load the AutoServerDoc.cpp file, and add the following line to the class's constructor, right after the TODO: add one-time construction code here comment:
m_x = 20;
This line initializes the data to its default value.
5. Add the following line to the Serialize() function, right after the TODO: add storing code here comment:
ar << m_x;
6. Add the following line to the Serialize() function, right after the TODO: add loading code here comment:
ar >> m_x;
7. Load the AutoServerView.cpp file, and add the following line to the OnDraw() function, right after the TODO: add draw code for native data here comment:
pDC->TextOut(pDoc->m_x, 20, "TEST STRING");
This line displays the document's test string, using the X position found in the document class's m_x data member.
8. Load the IDR_MAINFRAME menu into Developer Studio's menu editor. Add a
Set menu, as shown in Figure 28.2. Give theSet X Position command an ID of ID_SET_SETXPOSITION.Use the menu editor to add the
Set menu.9. Use ClassWizard to add the OnSetSetxposition() message response function to the document class, as shown in Figure 28.3.
The OnSetSetxposition() message response function responds to your new
Set X Position menu command.10. Click the
Edit Code button, and add the lines shown in Listing 28.1 to the OnSetSetxposition() function:Listing 28.1 lst28_01.cpp Program Lines for the OnSetSetxposition() Function
if (m_x == 20) m_x = 300; else m_x = 20; SetModifiedFlag(); UpdateAllViews(NULL);
You've now created the stand-alone AutoServer application. To compile the program, choose Developer Studio's Build, Build command. Then,
choose Build, Execute to run the program. When you do, you see the window shown in Figure 28.4. To see the application in action, choose the
Set, Set X Position menu command. The display string moves to a new location (see Figure 28.5). When you choose the command again, the
string moves back to its original position.
This is the AutoServer application's main window.
Here's the main window after the user chooses the Set X Position command.
AutoServer isn't exactly an award-winning application. (Talk about stating the obvious!) It is, however, fine for demonstrating how OLE automation works. Still, although AppWizard built a lot of OLE automation functionality into the program when you created it, you need to add the automation code that is specific to the application. In the following sections, you'll examine the code created by AppWizard. You'll then finish the automation server application by adding the properties and methods that make up the application's automation interface.
To understand the source code, the first place to look is in InitInstance(), where the application performs its initialization at startup. That function, which is defined as part of the CAutoServerApp class, performs its normal initialization, as well as some special initialization for OLE. To initialize the OLE libraries, InitInstance() calls AfxOleInit(), as shown in Listing 28.2.
Listing 28.2 lst28_02.cpp Initializing ActiveX
if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; }
The InitInstance() function also calls the AfxEnableControlContainer() global function, which enables the application to act as a container for ActiveX controls. That function call looks like this:
AfxEnableControlContainer();
As you now know, all ActiveX and OLE applications must be registered with the system. Each application and document type is identified by a GUID. In the case of the AutoServer application, the GUID is defined, as shown in Listing 28.3.
Listing 28.3 lst28_03.cpp The Application's GUID
// {46DCFE58-6FBB-11D0-847F-444553540000} static const CLSID clsid = { 0x46dcfe58, 0x6fbb, 0x11d0, { 0x84, 0x7f, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } };
If you've followed the previous steps to create your own copy of the AutoServer application, your version will have a different GUID than the one shown in Listing 28.3.
Normally, when an automation server is loaded by a client application, the server's window remains hidden. The InitInstance() function handles this detail by checking whether or not the application is being run as an OLE server. If so, the function registers all servers as running and returns without showing the main window. Listing 28.4 shows the program lines that perform these actions.
Listing 28.4 lst28_04.cpp Initializing the Application as an OLE Server
// Check to see if launched as OLE server if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated) { // Register all OLE server (factories) as running. This enables the // OLE libraries to create objects from other applications. COleTemplateServer::RegisterAll(); // Application was run with /Embedding or /Automation. Don't show the // main window in this case. return TRUE; }
When the application is run as a stand-alone application, the program skips over the lines in Listing 28.4 and instead registers the application in the Registry and displays the main window, as shown in Listing 28.5.
Listing 28.5 lst28_05.cpp Registering the Application and Displaying Its Window
// When a server application is launched stand-alone, it is a good idea // to update the system registry in case it has been damaged. m_server.UpdateRegistry(OAT_DISPATCH_OBJECT); COleObjectFactory::UpdateRegistryAll(); // The one and only window has been initialized, so show and update it. m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow();
If you want the application's window to appear even when it's run as an OLE server, you can rewrite InitInstance() so that it displays the window no matter how the application is run. Another way to show the window at startup is to provide a ShowWindow() method in the server's automation interface, and then call ShowWindow() from the client application. You'll see how to create a ShowWindow() method later in this chapter, in the section entitled "Adding Properties and Methods to the Automation Server."
In an MFC automation server application, it's the document class that handles the automation interface. First, the document class defines its own GUID, as shown in Listing 28.6.
Listing 28.6 lst28_06.cpp Defining the Document Class's GUID
// {46DCFE82-6FBB-11D0-847F-444553540000} static const IID IID_IAutoServer = { 0x46dcfe82, 0x6fbb, 0x11d0, { 0x84, 0x7f, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } };
If you've created your own copy of the AutoServer application, your document class will have a different GUID than the one shown in Listing 28.6.
The document class also defines a dispatch map, which works similarly to the message maps you've used before, except that the dispatch map links the automation server's interface with the properties and methods that a client application can manipulate. At this point in the building of the application, the document class's dispatch map is defined, as shown in Listing 28.7. When you add properties and methods to the interface, ClassWizard will add entries to the dispatch map.
Listing 28.7 lst28_07.cpp The Document Class's Dispatch Map
BEGIN_DISPATCH_MAP(CAutoServerDoc, CDocument) //{{AFX_DISPATCH_MAP(CAutoServerDoc) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_DISPATCH_MAP END_DISPATCH_MAP()
Although the application class (CAutoServerApp, in this case) is responsible for initializing OLE, it is the document class (CAutoServerDoc, in this case) that sets up OLE automation. It does this in its constructor, as shown in Listing 28.8. The call to EnableAutomation() initializes automation for the application, whereas the call to the global function AfxOleLockApp() ensures that the server will remain in memory as long as a client application requires the server's services.
Listing 28.8 lst28_08.cpp Initializing OLE Automation
CAutoServerDoc::CAutoServerDoc() { // TODO: add one-time construction code here EnableAutomation(); AfxOleLockApp(); }
As you might have guessed, the call to AfxOleLockApp() has a complementary function that's called from the document class's destructor, as shown in Listing 28.9. The call to AfxOleUnlockApp() releases the server from its connection with this client. If no other clients are using the server, the server can be removed from memory.
Listing 28.9 lst28_09.cpp The Document Class's Destructor
CAutoServerDoc::~CAutoServerDoc() { AfxOleUnlockApp(); }
Now that you know how the basic application works, it's time to make AutoServer into a complete automation server. To create the full automation server application, you must create an interface that other application's can use to control the server. This interface is comprised of properties (similar to variables) and methods (similar to functions). The automation client (the application that controls the automation server) accesses values through the properties and performs actions by calling the server interface's methods. In this section, you'll add a property and method to the AutoServer application. Just complete the following steps.
ON THE WEB
http://www.quecorp.com/semfc The complete source code and executable file for this version of the ActiveXServ application is located in the Chap28\AutoServer, Part 2 folder at this books Web site.
1. Display ClassWizard (press Ctrl+W), and choose the Automation page (see Figure 28.6). Choose CAutoServerDoc in the Class
Name box.ClassWizard's Automation page enables you to add properties and methods to the automation server's interface.
2. Click the Add P
roperty button. The Add Property dialog box appears.3. Make sure that you choose the Get/Set
Methods option. Fill in the dialog box as shown in Figure 28.7. Click OK to dismiss the Add Property dialog box.This new property will represent the document class's m_x variable, enabling an automation client application to change the position of the display string in the server's window. The x variable is what the interface's user sees as the property's name. The m_x data member is where the value of the x property is stored.
Create the automation interface's new property as shown here.
4. Click the
Add Method button. The Add Method dialog box appears.5. Fill in the dialog box as shown in Figure 28.8. Click OK to dismiss the Add Method dialog box.
The ShowWindow() method enables an automation client application to display the server application's window. Normally, a server application runs without displaying its window.
Create the automation server's new method as shown here.
6. Click the
Edit Code button to find the ShowWindow() method. Add the lines shown in Listing 28.10 to the ShowWindow() method.Listing 28.10 lst28_10.cpp Program Lines for the ShowWindow() Method
// Get a pointer to the view window. POSITION position = GetFirstViewPosition(); CView* pView = GetNextView(position); // If the window exists... if (pView != NULL) { // Get a pointer to the frame window. CFrameWnd* pFrameWnd = pView->GetParentFrame(); // Show the frame window. pFrameWnd->ActivateFrame(SW_SHOW); }This code first gets a pointer to the server's view window. It then uses the view pointer to obtain a pointer to the server's parent window. Finally, the ActivateFrame() method is called through the parent window's pointer, which causes the window to be displayed.
7. Replace the return line in the GetX() property function with the following line:
return (short)m_x;
Returning the value as a short integer ensures that the server will work under both 16-bit and 32-bit Windows.
8. Complete the SetX() property function with the following program lines:
m_x = nNewValue;
SetModifiedFlag();
UpdateAllViews(NULL);
You've now added the specific automation functionality required by the AutoServer application. To compile the program, choose Developer Studio's B
uild, Build command. Now that you've completed this portion of the program, you've got an automation server that can also function as a stand-alone
application. Unfortunately, you currently have no way to test the program's automation capabilities, because you have no automation client application that can use
AutoServer's automation interface. In the next section, you'll create that client application.
There are a number of ways that you can test an automation server, because the server can be accessed from any language that supports OLE automation. This includes, not only C++, but also Visual Basic and Visual Basic for Applications. Because this is a C++ book, you'll now discover how to create a C++ client program using AppWizard. Creating an automation client application consists of a number of basic steps::
1. Create a normal AppWizard skeleton program.
You don't have to select any OLE options when creating the skeleton application. Because the application requires so little OLE code, it's easier to add the lines you need than to remove a lot of lines you don't need.
2. Call AfxOleInit() from the application class's InitInstance() function.
All application's that use ActiveX must call AfxOleInit() to initialize the OLE system.
3. Use ClassWizard to create an interface class from the server application's type library.
Although you didn't know it, when you created the server application, ClassWizard created a type-library file for the server's interface. This type library includes the names and types of properties, as well as a list of the methods that a client application can call. ClassWizard takes the information in the type library and creates a class for the interface. This class makes it easy for you to access server properties or to call server methods.
4. Create, as a data member of the view class, an object of the interface class.
Just as with any class, before you can access the class's data and function members, you have to create an instance of the class.
5. Call the interface object's CreateDispatch() function to load the automation server.
Creating an instance of the interface class isn't enough to get it rolling. You also need to call CreateDispatch(), which loads the server and connects the interface object with the server's interface.
6. Access the server's interface through the interface object.
After you have the interface object created and connected to the server's interface, you can access the server's properties and methods almost exactly as if they were part of your own application.
The requirements listed previously are easier to implement than they might sound, as you'll see as you create the automation client application. Complete the following steps to create the AutoClient application:
ON THE WEB
http://www.quecorp.com/semfc The complete source code and executable file for the AutoClient application is located at this books Web site.
1. Start a new AppWizard project workspace called AutoClient.
2. Give the new project the following settings in the AppWizard dialog boxes. The New Project Information dialog box should then look like Figure 28.9.
Step 1:
Single documentStep 2: Default settings
Step 3: Default settings
Step 4: Default settings
Step 5: Default settings
Step 6: Default settings
The final project settings should look like this.
3. Bring up ClassWizard, and click the Add Class button and choose the From a Type Library selection (see Figure 28.10). The Import From Type Library dialog box appears.
You can use ClassWizard to create a class from a type library.
4. Use the dialog box to locate and select the AutoServer.tlb file in your project's Debug or Release directory (see Figure 28.11).
The .tlb file contains all of the information that ClassWizard needs about the automation server's interface.
5. Click the
Open button, and the Confirm Classes dialog box appears (see Figure 28.12).The Confirm Classes dialog box gives you a chance to change class and file names.
6. Click OK to accept the default names for the server interface class and its files. ClassWizard creates the new IAutoServer interface class. Click OK to close ClassWizard.
IAutoServer is the interface class that you'll use to connect to the server's interface.
7. Open the AutoClientView.h header file, and add the following line to the Attributes section, right after the public keyword:
IAutoServer m_autoServer;
This line creates an object of the IAutoServer class as a data member of the view class.
8. Type the following line near the top of the AutoClientView.h file, right before the class's declaration begins:
#include "AutoServer.h"
This line ensures that the IAutoServer interface class's declaration is accessible to the view class.
9. Use ClassWizard to add an OnCreate() function to the CAutoClientView class, as shown in Figure 28.13.
Add the OnCreate() function using these ClassWizard settings.
10. Click the
Edit Code button, and add the lines shown in Listing 28.11 to the OnCreate() function, right after the TODO: Add your specialized creation code here comment:Listing 28.11 lst28_11.cpp Program Lines for the OnCreate() Function
// Load the automation server object. BOOL success = m_autoServer.CreateDispatch("AutoServer.Document"); // If AutoServer object didn't load, halt the program. if (!success) { AfxMessageBox("Failed to create AutoServer object!"); return -1; }11. Use Developer Studio's menu editor to add the
Automate menu shown in Figure 28.14. Give the SetXPosition command the ID_AUTOMATE_SETXPOSITION menu ID, and give theShow Window command the ID_AUTOMATE_SHOWWINDOW menu ID.The new Automate menu should look like this.
12. Use ClassWizard to create the OnAutomateSetxposition() message response function (see Figure 28.15).
Create the OnAutomateSetxposition() function like this.
13. Click the
Edit Code button, and add the lines shown in Listing 28.12 to the OnAutomateSetxposition() function, right after the TODO: Add your command handler code here comment:Listing 28.12 lst28_12.cpp Program Lines for the OnAutomateSetxposition() Function
// Get the current X position. int x = m_autoServer.GetX(); // Set the new X position. if (x == 20) m_autoServer.SetX(300); else m_autoServer.SetX(20);14. Use ClassWizard to create the OnAutomateShowwindow() message response function (see Figure 28.16).
Create the OnAutomateShowwindow() function like this.
15. Click the
Edit Code button, and add the line shown below to the OnAutomateShowwindow() function, right after the TODO: Add your command handler code here comment:m_autoServer.ShowWindow();
16. Load the AutoClient.cpp file, and add the lines shown in Listing 28.13 to the beginning of the InitInstance() function:
Listing 28.13 lst28_13.cpp Program Lines for the InitInstance() Function
if (!AfxOleInit()) { AfxMessageBox("OLE initialization failed!"); return FALSE; }17. Load the MainFrame.cpp file, and add the lines that follow to the PreCreateWindow() function:
cs.cx = 250;
cs.cy = 200;
These lines set the initial size of the application's frame window.
You've now created the AutoClient application. To compile the program, choose Developer Studio's Build, Build command. Then, choose B
uild, Execute to run the program. When you do, you see the window shown in Figure 28.17. To see the server application's window, choose the A
utomate, Show Window command. The server application's window appears, as shown in Figure 28.18.
Here's AutoClient when you first run it.
The client application can display the server application's window.
To change the server application's string position, choose the Automate, Set X Position command. This forces the server application to update its
m_x member variable and redraw the string at its new position (see Figure 28.19).
The client application can also reposition the string displayed in the server application's window.
The automation server and client applications presented in this chapter only scratch the surface of what this powerful technology can do. You should use the knowledge you've gained here and spend some time creating automation applications that actually do useful work.