Passing a Delphi record type to VB6 DLL - delphi

VB6 DLL
I have written a VB6 DLL which includes the following user-defined type (UDT), consisting only of primitive data types...
Public Type MyResults
Result1 As Double
Result2 As Double
Result3 As Double
Result4 As Double
Result5 As Double
End Type
...and the following method...
Public Sub Method1(ByRef Results As Variant)
Dim myRes As MyResults
myRes = Results
MsgBox "MyResults.Result1: " & myRes.Result1
...
End Sub
Delphi Test Harness
I have also written a test harness in Delphi and have created an equivalent Delphi record type to mimic the VB UDT, which is defined as follows...
TMyResults = packed record
Result1: Double;
Result2: Double;
Result3: Double;
Result4: Double;
Result5: Double;
end;
From Delphi, I would like to call Method1 in the VB6 DLL and pass a variable of this type as an argument, so that VB can interpret it as an equivalent "type". So far, I have tried the following Delphi call...
procedure TMyApp.CallMethod1FromVBDLL(var MyResults: TMyResults);
var
results: OleVariant;
dll: Variant;
begin
results := RecToVariant(MyResults, SizeOf(MyResults));
dll := CreateOleObject('ApplicationName.ClassName');
dll.Method1(results);
...
end;
...which uses the following function (source: http://www.delphigroups.info/2/2c/462130.html) to convert a record to a variant...
function TMyApp.RecToVariant(var AR; ARecSize: Integer): Variant;
var
p: Pointer;
begin
Result := VarArrayCreate([1, ARecSize], varByte);
p := VarArrayLock(Result);
try
Move(AR, p^, ARecSize);
finally
VarArrayUnLock(Result);
end;
end;
An EOleException is reported back to Delphi due to a Type mismatch on the myRes = Results line in Method1 of the DLL.
Am I preparing and passing the argument correctly in Delphi?
How should I be using the argument in VB6?
Any assistance/advice would be gratefully received.

The problem is that your Delphi code is creating an OleVariant that contains a byte array (Automation type VT_UI1 or VT_ARRAY), which is not what VB is expecting. It is expecting a UDT record (Automation type VT_RECORD) instead.
MSDN has an article explaining how to pass a UDT inside a Variant:
Passing UDTs
To pass single UDTs or safearrays of UDTs for late binding, the Automation client must have the information necessary to store the type information of the UDT into a VARIANT (and if late-binding, the UDT must be self-described). A VARIANT of type VT_RECORD wraps a RecordInfo object, which contains the necessary information about the UDT and a pointer the UDT itself. The RecordInfo object implements a new interface, IRecordInfo, for access to the information. It is important to know that the actual storage of the UDT is never owned by the VARIANT; the IRecordInfo pointer is the only thing that is owned by the VARIANT.
The data for a UDT includes an IRecordInfo pointer to the description of the UDT, pRecInfo, and a pointer to the data, pvRecord.
In other words, you need to create a class that implements the IRecordInfo interface and wraps your actual record data, then you can store an instance of that class inside the OleVariant using the VT_RECORD type. That will provide both COM and VB with the necessary metadata to marshal and access your record data.

Related

Using ToString for Variant variables

The following code produces an EVariantInvalidOpError exception:
var
i : Variant;
begin
i := 10;
ShowMessage(i.ToString());
end;
All the following works good but I don't understand why the ToString function raises exception for Variant type variables:
var
i : Variant;
begin
i := 10;
ShowMessage(VarToStr(i));
end;
var
i : Integer;
begin
i := 10;
ShowMessage(i.ToString());
end;
Variants let you store values of various types in them, while the type may be unknown at compile-time. You can write an integer value into single variable of Variant type an later overwrite it with string value. Along with the value variant records stores also the type information in it. Among those values some of them are automatically allocated and/or reference counted. The compiler does a lot of stuff behind the scenes when writing or reading the value from Variant variable.
Variants of type varDispatch get even more special treat from the compiler. varDispatch indicates that the value is of type IDispatch (usually, but not necessarily related to Windows COM technology). Instance of IDispatch provides information about its methods and properties via GetTypeInfoCount and GetTypeInfo methods. You can use its GetIDsOfNames method to query the information by name.
Let's answer the question from your comment first:
Why does Delphi allow me to use the ToString function even if there is no helper implementing such function for the Variant type?
This is how Delphi implements concept called late binding. It allows you to call methods of an object which type is unknown at compile-time. The prerequisite for this to work is that the underlying variant type supports late binding. Delphi has built-in support for late binding of varDispatch and varUnknown variants as can be seen in procedure DispInvokeCore in unit System.Variants.
I don't understand why the ToString function raises exception for Variant type variables.
As discussed above, in run-time your program tries to invoke ToString method on variant value which in your case is of type varByte. Since it doesn't support late binding (as well as further ordinal variant types) you get the exception.
To convert variant value to string use VarToStr.
Here's a simple example of using late binding with Microsoft Speech API:
uses
Winapi.ActiveX,
System.Win.ComObj;
var
Voice: Variant;
begin
CoInitialize(nil);
try
Voice := CreateOleObject('SAPI.SpVoice');
Voice.Speak('Hello, World!');
finally
CoUninitialize;
end;
end.

How to get property of the 'record' type using TypInfo unit

I have this record type
TDoublePoint = record
X : Double;
Y : Double;
end;
then I have object with this property
uses ..TypInfo;
TCell = class(TPersistent)
private
FZoom : TDoublePoint
published
property Zoom : TDoublePoint read FZoom write FZoom;
end;
But when I want to get PropInfo of this property with this function:
function GetKind(AObject:TObject; Propertyname :shortstring):TTypeKind;
var p :ppropinfo;
begin
p:=GetPropInfo(AObject, Propertyname); // <p = nil
Result:= p^.proptype^.Kind;
end;
..
..
c := TCell.Create;
GetKind(c, 'Zoom'); // <- error
c.Free;
I get error, because variable p is nil in the function.
But Why?
There is tkRecord in the TTypeKind, so I expected no problems to read/write the property of record type, but it seems, it is not possible (?)
Google search did not tell much.
Delphi 7 does not generate RTTI for a record type by default, and so a published property that uses a record type will not have RTTI, either (you can use TypInfo.GetPropList() to confirm that).
At one point, this was a documented limitation:
Published properties are restricted to certain data types. Ordinal, string, class, interface, variant, and method-pointer types can be published.
However, there is a workaround. IF a record type contains any compiler-managed data types (long strings, interfaces, dynamic arrays, etc), then RTTI will be generated for that record type, as will any published property that uses that record type, and thus GetPropInfo() can find such properties (I have confirmed that this does work in Delphi 7).
Your TDoublePoint record does not contain any compiler-managed data types, so that is why GetPropInfo() is returning nil for your TCell.Zoom property.
That RTTI issue was fixed in a later version (not sure which one. I'm guessing maybe in Delphi 2010, when Extended RTTI was first introduced). For instance, the code you have shown works for me as-is in XE. GetPropInfo() can find the Zoom property as expected, without having to introduce any compiler-managed types into the TDoublePoint record type.

COM Exception "Bad Variable Type" Error in Delphi Call of Domino GetDocumentByKey Method

I have a legacy Delphi 2 application that I need to convert from communicating with Notes via OLE Automation to communicating via COM early binding. I am using Delphi 7 since the code base is large and I want to avoid the work of dealing with the Unicode support in the more current versions of Delphi.
The basics are working: the program opens the database then the view and searches for a particular document using the NotesView.GetDocumentByKey method. The GetDocumentByKey call works when the first parameter is a single string cast to an OleVariant as shown below (opening of DB and view not shown).
var
Key: OleVariant;
const ExactMatch: WordBool = True;
begin
Key := 'AKeyValue';
Doc := View.GetDocumentByKey(Key, ExactMatch);
The bad variable type error occurs when the first parameter is a variant array as required when it is desired to search the view based on multiple columns as shown below.
var
TwoKeysV: OleVariant;
const ExactMatch: WordBool = True;
begin
TwoKeysV := VarArrayCreate([0, 1], varOleStr);
TwoKeysV[0]:= WideString('Key1');
TwoKeysV[1]:= WideString('Key2');
Doc := View.GetDocumentByKey(TwoKeysV, ExactMatch);
I have tried several variations on the two key assignment statements with no success. For example, just assigning the key string without a cast still produces the bad variable type, and using the StringToOleString function is rejected by the compiler as an invalid assignment (PWideChar to Variant).
I can't test this, so I'm not sure this works.
HELP: If this method is used under COM, with a keyArray parameter of an array, it must be defined as an array of type Variant
So you need to pass: an array of type Variant
Based on How to use variant arrays in Delphi.
Note: Code edited by Keeloid to match code that worked by casting key string to WideString.
var
TwoKeysV: OleVariant;
const ExactMatch: WordBool = True;
begin
TwoKeysV := VarArrayCreate([0, 1], varVariant);
TwoKeysV[0]:= WideString('Key1'); {WideString = varOleStr}
TwoKeysV[1]:= WideString('Key2');
Doc := View.GetDocumentByKey(TwoKeysV, ExactMatch);

Delphi - How can I pass Generic parameter to function that accept Array of const parameter

I have a 'Base class' which contain a 'function' that accept parameter of type 'Array of const' as shown below:-
type
TBaseClass = class(TObject)
public
procedure NotifyAll(const AParams: array of const);
end;
procedure TBaseClass.NotifyAll(const AParams: array of const);
begin
// do something
end;
I have another 'Generic class' that is derived from the 'base class' ( defined above )
type
TEventMulticaster<T> = class(TBaseClass)
public
procedure Notify(AUser: T); reintroduce;
end;
procedure TEventMulticaster<T>.Notify(AUser: T);
begin
inherited NotifyAll([AUser]); ERROR HERE
end;
Every time I compile this code it gives error saying:
Bad argument type in variable type array constructor
What does it referring to be wrong?
You cannot pass a Generic argument as a variant open array parameter. The language Generics support simply does not cater for that.
What you can do instead is wrap the Generic argument in a variant type, for instance TValue. Now, you cannot pass TValue instances in a variant open array parameter either, but you can change NotifyAll() to accept an open array of TValue instead:
procedure NotifyAll(const AParams: array of TValue);
Once you have this in place, you can call it from your Generic method like so:
NotifyAll([TValue.From<T>(AUser)]);
Fundamentally, what you are attempting to do here is combine compile-time parameter variance (Generics) with run-time parameter variance. For the latter, there are various options. Variant open array parameters are one such option, but they do not play well with Generics. The alternative that I suggest here, TValue, does have good interop with Generics.
The System.Rtti unit has something exactly for you needs, but not widely known:
TValueArrayToArrayOfConst() and
ArrayOfConstToTValueArray()
So your implementation should be:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;
Notes:
TValueArrayToArrayOfConst()'s result is a non-owning container. It contains memory pointers backed by the source array of the TValue container. ParametersAsTValueArray is alive and not being altered while the array of const is used.
One of these 2 Rtti procedures has a bug with regard to TClass values processing. TClass becomes a Pointer on some stage, and string.Format() breaks because Pointer and TClass are not the same thing. Perform tests on all TVarRec.VType, they are not so many, much less that Variant's VType.

How to know what type is a var?

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?
No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.
With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.
Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Resources