📄 auto.html.primary
字号:
<html>
<head>
<title> OLE Automation </title>
<meta name="description" content="OLE Automation Tutorial">
<meta name="keywords" content="OLE, automation, reliable, software, cplusplus, source code, example, windows, tutorial, object oriented, programming">
</head>
<body background="../images/grid.gif" bgcolor="white" text="black">
<!--Home button and Title-->
<table cellpadding=10 width="100%">
<tr>
<td width=100 align=center valign=middle>
<a href="../index.htm">
<img src="../images/rsbullet.gif" alt="RS" border=0 width=39 height=39>
<br>Home</a>
<td>
<font face="arial" color="#009966">
<center><i><b>
<font size="+3">Automation</font>
<p align=center><font size="+1">A. K. A. OLE Automation</font>
</b></i></center>
</font>
</table>
<table width="100%"><!-- main table -->
<tr>
<td width=10> <!-- Left margin -->
<td> <!-- Middle column, there is also the right margin at the end -->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td bgcolor=white>
<hr><!--Text-->
<font size="+1"><b>I just wanted to be able</b></font> to syphoon information from a running copy of Microsoft Developers Studio. It should have been a simple task--DevStudio, like many other MS apps, exposes its interfaces through OLE Automation. Not so simple! You see, Microsoft assumed that clients of VC++ automation interfaces will either be using Visual Basic, or DevStudio's own clever wizards. I, on the other hand, like programming in C++. (Don't you think that Microsoft's Visual C++ should be renamed Microsoft Visual MFC wizard? It relegated C++ to the role of a script language for MFC.)
<p>Anyway, once I've found out how things should be done, it turned out not to be too difficult. You just have to figure out where the heck all the information is kept in the registry. In particular, the IIDs of all the interfaces. Tip: Use OLE-COM Object Viewer that comes with VC++ to look at type libraries. It would be common courtesy if Microsoft provided a source file or an obj file with the definitions of interface ids. As it is, I had to copy and paste them from the Object Viewer. Here's an example.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->static const <font color="#009966">IID</font> IID_IApplication =
{
0xEC1D73A1, 0x8CC4, 0x11CF, { 0x9B, 0xE9, 0x00, 0xA0, 0xC9, 0x0A, 0x63, 0x2C }
};
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
So how does one get hold of a running copy of DevStudio? First, you have to create an OLE object. For that, you'll need a class id of this object. You can get the class id from the system (it's stored in the registry) if you know the program id. Program id is supposed to be a human readable name. Of course, every human being knows that the Developer Studio goes by the name "MSDEV.APPLICATION". So that's easy.
<p>With the class id in hand we can create our <i>SObject</i>. We're passing <i>true</i> as the value of the parameter <i>running</i>, because we would like to connect to the running copy of MSDEV.APPLICATION, if possible. Getting an interface from <i>SObject</i> is as simple as instantiating the <i>SObjFace</i> template with the appropriate arguments. That will be our starting point, the interface to the application.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code--><font color="#009966">CLSID</font> idMsDev;
HRESULT hr = ::<font color="#000099"><b>CLSIDFromProgID</b></font> (L"MSDEV.APPLICATION", &idMsDev);
if (FAILED (hr))
throw HEx (hr, "Couldn't convert prog id to class id");
SObject obj (idMsDev, true);
SObjFace<IApplication, &IID_IApplication> app (obj);
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
Notice that the string you pass to CLSIDFromProgID must be Unicode (wide character). Putting L in front of the string literal takes care of that.
<p>I hope you can appreciate the simplicity of this code. It's almost as simple as its VB equivalent.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->Dim app as Application
Set app = <font color="#000099"><b>GetObject</b></font> (, "MSDEV.APPLICATION")
if (app = NULL)
Set app = <font color="#000099"><b>CreateObject</b></font> ("MSDEV.APPLICATION")
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
Now let's do something with this interface. It so happens that <i>IApplication</i> has a member <i>Visible</i> that you can <i>put</i> or <i>get</i>. When you set Visible to true, the application window becomes visible. Here's the syntax for "putting" a member. Notice that in OLE you have to use things called VARIANT_BOOL and VARIANT_TRUE instead of bool and true. That's because of compatibility with Basic (makes Bill happy).
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->VARIANT_BOOL b = VARIANT_TRUE;
app->put_Visible (b);
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
How did I know that IApplication has a member Visible? Good question! There is a subdirectory <i>objmodel</i> in VC++ <i>include</i> directory where you can find such files as Appauto.h that contain lines like the ones below. You can sort of learn to interpret these files. Their crypticity is caused by the (silly!) requirement that they be includable in both C and C++ code. Microsoft didn't want to maintain two sets of header files, so here you go.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->STDMETHOD(get_Visible)(THIS_ VARIANT_BOOL FAR* Visible) PURE;
STDMETHOD(put_Visible)(THIS_ VARIANT_BOOL Visible) PURE;
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
So what do we do, now that we have the application in our hands? How about finding out what document is currently active. Using the <i>IApplication</i> interface as our source, we can create another OLE object that will represent the active document. This particular OLE object, <i>SActiveDocument</i>, can be used as a source of a few interfaces, one of them being <i>IGenericDocument</i>. We grab this interface in a standard way--by creating an object from the <i>SObjFace</i> template. <i>SActiveDocument</i>, like all our OLE/COM objects, inherits from <i>CoObject</i>, the source of interfaces.
<p><i>IGenericDocument</i> has a member <i>FullName</i> that can be gotten by calling the method <i>get_FullName</i>. Unfortunately, Basic compatibility strikes again--the result is passed in the form of a <i>BSTR</i>, or Basic string. I have created two helper classes <i>BString</i> and <i>CString</i> to take care of this weirdness. In particular <i>BString</i> makes sure that the string is deallocated using the API <i>SysFreeString</i>.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->SActiveDocument <b>docObj</b> (<b>app</b>);
if (docObj)
{
SObjFace<IGenericDocument, &IID_IGenericDocument> doc (docObj);
BString bPath;
doc->get_FullName (bPath.GetPointer ());
CString path (bPath);
canvas.Text (20, y, "Active Document:");
canvas.Text (200, y, path);
}
</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
This a typical situation in OLE Automation. Using a method of one interface, <i>app</i>, in this case, you get hold of another object, <i>docObj</i>, with its own interfaces. For each such object-returning method, we'll construct a new smart pointer class, whose only distinguishing feature is its constructor. For instance, here's the class <i>SActiveDocument</i>.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->class <font color="#cc0066"><b>SActiveDocument</b></font>: public <font color="#cc0066"><b>DispObject</b></font>
{
public:
SActiveDocument (<font color="#cc0066"><b>SObjFace</b></font><IApplication, &IID_IApplication> & app)
{
HRESULT hr = app-><b>get_ActiveDocument</b> (&_iDisp);
if (FAILED (hr))
throw HEx (hr, "get_ActiveDocument failed");
}
};</font></pre><!--End Code-->
<td width=20>
</table>
<!--End of yellow background-->
<hr><!--Text-->
Notice that the base class of <i>SActiveDocument</i> is not <i>SObject</i>--it's a new class <i>DispObject</i>. It is almost exactly like <i>SObject</i> with one distinction--instead of internally using a pointer to <i>IUnknown</i> it uses a pointer to <i>IDispatch</i>. Does it matter? Not really, I could have used <i>SObject</i> and everything would've work the same. Except that <i>IDispatch</i> can be used for something more than just querying other interfaces. It can be used for dynamic dispatching of calls. Since our program is written in C++ and it knows all the interfaces up front, we don't really need to use dynamic dispatching. But there are situations in which you need to let the user decide what object to load and which method to call, real time. Script interpreters have to be able to do it. In particular, Visual Basic scripting tools require such functionality.
<p>Below is a skeleton implementation of a <i>DispObject</i> that demonstrates this point. It also explains why why we were talking about "members" like <i>Visible</i> or <i>FullName</i> when discussing interfaces. In VB they actually appear as data members, or properties, of objects. Here, I have implemented a dispatch method <i>GetProperty</i> that can be used to load the value of any property based on its DISPID. And you can get a DISPID of any property or method as long as you know its name. The method <i>GetDispId</i> will do that for you. In a similar way, you should be able to implement <i>PutProperty</i> and, if you need it, <i>Invoke</i>, that can be used to invoke any member function by its DISPID. I leave this as an exercise for the reader.
<hr><!--End Text-->
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20>
<td bgcolor="#e0e080">
<pre><font face="courier"><!--Code-->class <font color="#cc0066"><b>DispObject</b></font>: public <font color="#cc0066"><b>CoObject</b></font>
{
public:
<font color="#cc0066"><b>DispObject</b></font> (CLSID const & classId)
:_iDisp (0)
{
HRESULT hr = ::<font color="#000099"><b>CoCreateInstance</b></font> (
classId,
0,
CLSCTX_ALL,
IID_IDispatch,
(void**)&_iDisp);
if (FAILED (hr))
{
if (hr == E_NOINTERFACE)
throw "No IDispatch interface";
else
throw HEx (hr, "Couldn't create DispObject");
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -