Copyright
(c) 1997 by Charlie Calvert
Overview
Portions
of the following paper tend to be a bit
DCOM
allows you to easily share objects over a network. In this paper you will see
how to use
Subjects
covered in this paper include: Before
beginning a description of the fairly simple technical steps involved in
implementing DCOM, it is perhaps worthwhile talking about this technology from a
high level so that all the key ideas will be clear to everyone. If you already
understand DCOM, and just want to see how to implement it in Distributed
COM is important because it allows applications to talk to one another across a
network. In particular, it allows you to share objects that reside on two
separate machines. This means you can create an object in one application or
DLL, then call the methods of that object from an application that resides on a
different computer. DCOM is
built on top of COM, which is the technology that underlies both OLE and
ActiveX. The relationship between COM and OLE is a bit confusing, and the
boundaries separating the two technologies seems to shift at times, depending
on the vagaries of the Microsoft marketing machine. In general, it is safe to
say that OLE is a subset of COM. That is, everything that is part of OLE is
also part of COM, but not everything that is part of COM is a part of OLE.
There are, however, many people who appear to use the words COM and OLE
virtually interchangeably, and indeed, the two technologies are very closely
bound together. COM is
simply a specification for defining an object hierarchy. In particular, it lays
out a set of rules for defining objects that can be used across applications
and languages. DCOM extends this specification to allows objects on separate
machines to talk to one another. One of
the most important aspects of this technology is that it allows you to
distribute the load of a task across several machines. For instance, if you
have a complex database query to run, then you can use DCOM to ask an object on
a separate machine to run it. That way your current processor will not have to
expend any clock cycles on the task, nor will any large database related tools
be loaded onto memory on your own machine. This means that you are free to
continue playing Doom or Quake while your server looses clock cycles and
precious RAM to your background task. It's
important to understand that the COM specifications, as the name itself
implies, is really only a set of rules for defining an object hierarchy. These
rules include defining the names and methods of many of the key objects in the
hierarchy, as well as the specific techniques for structuring the objects
themselves. (When thought of in this way, there may be some value in regarding
OLE as an implementation of certain parts of the COM specification.) The COM
specification can be compared to the specification for other object hierarchies
such as VCL, OWL or MFC. For instance, all COM objects descend from a base
class called IUnknown, just as all VCL objects descend from a base class called
TObject. COM supports polymorphism and encapsulation, and it uses a series of
unique interfaces to achieve the same ends as traditional inheritance in
standard object oriented languages. Unlike OWL or VCL, COM is not tied to any
particular language, nor is it bound by application boundaries. In
short, COM is an alternative to the VCL, OWL or MFC that attempts to go these
object hierarchies one better by allowing you to use COM objects across
language, application, and now even machine boundaries. This means you can
write a COM object in Object Pascal, extend it in Delphi, and then use it in a
third language such as Visual Basic. You can call methods of the object from
inside a single application, from one application that is calling into a DLL,
or from one application that is calling an object in a separate application.
What DCOM brings to the picture is the ability to call objects that reside in
applications located on separate machines. It allows you to
"Distribute" the objects across the network. In particular, this
allows you to divide up the load of running a major task across several
machines. If you
already understand COM, then you are ready to use DCOM without any further
work. In particular, DCOM works exactly the same way that COM works. You can,
in fact, at least theoretically convert existing COM objects into DCOM objects
with no change to your code. This system works great between two Windows NT
machines, but Windows 95 requires that you switch from Share Level to User
Level access, which crimp your style in some cases. In particular, User Level
sharing requires that there be an NT machine or some other source of user
access lists available on your network. *
* * Begin Note * * * Note:
You can switch from Share Level access to User Level Access via the Network
applet found in the Control Panel. To then help configure your server, you can
use the DComCfg.exe application freely available from Microsoft's web server.
DComCfg will allow you to call DCOM servers on other machines exactly as if
they were located on your current machine. In short, you can use
CreateOleObject, rather than the custom CreateRemoteOleObject routine shown
later in this paper. *
* * End Note * * * If you
don't want to switch to User Level access, then there are alternatives that
will allow you to run DCOM as a client service on Windows 95 machines. In that
case, you only you need to add a single new parameter to your calls into an OLE
function named CoGetClassObject. More about this later, in the technical
section of this chapter. *
* * Begin Note * * * Note:
Consider these three points: The
upshot of this is that you can't set up a DCOM server unless you have an NT
machine on your network, and even under the best circumstance, a Windows 95 box
is crippled as a server. Giving these facts, I prefer to have the NT machine
act as my DCOM server, and to let the Windows 95 machines act only as clients. *
* * End Note * * * The key
point to grasp is that DCOM is really nothing more than a new capability added
to the already existing COM technology. If you have working COM objects then it
is trivial to upgrade them to work with DCOM. At the
time of this writing (February 97) DCOM has been working on Windows NT 4.0 for
over 6 months, but Microsoft has only just released DCOM for Windows 95.
Microsoft has stated that in the future, COM and DCOM will be ported to other
platforms such as UNIX and the Mac. Having,
in a sense, made the case for COM and DCOM in the previous section, its perhaps
worth stepping back for a moment and describing some competing systems. This is
a technical chapter, and I'm not interested in advocating any particular
system. However, it's probably worth while describing the current state of this
technology so that you can put this paper in perspective. COM is
a Microsoft technology that is competing with similar technologies such as
CORBA and DSOM, which are created by other corporations or groups of
corporations. Adherents of these alternate technologies can rally numerous
arguments regarding who implemented what first and who has designed the most
sophisticated technology. Furthermore, many people have invested themselves
heavily in technologies such as OWL or MFC that can be seen as
"competing", in some sense, with COM. OWL, MFC, and the VCL don't
have the same capabilities as COM, DSOM, OPENDOC or CORBA, but still there is a
feeling that the technologies are to some degree competing for the mind share
of contemporary programmers. There is no specific reason why you can't use COM
and VCL in the same program, and indeed that it is the approach I take in this
chapter. My
point here is not to advocate any particular solution, but only to make it
clear that this is a controversial topic that tends to excite strong opinions.
If you are considering using COM in your projects, you might also want to take
a look at CORBA and SOM. Conversely, if you hear criticisms of COM from other
members of the industry, you might check to see if they are so heavily invested
in some alternative technology that they are perhaps somewhat unfairly
predisposed to be critical of COM and DCOM. OLE Automation
is a technique that allows you to control one application from inside a second
application. In particular, it allows you to control an object placed inside
one application from the code of a second application. The key
to OLE Automation is a COM object called IDispatch. All OLE technologies are
based on COM, and in this particular case the functionality behind OLE
Automation is implemented by IDispatch. In short, OLE Automation is really just
a marketing term for publicizing the technology found in IDispatch. Or, more
charitably, OLE Automation is an implementation of the IDispatch specification.
IDispatch
is not difficult to understand, but it can be a bit awkward at times to
implement. To help simplify the use of IDispatch, the VCL has a class called
TAutoObject that encapsulates all the functionality of IDispatch inside a very
easy to use, and highly leveraged, technology. This
paper focuses much of its technical content on an analysis of TAutoObject.
However, it is possible to automate any COM object, and not just IDispatch. I
have chosen to concentrate on this one technology at the exclusion of others
because it provides a simple work around to the difficult problem of trying to
marshal code and data back and forth between two applications. Marshaling
is a COM specific term for the technique used to transfer data or function
calls back and forth between two applications that reside in separate
processes. For instance, if you have to pass a parameter to a function between
two applications, then you have to be sure that it is treated properly by both
applications. For instance, if you declare the parameter as an Integer in
Pascal, then that means you are passing a four byte ordinal value. How do you
express that same concept in C? How do you do it Visual Basic? The answers to
these questions are expressed in COM by a complex interface called IMarshal
that is beyond the scope of this chapter. Indeed, IMarshal is notorious for
being difficult to implement. Here is
how the Microsoft documentation defines IMarshal: "'Marshaling' is the
process of packaging data into packets for transmission to a different process
or machine. 'Unmarshaling' is the process of recovering that data at the
receiving end. In any given call, method arguments are marshaled and
unmarshaled in one direction, while return values are marshaled and unmarshaled
in the other." This is all good and well. Unfortunately, as I stated
earlier, the IMarshal interface is very hard to implement. If you
are using a standard COM object, then you don't have to implement IMarshal
because these interfaces will be marshaled for you automatically by the system.
In other words, if you are implementing an instance of IDispatch, IUnknown,
IClassFactory, IOleContainer, or any other predefined COM class, then you don't
have to worry about Marshaling. Microsoft will take care of it for you.
However, if you are creating a custom object of your own, then you need to
implement IMarshal, or to come up with some alternative scheme. Because
of its complexity, C programmers also generally choose not to attempt an
implementation of IMarshal. Instead, they rely on an intermediate language
called IDL which can be compiled into source code by a Microsoft created
program called MIDL.EXE. IDL (the Interface Definition Language) is a special
language meant to allow people to define interfaces in a neutral language that
can be compiled into source usable by multiple languages such as Pascal, C and
Visual Basic. In
other words, you can theoretically write your COM object in C or Pascal, then
use IDL to define its interface, and then use MIDL to turn that interface into
a set of files usable by any language. In other words, MIDL automatically takes
care of the IMarshal business for you so long as you first describe your interface
in IDL. This
approach is quite reasonable, but I will not treat it in this current book, in
part because For
now, however, I will back away from both IMarshal (because it is so complex)
and MIDL (because it doesn't ship with Here is
how Microsoft defines IDispatch: "IDispatch is a COM interface that
is designed in such a way that it can call virtually any other COM
interface." In other words, if you put a COM object in an application,
then you can calls its methods from a second application by using IDispatch.
This is how OLE Automation allows you to control one application from inside a
second application. (In particular, IDispatch was created to help make COM
programming easier from inside the limited confines of a Visual BASIC
application.) To
understand why IDispatch works its necessary to remember that marshaling is
taken care of for you automatically so long as you are using an existing COM
interface. In other words, you don't have to implement marshaling for IDispatch
because it is a standard COM object, and not a custom object designed by
yourself or someone on your team. IDispatch exists to allow you to call the
methods of any legal COM object. In other words, it is designed to solve the
whole problem of marshaling data. As such, it is the perfect solution for Before
closing this section of the Chapter, I should perhaps emphasize that you don't
have to use IDispatch. If you prefer, you could use other predefined COM
objects, or you could implement IMarshal, or you could attempt to use MIDL. It's
finally time to move away from theoretical issues and to concentrate instead on
technical matters. Ironically, the theory behind this technology is much harder
to understand than the technology itself. In short, this section of the paper
and the next outline a simple technique for using DCOM that can be used by any
intermediate level You
learned in the last few sections that TAutoObject is If you
go to the File menu in After
selecting the Automation Object icon, you are presented with a dialog. You can
fill in the fields of this dialog as you like, or you can put in the following
default values: ClassName:
IMyDCOM OLE
ClassName: MyProj.IMyDCOM Description:
My DCOM Object Instancing:
Multiple Instance When
you are done, The
RegisterIMyDCOM procedure is used to register your object with the system, that
is, to list it in the registry. The details of this process are described in
the section after next, called "Registration Issues". For now, you
need only take note of the ClassID assigned to your object, since you will need
this ID when you try to call the object from another machine, as described in
the next section of this paper. The act
of registering the object is not something you necessarily have to understand,
since it will occur automatically whenever you run the client application of
which IMyDCOM is a part. Note that the object will be registered repeatedly,
whenever you run the program, which ensures that you will find it easy to
register the object, while simultaneously requiring very little overhead in
terms of system resources. If you move the application to a new location, you
can register this change with the system by running it once. This guarentees
that the old items associated with your CLSID will be erased, and new items
will be filled in their place. Registering a class ID multiple times does not
mean that you will end up with multiple items in the registry, since each
registration of a CLSID will overwrite the previous registration. Besides
the registration procedure, the other key part of the code generated by the
Automation Expert is the class definition found at the top of the unit: This
code has two sections, one called private and the other called automated.
The automated section is where you can declare methods or properties
that you want to call across program or machine boundaries. In other words, any
methods or properties that you declare in this space will automatically be
marshaled for you by the underlying IDispatch object encapsulated by
TAutoObject. Consider
the following code fragment: There
are some limits to the marshaling that will be done for you by IDispatch. In
particular, the following types are legal to use in the declarations for the
methods or properties in the automated section: Currency Double Integer LongInt Single SmallInt String TDateTime Variant WordBool The
following types are illegal to use in the declarations for the methods or
properties in the automated section: Arrays Boolean Byte Cardinal Comp Pointer POleStr Records ShortInt Word The
apparent limitations created by the lack of support from IDispatch for custom
types can be considerably mitigated by an intelligent use of variant arrays.
These structures can be so helpful that I have added a section to the end of
this paper that describes there use. That's
all I'm going to say for now about creating the server side of a Delphi DCOM
project. Remember that this code will not work unless you first register the
IMyDCOM object with the system by running the server once. After you run the
server the first time, you never have to run it again, as it will be called
automatically by the client program described in the next section. Once again,
the whole point of this exercise is that the client program can be located on a
separate machine. The
following unit will call the functions in the server program described above: The
actual call to automate the object is nearly identical the call you would make
if you wanted to automated an object on your local machine. The only difference
is that you call CreateRemoteOleObject rather than CreateOleObject. CreateRemoteOleObject
is a custom function I have written that looks like this: CoServerInfo
and CoCreateInstanceEx are declared for you in Remember
that this code is not really necessary if you are able to set up DComCfg.exe on
your Windows 95 machine, or if you are using Windows NT on all your machines.
In many cases, however, it is simpler to use these routines than to wrestle
with Windows 95 and DComCfg.exe. These
CreateRemoteOleObject routines takes two parameters. The first contains the ID
of the object you want to obtain, and the second the name of the server where
the object resides. (Sometimes you may have to use the IP address itself,
rather than the name of the server.) CreateRemoteOleObject returns a variant
that "contains" a copy of the object that you want to call. You can
use this variant to call all the methods in the automated section of
your object: Variants
are a special The key
call in CreateRemoteOleObject is to CoGetClassObject: This
routine has long been a part of COM, but it has been altered slightly to
support DCOM. Here is how the routine is currently declared in OLE2.PAS: The
third parameter, that was previously reserved, is now the place where you pass
in the name of the server you want to access. The server is usually designated
with either a string, or a literal IP address, such as 143.186.149.111. You
would pass in the IP address in the form of a string. That is, don't try to
pass a number, just put the IP address in quotes, and pass it in as a string.
Here is the new declaration for CoGetClassObject: In
particular, here are the declarations for the records you pass in for the third
parameter. You need this declaration for The
first field of this record is just a version check field which should contain
the size of the TCoServerInfo record. The second parameter contains a Unicode
string that has the name of the server or its IP address embedded in it. You
can use the StringToWideChar function shown above to convert a standard The
call to CoGetClassObject retrieves a ClassFactory. Once you have the
ClassFactory back from the server, then you can use it to retrieve an instance
of the object you want to call. What you retrieve back, of course, is an
instance of IDispatch. You can convert this instance into a variant by calling
the I am
currently storing the entire CreateRemoteOleObject routine in a unit of my own
called OLEBOX.PAS, which is appended to the end of this paper. By the time I
give the talk at BDC, this routine will probably have changed to incorporate a
new version of CoCreateInstance called CoCreateInstanceEx. CoCreateInstanceEx
is superior to CoGetClassObject because it retrieves the object you want with
only one call, rather than having to first get the ClassFactory, and then call
CreateInstance on the ClassFactory. In short, CoCreateInstanceEx executes
faster than CoGetClassObject. (Remember, all calls between objects on separate
machines are going to have a considerable overhead associated with them!) Before
closing this section of the paper, let me review the key points covered so far: Before
closing this paper I want to mention a few issues about CLSIDs and the
Registry. If you already understand the registry, then you can skip this
section. The
registry is a place where information can be stored. It's a database. CLSIDs
are statistically unique numbers that can be used by the operating system to
reference an OLE object. CLSIDs are stored in the Registry. In this
case, it's probably best if you visit the actual perpetrator in its native
habitat. In the example explained here, I'm assuming that you have a copy of
Word loaded on your system. To get
started, use the Run menu on the Windows Taskbar to launch the RegEdit program
that ships with Windows NT. Just type in the word RegEdit and press the OK
button. Search through the HKEY_CLASSES_ROOT for the Word.Basic entry, shown in
Figure 29.2. When you find it, you can see that it's associated with the
following CLSID: This is
a unique class ID that 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. Now go
further up HKEY_CLASSES_ROOT and look for the CLSID branch. Open it up and
search for the CLSID shown above. When you find it, you can see two entries
associated with it: one is called LocalServer, or LocalServer32,
and the other is called ProgID. The ProgID is set to word.basic.
The LocalServer entry looks something like this: If you
take a look at this command, you can begin to grasp how Windows can translate
the CLSID passed to CoGetClassObject into the name of an executable. In
particular, Windows looks up the CLSID in the registry, and then uses the
LocalServer32 entry to find the directory and name of the executable or DLL you
want to launch. Having
these kinds of entries in the registration database does not mean that the
applications in question are necessarily automation servers. For instance,
there are many applications with LocalServer and ProgID entries
that 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, and not a reference to Word as a generic application. It references an
automation object inside of Word, and not Word itself. (The automation object
is an instance of IDispatch. It was not created with TAutoObject, but it has
all the same attributes.) The
same basic scenario outlined here takes place when you call CoGetClassObject
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. CLSID
are said to be statistically unique. You can create a new CLSID by calling
CoCreateGuid. The following code shows one way to make this call: The
code shown here begins by calling CoInitialize, which is usually unnecessary in
CoCreateGuid
is the call that retrieves the new CLSID from the system. This ID is guaranteed
to be unique so 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
children are also factored in are probably not true. At any rate, the result is
a number which 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. The
StringFromCLSID routine converts a CLSID into a string and the ParseGuid
routine is a custom function I wrote to convert a string of this type: into a
record of type TGUID that can be used in a Here is
the code for ParseGuid: That is
all I want to say about the registry for now. At the time when I deliver this
talk I should have additional information to give about inserting entries into
the registry that reference objects on other machines, as well as using the
registry to add security to your programs. Variant
arrays (and safe arrays) are costly in terms of memory and CPU cycles, so you
would not normally use them except in automation or DCOM code, or in special
cases where they provide obvious benefits over standard arrays. For instance,
the database code makes some use of variant arrays. The most
important calls for manipulating variant arrays are VarArrayCreate and VarArrayOf.
In particular, these functions are both used to create variant arrays. The
declaration for VarArrayCreate looks like this: The Bounds
parameter defines the dimensions of the array. The VarType parameter
defines the type of variable stored in the array. A one-dimensional array of
variants would be allocated like this: This
array has six elements in it, where each element is a variant. You can assign a
variant array to one or more of the elements in this array. That way you can
have arrays within arrays within arrays, if you so desire. If you
know the type of the elements to be used in an array, you can set the VarType
parameter to that type. For instance, if you knew you were going to be working
with integers, you could write: You
cannot use varString in the second parameter; instead, use varOleStr.
Remember that an array of Variant takes up 16 bytes for each member of
the array, while other types might take up less space. Arrays
of Variant can be resized with the VarArrayRedim function: 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. A
two-dimensional array would be declared like this: This
array has two dimensions, each with six elements. To access a member of this
array you would write code that looks like this: Notice
that the array performs type conversions for you, as it is an array of variants
and not, for instance, an array of integer. You can
use the VarArrayOf routine to quickly construct a one-dimensional
variant array: The
function internally calls VarArrayCreate, passing an array of Variant
in the first parameter and varVariant in the second parameter. Here is a
typical call to VarArrayOf: The
following code fragment shows how to use the VarArrayOf function: This
code prints the word "Total" in the caption of Form1. The ShowInfo
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. If you
tried to pass a variant with a VType of varInteger to this function,
You can
use the VarArrayHighBound, VarArrayLowBound, and VarArrayDimCount
functions to find out about the number of dimensions in your array, and about
the bounds of each dimension. The following function creates a pop-up message
box showing the number of dimensions in a variant array, as well as the high
and low values for each dimension: 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
created an array with the following call: the ShowInfo
function would produce the following output if passed MyVariant: ShowInfo would raise an
exception if you passed in a variant that would cause VarIsArray to
return False. There
is a certain amount of overhead in working with variant arrays. If you want to
process the arrays quickly, you can use two functions called VarArrayLock
and VarArrayUnlock. The first of these routines returns a pointer to the
data stored in an array. In particular, VarArrayLock takes a variant
array and returns a standard Pascal array. For this to work, the array must be
explicitly declared with one of the standard types, such as Integer, Bool,
string, Byte, or Float. The type used in the variant array
and the type used in the Pascal array must be identical. Here is
an example of using VarArrayLock and VarArrayUnlock: Notice
that this code first locks down the array, then accesses it as a pointer to a
standard array. Finally, it releases the array when the operation is finished.
You must remember to call VarArrayUnlock when you are finished working
with the data from the array: Remember
that the point of using VarArrayLock and VarArrayUnlock is that
it speeds access to the array. The actual code you write is more complex and
verbose, but the performance is faster. One of
the most useful reasons for using a variant array is to transfer binary data to
and from a server. If you have a binary file, say a WAV file or AVI file, you
can pass it back and forth between your program and an OLE server using variant
arrays. Such a situation would present an ideal time for using VarArrayLock
and VarArrayUnlock. You would, of course, use VarByte as the second
parameter to VarArrayCreate when you were creating the array. That is,
you would be working with an array of Byte, and accessing it directly by
locking down the array before moving data in and out of the structure. Such
arrays are not subject to translation while being marshaled across boundaries. Listing
1 contains a single example program that encapsulates most of the ideas that
you have seen in this section on variant arrays. The program from which this
code is excerpted is called VarArray. Listing 1 The VarArray program shows how to use
variant arrays. This program has two menu
items: Remember
that variant arrays are of use only in special circumstances. They are useful
tools, especially when making calls to OLE automation objects. However, they
are slower and bulkier than standard In this
paper you have learned how to use Please
remember that this paper will be updated at the conference with material that
is completely specific to
What is DCOM?
Why is COM Controversial?
DCOM, IDispatch, Marshaling and OLE Automation
Thinking about IDispatch
Using TAutoObject to Implement a DCOM Server
unit Unit2;
interface
uses
OleAuto;
type
IMyDCOM = class(TAutoObject)
private
{ Private declarations }
automated
{ Automated declarations }
end;
implementation
procedure RegisterIMyDCOM;
const
AutoClassInfo: TAutoClassInfo = (
AutoClass: IMyDCOM;
ProgID: 'Project1.IMyDCOM';
ClassID: '{7DA2AE60-BFEE-11CF-8CCD-0080C80CF1D2}';
Description: 'My DCOM object';
Instancing: acMultiInstance);
begin
Automation.RegisterClass(AutoClassInfo);
end;
initialization
RegisterIMyDCOM;
end.
IMyDCOM = class(TAutoObject)
private
{ Private declarations }
automated
{ Automated declarations }
end;
type
IMyDCOM = class(TAutoObject)
private
{ Private declarations }
automated
function GetName: string;
function ASquare(A: Integer): Integer;
end;
function IMyDCOM.GetName: string;
begin
Result := 'IMyDCOM';
end;
function IMyDCOM.ASquare(A: Integer): Integer;
begin
Result := A * A;
end;
Creating
the DCOM Client
unit
interface
uses
Windows, Messages, SysUtils,
Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses
Ole2, OleBox;
{$R *.DFM}
const
CLSID_MyDCOM: TGUID = (
D1:$7DA2AE60;
D2:$BFEE;
D3:$11CF;
D4:($8C,$CD,$00,$80,$C8,$0C, $F1, $D2));
procedure TForm1.Button1Click(Sender: TObject);
var
V: Variant;
begin
Screen.Cursor := crHourGlass;
V := CreateRemoteOleObject(CLSID_MySam, 'CharlieC');
Screen.Cursor := crDefault;
ShowMessage(V.Getname);
ShowMessage(V.ASquare(10));
end;
end.
function CoCreateInstanceEx; external ole32 name 'CoCreateInstanceEx';
function GetRemoteOleObject(ClassID: TGUID; const Server: string): Variant;
var
Unknown: IUnknown;
ClassFactory: IClassFactory;
Info: TCoServerInfo;
Dest: Array[0..127] of WideChar;
begin
ClassFactory := nil;
Info.dwReserved1 := 0;
Info.pwszName := StringToWideChar(Server, Dest, SizeOf(Dest) div 2);
Info.pAuthInfo := nil;
Info.dwReserved2 := 0;
OleCheck(CoGetClassObject(ClassID, CLSCTX_REMOTE_SERVER, @Info,
IID_IClassFactory, ClassFactory));
if ClassFactory = nil then
ShowMessage('No Class Factory')
else
ClassFactory.CreateInstance(nil, IID_IUnknown, Unknown);
try
Result := VarFromInterface(Unknown);
finally
ClassFactory.Release;
Unknown.Release;
end;
end;
function CreateRemoteOleObject(ClassID: TGUID; const Server: string): Variant;
var
Info: TCoServerInfo;
Dest: Array[0..127] of WideChar;
MultiQI: TMultiQi;
begin
MultiQi.IID := @IID_IDispatch;
MultiQI.Unknown := nil;
FillChar(Info, sizeOF(Info), #0);
Info.pwszName := StringToWideChar(Server, Dest, SizeOf(Dest) div 2);
OleCheck(CoCreateInstanceEx(ClassID, nil, CLSCTX_REMOTE_SERVER,
@Info, 1, @MultiQI));
try
Result := VarFromInterface(MultiQI.Unknown);
finally
MultiQi.Unknown.Release;
end;
end;
function GetRemoteOleObject(ClassID: TGUID; const Server: string): Variant;
var
Unknown: IUnknown;
ClassFactory: IClassFactory;
Info: TCoServerInfo;
Dest: Array[0..127] of WideChar;
begin
ClassFactory := nil;
Info.dwReserved1 := 0;
Info.pwszName := StringToWideChar(Server, Dest, SizeOf(Dest) div 2);
Info.pAuthInfo := nil;
Info.dwReserved2 := 0;
OleCheck(CoGetClassObject(ClassID, CLSCTX_REMOTE_SERVER, @Info,
IClassFactory, ClassFactory));
if ClassFactory = nil then
ShowMessage('No Class Factory')
else
ClassFactory.CreateInstance(nil, IUnknown, Unknown);
try
Result := Unknown;
finally
ClassFactory._Release;
Unknown._Release;
end;
end;
function CreateRemoteUnknown(ClassID: TGUID; const Server: string): IUnknown;
var
Info: TCoServerInfo;
Dest: Array[0..127] of WideChar;
MultiQI: TMultiQi;
Guid: TGuid;
begin
Guid := IDispatch;
MultiQi.IID := @Guid;
MultiQI.Unknown := nil;
FillChar(Info, sizeOF(Info), #0);
Info.pwszName := StringToWideChar(Server, Dest, SizeOf(Dest) div 2);
OleCheck(CoCreateInstanceEx(ClassID, nil, CLSCTX_REMOTE_SERVER,
@Info, 1, @MultiQI));
Result := MultiQI.Unknown;
end;
function CreateRemoteOleObject(ClassID: TGUID; const Server: string): Variant;
begin
Result := CreateRemoteUnknown(ClassID, Server) as IDispatch;
end;
var V: Variant; S: String; I: Integer;begin V :=
CreateRemoteOleObject(CLSID_MySam, 'CharlieC'); S := V.Getname; I :=
V.ASquare(10);end;OleCheck(CoGetClassObject(ClassID, CLSCTX_REMOTE_SERVER, @INFO,
IID_IClassFactory, ClassFactory));
function CoGetClassObject(
const clsid: TCLSID; // The ID of the object you want
dwClsContext: Longint; // In process, local or remote server?
pvReserved: Pointer; // Previously reserved, now used!!!!
const iid: TIID; // Usually IID_IClassFactory
var pv): // Where the class factory is returned
HResult; stdcall; // Success? Error?
function CoGetClassObject(
const clsid: TCLSID; // The ID of the object you want
dwClsContext: Longint; // In process, local or remote server?
ServerInfo: PCoServerInfo; // Previously reserved, now used!!!!
const iid: TIID; // Usually IID_IClassFactory
var pv): // Where the class factory is returned
HResult; stdcall; // Success? Error?
const
SOleError = 62211;
CLSCTX_REMOTE_SERVER = $10;
type
PMultiQi = ^TMultiQI;
TMultiQi = record
IID: PIID;
Unknown: IUnknown;
hr: HRESULT;
end;
PCoAuthIdentity = ^TCoAuthIdentity;
TCoAuthIdentity = record
// User: PUSHORT;
UserLength: ULong;
// Domain: PUShort;
DomainLength: ULong;
// Password: PUShort;
PasswordLength: ULong;
Flags: ULong;
end;
PCoAuthInfo = ^TCoAuthInfo;
TCoAuthInfo = record
dwAuthnSvc: DWord;
dwAuthzSvc: DWord;
pwszServerPrincName: PWideChar;
dwImpersonationLevel: DWord;
pAuthIdentityData: PCoAuthIdentity;
dwCapabilities: Dword;
end;
PCoServerInfo = ^TCoServerInfo;
TCoserverinfo = record
dwReserved1: DWord;
pwszName: PWideChar;
pAuthInfo: PCoAuthInfo;
dwReserved2: DWord;
end;
Registration
Issues
{000209FE-0000-0000-C000-000000000046}
C:\WINWORD\WINWORD.EXE /Automation
CoInitialize(nil);
CoCreateGuid(GUID);
StringFromCLSID(GUID, P);
Edit1.Text := WideCharToString(P);
S := ParseGuid(Edit1.Text);
CoUninitialize;
{FC41CC90-C01D-11CF-8CCD-0080C80CF1D2}
CLSID_MyObject: TGUID = (
D1:$FC41CC90;
D2:$C01D;
D3:$11CF;
D4:($8C,$CD,$00,$80,$C8,$0C,$F1,$D2));
function ReplaceString(NewStr, ReplaceStr, Data: string): string;
var
OffSet: Integer;
begin
OffSet := Pos(ReplaceStr, Data);
Delete(Data, OffSet, Length(ReplaceStr));
Insert(NewStr, Data, OffSet);
Result := Data;
end;
function TForm1.ParseGUID(S: string): string;
var
Len, i: Integer;
begin
FOrigClassID := S;
S := ReplaceString('D1:$', '{', S);
S := ReplaceString(';D2:$', '-', S);
S := ReplaceString(';D3:$', '-', S);
S := ReplaceString(';D4:($', '-', S);
S := ReplaceString(',$', '-', S);
S := ReplaceString('));', '}', S);
for i := 1 to 7 do begin
Len := Length(S);
if i <> 6 then
Insert(',$', S, Len - (4 * i));
end;
S := ' CLSID_++++: TGUID = (' + #13#10#32#32#32#32 + S;
Result := S;
end;
Using
Variant Arrays to Pass Data
function VarArrayCreate(const Bounds: array of Integer;
VarType: Integer): Variant;
MyVariant := VarArrayCreate([0, 5], varVariant);
MyVariant := VarArrayCreate([0, 5], varInteger);
procedure VarArrayRedim(var A: Variant; HighBound: Integer);
MyVariant := VarArrayCreate([0, 5, 0, 5], varVariant);
procedure TForm1.GridClick(Sender: TObject);
var
MyVariant: Variant;
begin
MyVariant := VarArrayCreate([0, 5, 0, 5], varVariant);
MyVariant[0, 1] := 42;
Form1.Caption := MyVariant[0, 1];
end;
function VarArrayOf(const Values: array of Variant): Variant;
V := VarArrayOf([1, 2, 3, 'Total', 5]);
procedure TForm1.ShowInfo(V: Variant);
begin
Caption := V[3];
end;
procedure TForm1.Button1Click(Sender: TObject);
var
V: Variant;
begin
V := VarArrayOf([1, 2, 3, 'Total', 5]);
ShowInfo(V);
end;
procedure TForm1.ShowInfo(V: Variant);
var
Count, HighBound, LowBound, i: Integer;
S: string;
begin
Count := VarArrayDimCount(V);
S := #13 + 'DimCount: ' + IntToStr(Count) + #13;
for i := 1 to Count do begin
HighBound := VarArrayHighBound(V, i);
LowBound := VarArrayLowBound(V, i);
S := S + 'HighBound: ' + IntToStr(HighBound) + #13;
S := S + 'LowBound: ' + IntToStr(LowBound) + #13;
end;
ShowMessage(S);
end;
MyVariant := VarArrayCreate([0, 5, 1, 3], varVariant);
DimCount: 2
HighBound: 5
LowBound: 0
HighBound: 3
LowBound: 1
const
HighVal = 12;
function GetArray: Variant;
var
V: Variant;
i, j: Integer;
begin
V := VarArrayCreate([0, HighVal, 0, HighVal], varInteger);
for i := 0 to HighVal do
for j := 0 to HighVal do
V[j, i] := i * j;
Result := V;
end;
procedure TForm1.LockedArray1Click(Sender: TObject);
type
TData = array[0..HighVal, 0..HighVal] of Integer;
var
i, j: Integer;
V: Variant;
Data: ^TData;
begin
V := GetArray;
Data := VarArrayLock(V);
for i := 0 to HighVal do
for j := 0 to HighVal do
Grid.Cells[i, j] := IntToStr(Data^[i, j]);
VarArrayUnLock(V);
end;
Data := VarArrayLock(V);
for i := 0 to HighVal do
for j := 0 to HighVal do
Grid.Cells[i, j] := IntToStr(Data^[i, j]);
VarArrayUnLock(V);
unit main;
interface
uses
Windows, Messages, SysUtils,
Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls,
Menus, Grids;
type
TForm1 = class(TForm)
Grid: TStringGrid;
MainMenu1: TMainMenu;
Info1: TMenuItem;
OneDimensino1: TMenuItem;
TwoDimension1: TMenuItem;
Show1: TMenuItem;
Normal1: TMenuItem;
LockedArray1: TMenuItem;
procedure bOneDimClick(Sender: TObject);
procedure bTwoDimClick(Sender: TObject);
procedure Normal1Click(Sender: TObject);
procedure LockedArray1Click(Sender: TObject);
private
procedure ShowInfo(V: Variant);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ShowInfo(V: Variant);
var
Count, HighBound, LowBound, i: Integer;
S: string;
begin
Count := VarArrayDimCount(V);
S := #13 + 'DimCount: ' + IntToStr(Count) + #13;
for i := 1 to Count do begin
HighBound := VarArrayHighBound(V, i);
LowBound := VarArrayLowBound(V, i);
S := S + 'HighBound: ' + IntToStr(HighBound) + #13;
S := S + 'LowBound: ' + IntToStr(LowBound) + #13;
end;
ShowMessage(S);
end;
procedure TForm1.bOneDimClick(Sender: TObject);
var
V: Variant;
begin
V := VarArrayOf(['Varariant Info', 12, 15, 23, 25]);
ShowInfo(V);
end;
procedure TForm1.bTwoDimClick(Sender: TObject);
var
V: Variant;
begin
V := VarArrayCreate([0, 5, 1, 5], varVariant);
ShowInfo(V);
end;
function GetArray: Variant;
var
V: Variant;
i, j: Integer;
begin
V := VarArrayCreate([0, 12, 0, 12], varInteger);
for i := 0 to 12 do
for j := 0 to 12 do
V[j, i] := i * j;
Result := V;
end;
procedure TForm1.Normal1Click(Sender: TObject);
var
i, j: Integer;
V: Variant;
begin
V := GetArray;
for i := 0 to VarArrayHighBound(V, 1) do
for j := 0 to VarArrayHighBound(V, 2) do
Grid.Cells[i, j] := V[i, j];
end;
procedure TForm1.LockedArray1Click(Sender: TObject);
type
TData = array[0..12, 0..12] of Integer;
var
i, j: Integer;
V: Variant;
Data: ^TData;
begin
V := GetArray;
Data := VarArrayLock(V);
for i := 0 to 12 do
for j := 0 to 12 do
Grid.Cells[i, j] := IntToStr(Data^[i, j]);
VarArrayUnLock(V);
end;
end.
Summary