Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Delphi records as OleVariant out parameters

1,825 views
Skip to first unread message

Howard Moon

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to
Hi...
I have a server Com object hitting a database and returning the fields as
OleVariants. I want to return all the fields as one record, passing it as
an OleVariant (which appears to be the only way to use an out parameter in a
Com object).
How can I create this OleVariant on the server side and then get it as a
record again on the client side? It's easy with string or integers...Delphi
wraps and unwraps them for me. But this does not seem to be the case for
records.
I have checked the Help files, but only see limited info on the OleVariant
type, and no clue as to how to set the variant type and get it to point to
my record data. I also don't see how the client will then unwrap the data.
Any clues??? (Thanks in advance...)
-Howard


Deborah Pate

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to
<<Howard Moon:

How can I create this OleVariant on the server side and
then get it as a record again on the client side?
>>

You can send records, or arrays of records, as Variant
byte arrays. Here's an example:

type
TRec = record
A, B: integer;
end;
PRec = ^TRec ;

function SendArray: OleVariant;
var
Rec: TRec;
RecPtr: PRec;
Size: integer;
begin
// Set up the record
Rec.A := 4;
Rec.B := 5;

// Stuff it into a variant array
Size := SizeOf(TRec);
Result := VarArrayCreate([0, Size], varByte);
RecPtr := VarArrayLock(Result);
CopyMemory(RecPtr, @Rec, Size);
VarArrayUnlock(Result);
end;

procedure GetDataFromArray(const VArray: Variant);
var
RecPtr: PRec;
begin
RecPtr := PRec(VarArrayLock(VArray));
VarArrayUnlock(VArray);
ShowMessage('Value of Rec.a is ' + IntToStr(RecPtr^.A));
end;

procedure TForm1.PassingRecordsBtnClick(Sender: TObject);
begin
GetDataFromArray(SendArray);
end;

--
Deborah Pate

Santiago Cimadevilla

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to

What I do is 'pack' the records into variant arrays. That is, if the
record you're using has simple types...if it has an array, for
instance, or another record, you must pack those separately.

For instance, having

Type
TMyRec = Record
String1, String2 : String;
Integer1 : Integer;
End;

what you do is :
(untested code follows)

MyOleVariant := VarArrayCreate ([0, 3], VarVariant);
MyOleVariant [0] := MyRec.String1;
MyOleVariant[1] := MyRec.String2;
MyOleVariant[2] := MyRec.Integer1;

Of course, in the other end you must do this the other way around in
order to unpack it. Also be aware that if you change the record
structure, you must adapt the packing/unpacking code (best to put
those into two separate functions too, for clarity's sake).

However, if there is any simpler way of doing this, I'd be delighted
to know - I had to do this for a *very* complex structure, and it was
a long and boring work.

Santiago.-

"Howard Moon" wrote:
>(snip)


> How can I create this OleVariant on the server side and then get it as a

Santiago Cimadevilla

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to

Of course, Deborah's solution is much better than mine - I was
thinking of situations where the records contain references to objects
and stuff like that (that's what I had to do in my case).

Cheers,
Santiago.-

"Deborah Pate" wrote:
>[a better solution than mine]


Howard Moon

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to
Thanks for the responses, Deborah and Santiago. Deborah's method was
exactly what I was looking for (but couldn't find anywhere). But even
better, I found that Delphi5 now has an update pack that fixes the bug in
putting records directly into the type library so that I can return the
record witohut using variants! Thanks again...
-Howard

Santiago Cimadevilla

unread,
Feb 21, 2000, 3:00:00 AM2/21/00
to

That sounds great! Can you pass any kind of record that way?
Unfortunately, I'm stuck with D4, but hopefully not for long.

Santiago.-

Howard Moon

unread,
Feb 22, 2000, 3:00:00 AM2/22/00
to
In the type library editor, you can add records, and then add fields to
them. The fields can be any of the types available as parameters for com
objects. I have not tried it, but I imagine I can nest records inside of
records. What I can't do is pass Delphi objects directly. I can pass
records which include BSTR (WideString) members, which is great...much
better than making an array out of my record, eh?
One thing I find a little strange is the c-style listing of types in the
editor. For example, if I make a record called CustomerRecord, and want it
to be an out parameter, then I need to select the type CustomerRecord*,
which is c-style for a pointer to a CustomerRecord. But in the Delphi code
that is generated, I only see CustomerRecord as the type, (just like a var
parameter), which caused me a little confusion at first.

Santiago Cimadevilla <cimad...@arsinfo.nl> wrote in message
news:38b18151...@forums.inprise.com...

Dan Miser (TeamB)

unread,
Feb 22, 2000, 3:00:00 AM2/22/00
to
Go to the Tools | Environment Options dialog and select Pascal language from
the Type Library page.
--
Dan Miser
www.distribucon.com

Howard Moon <hm...@landstar.com> wrote in message
news:88treg$4k...@bornews.borland.com...

Ed Hillmann

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
It is possible to return an array of records through an automation
interface. However, you'll need to do some leg work to do it. Fortunately,
all the work is on the server side.

MicroSoft refers to this as passing an array of User-Defined Types (UDT's).
A very good idea is to go to MSDN online and read up on UDT's and how to use
them.

This is also (relatively) new functionality from MS. I think you need to
have the DCOM Patch 1.3 for 95/98, or the NT Service Pack 3. Again, you can
confirm these on MSDN.

The basic idea is that you can create a SafeArray of a new type, VT_RECORD.
When you create a SafeArray of this type, you need to pass it an instance of
a new COM Object, IRecordInfo. Fortunately, you don't have to populate it
yourself. You do need to define the interface, though, as you need to hold
on to the reference for a bit.

To do this, I needed to:
1) Provide a definition of IRecordInfo
2) Define a VCL hook into the SafeArrayCreateEx function in oleaut32.dll
3) Define a VCL hook into the GetRecordInfoFromTypeInfo in oleaut32.dll
4) Define a Delphi function, VarArrayCreateEx, which copies the existing
VarArrayCreate, but uses SafeArrayCreateEx
5) Define a Delphi function, GetStructRecordInfo, that retrieves an
IRecordInfo instance from TypeLib details.

Don't worry, it's not as scary as it seems. I'll put some code into
this.... the following code block is in my prototype's implementation unit
for the main automation object

(*CODE BLOCK START *)

const
IID_BOOKStr: TGUID = '{716C8172-2598-11D3-8560-004005E138A5}';
VT_Record = 36;

type
IRecordInfo = interface(IUnknown)
['{0000002F-0000-0000-C000-000000000046}']
function GetField(Data: pointer; FieldName: WideString; Field:
OleVariant):
HResult; stdcall;
function GetFieldNames(NumberOfNames: integer; NameList: OleVariant):
HResult; stdcall;
function GetFieldNoCopy(const Data: pointer; const FieldName:
WideString;
Field: OleVariant; DataArray: pointer): HResult; stdcall;
function GetGUID(var GUID: TGUID): HResult; stdcall;
function GetName(var Name: WideString): HResult; stdcall;
function GetSize(var Size: Integer): HResult; stdcall;
function GetTypeInfo(var TypeInfo: ITypeInfo): HResult; stdcall;
function IsMatchingType(const RecordInfo: IRecordInfo): HResult;
stdcall;
function PutField(const Flags: integer; Data: pointer;
const FieldName: WideString; const Field: OleVariant): HResult;
stdcall;
function PutFieldNoCopy(const Flags: integer; Data: pointer;
const FieldName: WideString; const Field: OleVariant): HResult;
stdcall;
function RecordClear(Existing: pointer): HResult; stdcall;
function RecordCopy(Existing: pointer; var New: pointer): HResult;
stdcall;
function RecordCreate: Pointer; stdcall;
function RecordCreateCopy(Source: pointer; var Dest: pointer):
HResult; stdcall;
function RecordDestroy(RecordToDelete: pointer): HResult; stdcall;
function RecordInit(NewRecord: pointer): HResult; stdcall;
end;

function SafeArrayCreateEx(VarType, DimCount: Integer;
const Bounds; RecordDetails: IRecordInfo): PVarArray; stdcall;
external 'oleaut32.dll' name 'SafeArrayCreateEx';

function GetRecordInfoFromTypeInfo(const TypeInfo: ITypeInfo;
out RecordInfo: IRecordInfo): HResult; stdcall;
external 'oleaut32.dll' name 'GetRecordInfoFromTypeInfo';

function VarArrayCreateEx(const Bounds: array of Integer;
VarType: Integer; RecordInfo: IRecordInfo): Variant;
var
I, DimCount: Integer;
VarArrayRef: PVarArray;
VarBounds: array[0..63] of TVarArrayBound;
begin
if not Odd(High(Bounds)) or (High(Bounds) > 127) then
raise Exception.Create('Exception raised in VarArrayCreateEx');
DimCount := (High(Bounds) + 1) div 2;
for I := 0 to DimCount - 1 do
with VarBounds[I] do
begin
LowBound := Bounds[I * 2];
ElementCount := Bounds[I * 2 + 1] - LowBound + 1;
end;

VarArrayRef := SafeArrayCreateEx(VarType, DimCount, VarBounds,
RecordInfo);
if VarArrayRef = nil then
raise Exception.Create('Exception raised in VarArrayCreateEx');
VarClear(Result);
TVarData(Result).VType := VarType or varArray;
TVarData(Result).VArray := VarArrayRef;
end;

