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
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
Cheers,
Santiago.-
"Deborah Pate" wrote:
>[a better solution than mine]
Santiago.-
Santiago Cimadevilla <cimad...@arsinfo.nl> wrote in message
news:38b18151...@forums.inprise.com...
Howard Moon <hm...@landstar.com> wrote in message
news:88treg$4k...@bornews.borland.com...
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.
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
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.
>>>
>