The client presented in the first installment of this series has the benefits of clarity and compactness. However, it contains little error-checking code and that makes it insufficient in a real application. Let's review that code, however, because it is so simple and it shows the exact steps that you must take to create a successful client:
void main()
{
HRESULT hr; // COM error code
IBeepDllObj *IBeep; // pointer to interface
hr = CoInitialize(0); // initialize COM
if (SUCCEEDED(hr)) // macro to check for success
{
hr = CoCreateInstance(
clsid,// COM class id
NULL, // outer unknown
CLSCTX_INPROC_SERVER, // server INFO
iid, // interface id
(void**)&IBeep ); // pointer to interface
if (SUCCEEDED(hr))
{
hr = IBeep-Beep(800); // call method
hr = IBeep-Release(); // release interface
}
CoUninitialize(); // close COM
}
The call to CoInitialize and CoCreateInstance initializes COM and gets a pointer to the necessary interface. Then you can call methods on the interface. When you are done calling methods you release the interface and call CoUninitialize to finish with COM. That's all there is to it. That would be all there is to it, that is, if things always worked as planned. There are a number of things that can go wrong when a COM clienttries to start a COM server. Some of the more common problems include:
l The client could not start COM
l The client could not locate the requested server
l The client could locate the requested server but it did not start properly
l The client could not find the requested interface
l The client could not find the requested method
l The client could find the requested method but it failed when called
l The client could not clean up properly
In order to track these potenital problems, you have to check things every step of the way by looking at HRESULT values. The above code does the checking, but it is difficult to tell what has gone wrong because the code is completely silent if an error occurs. The following function remedies that situation:
// This function displays detailed
// information contained in an HRESULT.
BOOL ShowStatus(HRESULT hr)
{
// construct a _com_error using the HRESULT
_com_error e(hr);
// The hr as a decimal number
cout << "hr as decimal: " <<>
// show the 1st 16 bits (SCODE)
cout << "SCODE: " <<>
// Show facility code as a decimal number
cout << "Facility: " <<>
// Show the severity bit
cout << "Severity: " <<>
// Use the _com_error object to format a message string.
// This is much easier then using ::FormatMessage
cout << "Message string: " <<>
return TRUE;
}
This function dismantles an HRESULT and prints all of its components, including the extremely useful English ErrorMessage value. You can call
it any time with this function call:
// display HRESULT on screen
ShowStatus( hr );
Figure 1. MFC Dialog example
To fully explore the different error modes of a simple COM program, the client in the following
demonstration code uses an MFC dialog application to let you control a number of possible errors
and see the effect they have on the HRESULT. When the client runs it will look like Figure 1.
You can see that the radio buttons on the left let you experiment with a lack of a CoInitialize
function, a bad class ID, and a bad interface ID. If you click the Run button the area on the right will
show the effect of the different errors on the HRESULT returned by different functions in the client.
When you explore the client code in this example, you will find that it is a somewhat more robust
version of the standard client code we used above. It also allows remote connections through
DCOM. For example, it sets default security using the CoInitializeSecurity function to introduce you
to that function, and it also makes use of the CoCreateInstanceEx function so that remote servers on other machines can be called. Walk
through the code, look up those two functions in the documentation and you will be amazed to find how easy it is to understand now that you
know something about COM!
0 comments:
Post a Comment