function GetStructRecordInfo(IID_Struct, IID_Library: TGUID;
VerMajor: Integer = 1; VerMinor: Integer = 0; Lcid: Integer = 0):
IRecordInfo;
var
res: HResult;
vTypeLib: ITypeLib;
vTypeInfo: ITypeInfo;
begin
res := LoadRegTypeLib(IID_Library, 1, 0, 0, vTypeLib);
if (res = S_OK) then begin
try
{We've found the ITypelib interface for the library. Now get the
type info of the Book Structure}
res := vTypeLib.GetTypeInfoOfGuid(IID_Struct, vTypeInfo);
if (res = S_OK) then begin
{We've got the Type Information. Use it to get the IRecordInfo.}
res := GetRecordInfoFromTypeInfo(vTypeInfo, result);
vTypeInfo := nil;
end;
finally
vTypeLib := nil;
end;
end;
end;

(*CODE BLOCK END *)

The above code block is fairly stock standard stuff. Now, to use it, I'll
include the implementation function for one of my interface function calls,
which asks for an array of books.... where BOOK is the name of a record I
have defined in my type library...

function TMyDelphiLibrarySvr.ListOfBooks: OleVariant;
var
vRecordInfo: IRecordInfo;
p: pointer;
i: integer;
begin
{Note, I had to define my own IID_BOOKStr, as at the time the _TLB file
didn't define a constant
for records.}
vRecordInfo := GetStructRecordInfo(IID_BOOKStr, LIBID_DPassStructSvr);
try
if (vRecordInfo <> nil) then begin
result := VarArrayCreateEx([0,FBookList.Count - 1], VT_RECORD,
vRecordInfo);
p := VarArrayLock(result);

for i := 0 to FBookList.Count - 1 do begin
PopulateBookFromInternalList(TBookArray(p)[i], i);
end;

VarArrayUnlock(result);
end;
finally
vRecordInfo := nil;
end;
end;

procedure TMyDelphiLibrarySvr.PopulateBookFromInternalList
(var pBookStr: BOOK; pIndex: integer);
var
BookFound: TInternalBookDetails;
begin
if ((pIndex > -1) and (pIndex < FBookList.Count)) then begin
BookFound := TBookDetailObject(FBookList.Items[pIndex]).BookDetails;

pBookStr.Title := BookFound.Title;
pBookStr.Authors := BookFound.Authors;
pBookStr.Price := BookFound.Price;
end;
end;


So, the OleVariant returned from ListofBooks is an array of UDT's. For the
client to get at these,
they simply treat it as any other VarArray... My client test stub looks
like this...

type
TBookArray = array of Book;

procedure TForm1.btnBookListClick(Sender: TObject);
var
BookList: OLEVariant;
vBook: BOOK;
iDimCount, iLow, iHigh, i, iBookLocs: integer;
begin
BookList := FMyLib.ListOfBooks;

iDimCount := VarArrayDimCount(BookList);
iLow := VarArrayLowBound(BookList, iDimCount);
iHigh := VarArrayHighBound(BookList, iDimCount);

p := VarArrayLock(BookList);
try
for i := iLow to iHigh do begin
vBook := TBookArray(p)[i];
{Use vBook here}
end;
finally
VarArrayUnlock(BookList);
end;
end;

The only tricky bit was to define TBookArray, so we can typecast the pointer
returned to us by VarArrayLock.

Hope this helps!

Ed
Howard Moon wrote in message <88rihg$2u...@bornews.borland.com>...


>Hi...
> I have a server Com object hitting a database and returning the fields as
>OleVariants. I want to return all the fields as one record, passing it as
>an OleVariant (which appears to be the only way to use an out parameter in
a
>Com object).

> How can I create this OleVariant on the server side and then get it as a
>record again on the client side? It's easy with string or
integers...Delphi
>wraps and unwraps them for me. But this does not seem to be the case for
>records.

Deborah Pate

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
<<Ed Hillmann:

It is possible to return an array of records through an automation
interface. However, you'll need to do some leg work to do it.
>>

Sorry if I've missed the point of your message, but I don't think
it needs quite as much leg work as this! Delphi deals with
the SafeArray stuff for you using variants, so you can pass
arrays of records using exactly the same technique as I showed
further up the thread for one record - you just declare a
record array type:
TRecArray = array [1..100] of TRec;
PRecArray = ^TRecArray;
and then treat these exactly the way I showed with TRec, PRec.

--
Deborah Pate

Ed Hillmann

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
The method I did was using a record defined in the type library, and catered
for the case when the OleVariant in the interface wanted to return an array
of record structures. This has been, at least in my experierience, for
marked performance improvements (rather than getting each value marshalled
individually).

If the OleVariant is only to represent a single record structure, you don't
even have to copy it into an array of Byte. You still define the record in
the type library and assign the return value (or the out parameter) to be of
the type of the record. Then the marshalling is handled for you.

I'm sure that your solution will work, but it's major drawback is that it's
assuming that the client is a Delphi app that can reuse the record
definition (unless you've tested this with a Delphi Server and a VB or C
client ... then I'm wrong :) ). While what I suggested takes more legwork,
it may be a necessity that you're building an automation server that could
be used by another development environment. Part and parcel of COM. By
using MS' method of passing arrays of UDT's, you can guarantee that anyone
can get the data, regardless of the client's development environment.

Ed

Deborah Pate wrote in message <38b3a664@dnews>...
><<Ed Hillmann:


>It is possible to return an array of records through an automation
>interface. However, you'll need to do some leg work to do it.
>>>
>

0 new messages