📄 ch27.htm
字号:
<TT>CoCreateInstanceEx</TT> is superior to
<TT>CoGetClassObject</TT> because it retrieves
the object you want with only one call instead of having to first get the <TT>ClassFactory</TT>
and then call <TT>CreateInstance</TT> on the <TT>ClassFactory</TT>. In short, <TT>CoCreateInstanceEx</TT>
executes faster than <TT>CoGetClassObject</TT>. (Remember, all calls between objects
on separate machines are going to have a considerable overhead associated with them!)
Another advantage of <TT>CoCreateInstanceEx</TT> is that it takes a
<TT>MultiQI</TT>
structure that can contain a list of multiple objects to retrieve. That way, you
can retrieve multiple objects through a single call. Again, using this method will
save considerable time.</P>
<P>Before I close this section, let me
review the key points covered so far:
<UL>
<LI>DCOM allows you to call objects located in one application or DLL on one machine
from inside a separate application on a separate machine.
<P>
<LI>You can use <TT>TAutoObject</TT> to create the
server side of your application.
<P>
<LI>You can use the custom <TT>CreateRemoteObject</TT> to call the server from a
client program located on a second server.
</UL>
<H3><A NAME="Heading27"></A><FONT COLOR="#000077">Registration
Issues</FONT></H3>
<P>Before going further, I want to mention a few issues about CLSIDs and the Registry.
If you already understand the Registry, you can skip this section. I covered some
aspects of the Registry in Chapter 13, "Flat-File,
Real-World Databases."
However, I will go over this material again here from the perspective of an OLE application.</P>
<P>The Registry is a place where information can be stored. It's a database.</P>
<P>CLSIDs are statistically unique numbers
that can be used by the operating system
to reference an OLE object. CLSIDs are stored in the Registry.</P>
<P>In this case, visiting the actual perpetrator in its native habitat is probably
best. In the example explained here, I'm assuming that you
have a copy of Word loaded
on your system.</P>
<P>To get started, use the Run menu on the Windows taskbar to launch the RegEdit
program that ships with Windows NT. Just type <TT>RegEdit</TT> and click OK. Search
through the <TT>HKEY_CLASSES_ROOT</TT>
for the <TT>Word.Basic</TT> entry, as shown
in Figure 27.4. When you find it, you can see that it's associated with the following
CLSID:</P>
<PRE><FONT COLOR="#0066FF">{000209FE-0000-0000-C000-000000000046}
</FONT></PRE>
<P>This unique class ID is
inserted into the Registry of all machines that contain
a valid, and properly installed, copy of Word for Windows. The only application that
uses this ID is Word for Windows. It belongs uniquely to that application.</P>
<P>Now go further up
<TT>HKEY_CLASSES_ROOT</TT> and look for the CLSID branch. Open
it and search for the CLSID shown above. When you find it, you can see two entries
associated with it: one is called <TT>LocalServer</TT>, or <TT>LocalServer32</TT>,
and the other is
called <TT>ProgID</TT>. The <TT>ProgID</TT> is set to <TT>word.basic</TT>.
The <TT>LocalServer</TT> entry looks something like this:</P>
<PRE><FONT COLOR="#0066FF">C:\WINWORD\WINWORD.EXE /Automation
</FONT></PRE>
<P><FONT COLOR="#0066FF"><BR>
<A
NAME="Heading28"></A></FONT><A HREF="27ebu04.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/27/27ebu04.jpg">FIGURE 27.4.</A><FONT
COLOR="#000077"> </FONT><I>If you run the Windows program <TT>Regedit.exe</TT>, then
you can see the registration database entry for <TT>Word.Basic</TT> under
<TT>HKEY_CLASSES_ROOT</TT>.</I></P>
<P>If you look at this command, you can begin to grasp how Windows can translate
the CLSID passed to <TT>CoGetClassObject</TT> into the name of an executable. In
particular, Windows looks up the CLSID in the
Registry and then uses the <TT>LocalServer32</TT>
entry to find the directory and name of the executable or DLL you want to launch.</P>
<P>Having these kinds of entries in the registration database does not mean that
the applications in question are
necessarily Automation servers. For example, many
applications with <TT>LocalServer</TT> and <TT>ProgID</TT> entries are not Automation
servers. However, all Automation servers do have these two entries. Note, further,
that this is a reference to the
Automation server in Word, not a reference to Word
as a generic application. It references an Automation object inside Word, not Word
itself. (The Automation object is an instance of <TT>IDispatch</TT>. It was not created
with <TT>TAutoObject</TT>,
but it has all the same attributes.)</P>
<P>The same basic scenario outlined here takes place when you call <TT>CoGetClassObject</TT>
and specify the CLSID of an object on another machine. In particular, Windows contacts
the specified machine, asks it
to look up the CLSID in the Registry, and then marshals
information back and forth between the two machines.</P>
<P>CLSIDs are said to be statistically unique. You can create a new CLSID by calling
<TT>CoCreateGuid</TT>. The following code shows one
way to make this call:</P>
<PRE><FONT COLOR="#0066FF">CoInitialize(NULL);
CoCreateGuid(GUID);
// eventually you should call CoUninitialize;
</FONT></PRE>
<P>The code shown here begins by calling <TT>CoInitialize</TT>, which is usually
unnecessary
in BCB because the <TT>OLE2</TT> unit will call this function automatically
when your program is launched; that is, it will do so if you include <TT>OLE2</TT>
in the <TT>uses</TT> clause of one of your units.</P>
<P><TT>CoCreateGuid</TT> is the call
that retrieves the new CLSID from the system.
This ID is guaranteed to be unique as long as you have a network card on your system.
Each network card has a unique number on it, and this card number is combined with
the date and time and other random
bits of information to create a unique number
that could only be generated on a machine with your network card at a particular
date and time. Rumors that the phase of the moon and current age of Bill Gates's
children are also factored in are probably
not true. At any rate, the result is a
number that is guaranteed to be statistically unique, within the tolerance levels
for your definition of that word given your faith in mathematicians in general and
Microsoft-based mathematicians in
particular.</P>
<P>The <TT>StringFromCLSID</TT> routine converts a CLSID into a string. The <TT>ParseGuid</TT>
routine is a custom function I wrote to convert a string of type</P>
<PRE><FONT COLOR="#0066FF">{FC41CC90-C01D-11CF-8CCD-0080C80CF1D2}
</FONT></PRE>
<P>into a record of type <TT>GUID</TT> that can be used in a BCB application as defined
in <TT>Wtypes.h</TT>:</P>
<PRE><FONT COLOR="#0066FF">typedef struct _GUID
{
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE
Data4[ 8 ];
} GUID;
</FONT></PRE>
<P>That's all I want to say about the Registry for now. This subject can appear a
bit tricky at first, but ultimately it is not complicated.
<H3><A NAME="Heading29"></A><FONT COLOR="#000077">Using Variant
Arrays to Pass Data</FONT></H3>
<P>BCB enables you to create variant arrays, which are the VCL version of the safe
arrays used in OLE Automation. You can use variant arrays to pass large chunks of
data back and forth between COM objects. For example,
you can pass a bitmap, AVI
file, or text file between two applications using variant arrays. In short, this
type can help you avoid the shortcomings created by the limited types supported by
<TT>IDispatch</TT>.</P>
<P>Variant arrays (and safe arrays)
are costly in terms of memory and CPU cycles,
so you normally would not use them except in automation or DCOM code, or in special
cases in which they provide obvious benefits over standard arrays. For example, the
database code makes some use of
variant arrays.</P>
<P>The <TT>Variant</TT> class type, found in <TT>SysDefs.h</TT> and covered in Chapter
3 has constructors for creating variant arrays:</P>
<PRE><FONT COLOR="#0066FF">// constructor for array of variants of type varType
__fastcall
Variant(const int* bounds, const int boundsSize,
Word varType);
// constructor for one-dimensional array of type Variant
__fastcall Variant(const Variant* values, const int valuesSize);
</FONT></PRE>
<P>If you know the type of
the elements to be used in an array, you can set the <TT>VarType</TT>
parameter to that type. For example, if you know you're going to be working with
integers, you can write the following:</P>
<PRE><FONT COLOR="#0066FF">Variant
MyVariant(OPENARRAY(int, (0, 5)), varInteger);
</FONT></PRE>
<P>You cannot use <TT>varString</TT> in the last parameter; instead, use <TT>varOleStr</TT>.
Remember that an array of <TT>Variant</TT> takes up 16 bytes for each member of the
array, and
other types might take up less space.</P>
<P>Arrays of <TT>Variant</TT> can be resized with the <TT>VarArrayRedim</TT> function:</P>
<PRE><FONT COLOR="#0066FF">extern void __fastcall VarArrayRedim(Variant &A, int HighBound);
</FONT></PRE>
<P>The
variable to be resized is passed in the first parameter, and the number of
elements to be contained in the resized array is held in the second parameter.</P>
<P>You declare a two-dimensional array like this:</P>
<PRE><FONT COLOR="#0066FF">Variant
MyVariant(OPENARRAY(int, (0, 5, 0, 5)), varInteger);
</FONT></PRE>
<P>This array has two dimensions, each with six elements. To access a member of this
array, you write code that looks like the following:</P>
<PRE><FONT COLOR="#0066FF">for (i = 0; i
< 6; i++)
for (j = 0; j < 6; j++)
MyVariant.PutElement(i * j, i, j);
for (i = 0; i < 6; i++)
{
for (j = 0; j < 6; j++)
{
S = S + " " + MyVariant.GetElement(i, j);
}
S = S + `\r';
}
</FONT></PRE>
<P>The following code fragment shows how to use a one-dimensional array and how to
query an array to find out about its composition:</P>
<PRE><FONT COLOR="#0066FF">AnsiString TForm1::GetInfo(Variant &V)
{
int Count, HighBound,
LowBound, i;
AnsiString S;
Count = VarArrayDimCount(V);
S = AnsiString("\nDimension Count: ") + IntToStr(Count) + `\n';
for (i = 1; i <= Count; i++)
{
HighBound = VarArrayHighBound(V, i);
LowBound =
VarArrayLowBound(V, i);
S = S + "LowBound: " + IntToStr(LowBound) + `\n';
S = S + "HighBound: " + IntToStr(HighBound) + `\n';
}
return S + `\n';
</FONT></PRE>
<PRE><FONT COLOR="#0066FF">
}
</FONT></PRE>
<PRE><FONT COLOR="#0066FF">
void __fastcall TForm1::bOneDimClick(TObject *Sender)
{
AnsiString S;
int i;
S = "";
Variant MyVariant(OPENARRAY(int, (0, 5)), varInteger);
for (i = 0; i <= 5; i++)
MyVariant.PutElement(i
* 2, i);
for (i = 0; i <= 5; i++)
S = S + " " + MyVariant.GetElement(i);
S = GetInfo(MyVariant) + S;
ShowMessage(S);
}
</FONT></PRE>
<P>The <TT>GetInfo</TT> method demonstrates how to work with a variant array passed
as a
parameter. Notice that you don't have to do anything special to access a variant
as an array. The type travels with the variable.</P>
<P>If you try to pass a variant with a <TT>VType</TT> of <TT>varInteger</TT> to this
function, BCB raises an
exception when you try to treat the variant as an array.
In short, the variant must have a <TT>VType</TT> of <TT>VarArray</TT>; otherwise,
the call to <TT>GetInfo</TT> will fail. You can use the <TT>VarType</TT> function
to check the current setting
for the <TT>VType</TT> of a variant, or you can call
<TT>VarIsArray</TT>, which returns a Boolean value.</P>
<P>You can use the <TT>VarArrayHighBound</TT>, <TT>VarArrayLowBound</TT>, and <TT>VarArrayDimCount</TT>
functions to find out about the number
of dimensions in your array and about the
bounds of each dimension. The following <TT>GetInfo</TT> function creates a string
showing the number of dimensions in a variant array, as well as the high and low
values for each dimension:</P>
<PRE><FONT
COLOR="#0066FF">AnsiString TForm1::GetInfo(Variant &V)
{
int Count, HighBound, LowBound, i;
AnsiString S;
Count = VarArrayDimCount(V);
S = AnsiString("\nDimension Count: ") + IntToStr(Count) + `\n';
for (i = 1; i <=
Count; i++)
{
HighBound = VarArrayHighBound(V, i);
LowBound = VarArrayLowBound(V, i);
S = S + "LowBound: " + IntToStr(LowBound) + `\n';
S = S + "HighBound: " + IntToStr(HighBound) + `\n';
}
return S +
`\n';
}
</FONT></PRE>
<P>This routine starts by getting the number of dimensions in the array. It then
iterates through each dimension, retrieving its high and low values. If you create
an array with the call</P>
<PRE><FONT COLOR="#0066FF">Variant
MyVariant(OPENARRAY(int, (0, 5, 1, 3)), varInteger);
</FONT></PRE>
<P>the <TT>GetInfo</TT> function produces the following output if passed <TT>MyVariant</TT>:</P>
<PRE><FONT COLOR="#0066FF">Dimension Count: 2
HighBound: 5
LowBound: 0
HighBound: 3
LowBound: 1
</FONT></PRE>
<P><TT>GetInfo</TT> raises an exception if you pass in a variant that causes <TT>VarIsArray</TT>
to return <TT>False</TT>.</P>
<P>A certain amount of overhead is involved in working with variant arrays. If you
want to
process the arrays quickly, you can use two functions called <TT>VarArrayLock</TT>
and <TT>VarArrayUnlock</TT>. The first of these routines returns a pointer to the
data stored in an array. In particular, <TT>VarArrayLock</TT> takes a variant array
and returns a standard Pascal array. For it to work, the array must be explicitly
declared with one of the standard types listed earlier in the chapter. The type used
in the variant array and the type used in the Pascal array must be identical.</P>
<P>Here is an example of using <TT>VarArrayLock</TT> and <TT>VarArrayUnlock</TT>:</P>
<PRE><FONT COLOR="#0066FF">Variant GetArrayData()
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -