In the DLL chapter, we developed a mock simulation environment representing a Honda automobile factory and various dealerships. The collaboration needed to achieve this interoperability between the Inventory keeper (our server) and the factory and dealerships (our clients ), was provided through our DLL using shared segmentation and external functions. This DLL provided an example of automation. Automation is the capability of an application to control, effect and/or access objects within another application.
COM provides an even easier way to support automation. In fact, we can create an outproc COM server with the same functionality as our DLL that automated the Honda factory/ dealership simulation, which we created in Chapter 16. A COM example of this automation is provided on the companion CD-ROM. You're encouraged to take a look at this example by opening the ProjectGroup1.bpr file within the AutoBusiness folder for this chapter. This project group includes the InventoryKeeperServer , Dealership , and Factory projects. The InventoryKeeperServer project is an outproc server that mimics the functionality provided in the original shared segmented DLL from Chapter 16. The Dealership and Factory projects represent COM client applications.
We can also take an existing application such as the MP3 player we created originally in Chapter 15, and provide an automation object so that other applications can benefit from the MP3 player's capabilities. Let's take a look at how this is done.
To follow along with this example of adding automation to an existing application, copy the MP3Demo folder from Chapter 15 and name it as MP3DemoCOM . Open the project within this folder using C++Builder. We're going to convert the MP3Player into a COM server. This is done by selecting File, New, Other, and then selecting the ActiveX tab within the New Items dialog. Next , select the Automation Object glyph. This will enable us to define both the COM interface and the CoClass within the same Type Library. This is illustrated in Figure 17.10.
If you haven't already, fill in the CoClass Name entry field and Description, then click the OK button. At this time, we don't want to select Generate Event Support Code within this dialog. We'll explain this selection within the Event Sinks section that follows . Also, an Apartment thread will work fine for our example. (For more on threading models see the Threading Model note later in this chapter).
After the OK button is selected, the Type Library Editor will appear, as illustrated in Figure 17.11.
You'll notice that within this figure we've added three methods to the IMP3Player interface that was created: Play , Stop , and Open . We could have easily added more methods such as a Pause method. Within the Open method, a File parameter has been added based on the LPSTR type. Our client will use this method to pass in the filename it wants to open. Incidentally, the parent interface for IMP3Player is IDispatch , which is used to support COM automation.
After we're content with the methods we need for our interface, we can then select the Refresh Implementation glyph on the top of the Type Library Editor. This will generate the implementation code discussed earlier in Table 17.4. Listing 17.4 displays the implementation code we need for tapping into our MP3Player.
// MP3OBJECTIMPL : Implementation of TAppObjectImpl (CoClass: AppObject, // Interface: IAppObject) #include <vcl.h> #pragma hdrstop #include "MP3ObjectImpl.h" #include "mp3Demo.h" ///////////////////////////////////////////////////////////////////////////// // TAppObjectImpl STDMETHODIMP TMP3ObjectImpl::Open(LPSTR File) { Form1->OpenMP3File(File); return S_OK; } STDMETHODIMP TMP3ObjectImpl::Play() { Form1->PlayButtonClick(NULL); return S_OK; } STDMETHODIMP TMP3ObjectImpl::Stop() { Form1->StopButtonClick(NULL); return S_OK; }
One of the few changes that we need to make to the original MP3 player code is the addition of the OpenMP3File() method for assigning a file to the player. This implementation is shown in Listing 17.5.
void __fastcall TForm1::OpenMP3File(char* filename) { bool was_playing = mp3_.playing(); if (mp3_.open(filename)) { FileText->Caption = " " + ExtractFileName(AnsiString(filename)); UpdateLengthInfo(); MP3Timer->Enabled = true; if (was_playing) mp3_.play(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::OpenButtonClick(TObject *Sender) { if (OpenDialog1->Execute()) { OpenMP3File(OpenDialog1->FileName.c_str()); } }
Before our MP3Player program can compile, we need to make a few more minor changes to the MP3Player code. Within the header file called mp3Demo.h , we use a message handler template map to field windows messages. Now that we're linking in with COM, our MESSAGE_HANDLER template has been slightly altered within one of the header files we include. To alleviate this problem, simply change MESSAGE_HANDLER to VCL_MESSAGE_HANDER , as shown here.
public: // User declarations void __fastcall OpenMP3File(char* filename); __fastcall TFormMP3(TComponent* Owner); BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(MM_MCINOTIFY, TMessage, MMMciNotify) VCL_MESSAGE_HANDLER(WM_ENTERSIZEMOVE, TMessage, WMEnterSizeMove) VCL_MESSAGE_HANDLER(WM_MOVING, TMessage, WMMoving) END_MESSAGE_MAP(TForm)
Also, we need to include the header file that defines MM_MCINOTIFY at the top of our mp3Demo.h file.
#include "MCIDevice.h"
And, for this particular example, we need to move the declaration mp3_ from the private section of our form class into the mp3Demo.cpp file so that it's in proper scope for supporting any COM Controllers.
#pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; MP3Device mp3;
Next, we just need to compile and link our program, and run one time to register it. A view of the application during execution is illustrated in Figure 17.12. Notice that it looks and acts the same as it did before!
Now, we just need to create a simple automation controller application that can take advantage of the capabilities provided by the MP3Player we just modified.
Start by creating a brand-new application. Then, select Project, Import Type Library from the main menu. The Import Type Library dialog will appear. Select Proj_mp3Demo Library, as shown in Figure 17.13.
Select Create Unit for this dialog to create a TLB unit file that will be added to your project. Next, include the header file for the TLB within your Unit's header file as follows:
#include "Proj_mp3Demo_TLB.h"
Within the private section of your class, add the following declaration.
private: // User declarations TCOMIMP3Object MP3Player;
Within the client example provided on the CD-ROM, I've added a Directory List Box and a File List Box that enables the application to operate like a file explorer. In this case, it's only interested in MP3 files. A button has been added that will play a selected MP3 file. The code for this Automation Controller is shown in Listing 17.6.
/*--------------------------------------------------------------------------- MP3FileViewerForm.cpp Chapter 17 - COM Automation Controller Example created by Paul Gustavson, 2002 ---------------------------------------------------------------------------*/ #include <vcl.h> #pragma hdrstop #include "MP3FileViewerForm.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { MP3Player = NULL; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { // let's make sure the file exists if (FileListBox1->FileName.Length() == 0) { MessageBeep(MB_OK); return; } // if (!FileExists(EditFileDll->Text)) if (!FileExists(FileListBox1->FileName)) { ShowMessage(AnsiString("Please select a file, and try again. ")); MessageBeep(MB_OK); return; } // instantiate COM object if (!MP3Player) // if we haven't instantiated MP3 COM object, do it now MP3Player = Proj_mp3demo_tlb::CoMP3Object::Create(); else // stop anything already playing MP3Player->Stop(); MP3Player->Open(FileListBox1->FileName.c_str()); MP3Player->Play(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { if (MP3Player) MP3Player->Stop(); // make sure it's stopped. } //--------------------------------------------------------------------------- void __fastcall TForm1::SpeedButtonApplyMaskClick(TObject *Sender) { FileListBox1->Mask = EditMask->Text; }
The key piece of code to analyze in this listing is the Button1Click() event handler. This handler is triggered when the user presses the button to play the selected MP3 file. If the MP3_object is NULL , it is instantiated using the CoMP3Player::Create() method. If it has been previously instantiated, a call is made to ensure that any current files playing have been stopped. It then loads and plays the new file using the methods defined by the IMP3Player interface. Figure 17.14 illustrates the Automation activities brought on by the COM Controller during execution.
The concepts we used to create an automation controller for the MP3Player can be applied for creating automation controllers for other available COM objects. Microsoft Word, for instance, can be leveraged by Automation Controller's that you create. For more information on this topic, see the Microsoft Office Integration chapter in the C++Builder 5 Developer's Guide (Chapter 21), which is provided in PDF format on the book CD-ROM.
THREADING MODELSWhen defining a class that will be instantiated as an object for a COM Server, it's important to identify the type of threading model that should be used. The threading model identifies how an object can be accessed in a multithreaded environment. More specifically , it identifies how COM should respond to (or serialize) simultaneous calls to the interface. Within C++Builder, the selection of the threading model in the New COM Object dialog for Automation objects, Active Server objects, ActiveX controls, and COM objects determines how the object will be registered. Table 17.5 identifies the various types of threading models that can be chosen . |
Threading Model | Description |
---|---|
Single | All client calls to an object are handled by only one common thread within the server. Not well suited for a server that needs to accommodate a large volume of clients simultaneously . |
Apartment | Client calls to an object are handled by separate threads. In this situation, multiple threads can access global memory, which needs to be protected. However, objects can safely access their own instance data (object properties and members ). This is also known as single-threaded apartment (STA). |
Free | An instantiated object can be accessed by multiple threads within the server at any one time. In this situation, it's important to protect both instance data and global memory. This is also known as multithreaded apartment (MTA). |
Both | Each object instance can be called by multiple threads simultaneously, except that all callbacks supplied by clients are executed in the same thread. |
Neutral | Allows multiple clients access to an object on different threads simultaneously, but COM arbitrates access ensuring no two method calls conflict. Even though calls are arbitrated, access to global memory and instance data must still be protected. This is not suitable for objects with a user interface. Only available under COM+ within C++Builder, otherwise mapped to the Apartment model. |
Top |