Reputation: 61
I have Delphi 2006 client application. This client recieves an Olevariant type data from COM server. The function is:
procedure OnLimitsChanged(var SymbolsChanged: {??PSafeArray}OleVariant);
This function returns an array of string. I can´t read OleVariant type data from delphi.
From Excel VBA it´s working:
Private Sub g_Realtime_OnLimitsChanged(SymbolsChanged() As String)
Dim i%
Dim Salir As Boolean
If UBound(SymbolsChanged) <> -1 Then
i = 0: Salir = False
While Not Salir
If SymbolsChanged(i) = Simbolo Then
LlamarALimites
Salir = True
Else
i = i + 1
If i > UBound(SymbolsChanged) Then Salir = True
End If
Wend
End If
End Sub
I tried to convert OleVariant to Psafearray...
procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject;
var ArrayTicks : Olevariant);
var
Data : pSafeArray;
i,iLow, iHigh : Integer;
value : wideString;
begin
Data:=PSafeArray(TVarData(ArrayTicks).VArray);
SafeArrayGetLBound(Data,1,iLow);
SafeArrayGetUBound(Data,1,iHigh);
for i:=iLow to iHigh do
begin
SafeArrayGetElement(Data,i,Value);
Showmessage(Value);
end;
But I recieve an except in this line:
SafeArrayGetLBound(Data,1,iLow);
Debugger Fault Notification
Project ... faulted with message: ' access violation at 0x751de18c: read of address 0xabababab'. Process Stopper. Use Step or Run to continue
Any advice and suggestions will be greatly appreciated.
Upvotes: 6
Views: 7273
Reputation: 256581
The RTL has the function let access a Varant
array as a SAFEARRAY
:
function VarArrayAsPSafeArray(const V: Variant): PSafeArray;
I wanted to document how to do the reverse.
In Delphi a Variant
is an opaque blob. But internally it is really the TVarData
structure (aka the Windows VARIANT
structure). A variant can hold different types of data. You indicate which type through the VType
member. The value of the VType
member tells you how to interpret the rest of the structure:
a 32-bit Integer (VT_I4
)
VType: Word = VT_I4;
//3VInteger: Integer;
a IUnknown interface (VT_UNKNOWN
)
VType: Word = VT_UNKNOWN;
//13VUnknown: Pointer;
//actually IUnknownan BSTR (aka WideString in Delphi)
VType: Word = VT_BSTR;
//8VOleStr: PWideChar;
In the case that the variant is a SAFEARRAY of 32-bit integers:
VType: Word = (VT_ARRAY or VT_I4);
VArray: PVarArray;
And then VArray
points to a SAFEARRAY
strucuture:
VType: Word = (VT_ARRAY or VT_I4);
VArray: PVarArray;
cDims: Word;
fFeatures: Word;
cbElements: LongWord;
cLocks: LongWord;
pvData: Pointer;
rgsabound: array[0..0] of TSafeArrayBound;
There are times, particularly when interacting with COM or .NET that you:
PSafeArray
, PSafeArray
.You can construct a SafeArray
easily enough, if you use Delphi's functions to create a variant array. Delphi does the heavy lifting to creating the underlying SafeArray that your "variant array" actually is.
But we want to go the other way; we are given a PSafeArray
, and we want to wrap it up inside a Delphi Variant variable, so that it handles all the ugliness and lifetime.
assemblies: PSafeArray;
assemblies := DefaultAppDomain.GetAssemblies;
How can we deal with this pointer to a SAFEARRAY
?
function PSafeArrayToVariant(psa: PSafeArray): OleVariant;
begin
TVarData(v).VType = (VT_ARRAY or VT_xxxx);
TVarData(v).VArray := PVarArray(psa);
end;
except we need to know what the SafeArray contains; we need to fill in the VT_xxxx in the above code.
Fortunately, one of the members of the SAFEARRAY structure tells what VType the members of the array are:
fFeatures: Word;
VT_BSTR
)VT_UNKNOWN
)VT_DISPATCH
)VT_VARIANT
)function SafeArrayGetVartype(psa: PSafeArray): TVarType; safecall; external 'OleAut32.dll';
function PSafeArrayToVariant(psa: PSafeArray): OleVariant;
var
features: Word;
vt: TVarType;
const
FADF_HAVEVARTYPE = $80;
begin
features := psa^.fFeatures;
if (features and FADF_UNKNOWN) = FADF_UNKNOWN then
vt := VT_UNKNOWN
else if (features and FADF_DISPATCH) = FADF_DISPATCH then
vt := VT_DISPATCH
else if (features and FADF_VARIANT) = FADF_VARIANT then
vt := VT_VARIANT
else if (features and FADF_BSTR) <> 0 then
vt := VT_BSTR
else if (features and FADF_HAVEVARTYPE) <> 0 then
vt := SafeArrayGetVartype(psa)
else
vt := VT_UI4; //assume 4 bytes of *something*
TVarData(Result).VType := VT_ARRAY or vt;
TVarData(Result).VArray := PVarArray(psa);
end;
Upvotes: 3
Reputation: 595295
The RTL has a VarArrayAsPSafeArray()
function for extracting a PSafeArray
correctly from an (Ole)Variant
:
procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : OleVariant);
var
Data : PVarArray; // RTL's version of PSafeArray
//...
begin
Data := VarArrayAsPSafeArray(ArrayTicks);
//...
end;
If the (Ole)Variant
does not contain an array, an exception will be raised. Or you can use VarIsArray()
to check it manually:
procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : OleVariant);
var
Data : PVarArray;
//...
begin
if not VarIsArray(ArrayTicks) then Exit;
Data := VarArrayAsPSafeArray(ArrayTicks);
//...
end;
That being said, (Ole)Variant
has build-in support for accessing PSafeArray
element data, so you don't really need to resort to accessing PSafeArray
directly (unless you want an extra performance boost, in which case you need to validate the PSafeArray
yourself before you access its data):
procedure TfmConfiguracion.RecibirNuevosTicks(ASender: TObject; var ArrayTicks : Olevariant);
var
i : Integer;
value : String;
begin
if not VarIsArray(ArrayTicks) then Exit;
for i := VarArrayLowBound(ArrayTicks, 1) to VarArrayHighBound(ArrayTicks, 1) do
begin
Value := ArrayTicks[i];
ShowMessage(Value);
end;
Upvotes: 5