I'm working on c++ language bindings for our game engine (made with Delphi XE). How would I translate the array of OleVariant and array of const params to work properly in C++ side?
function DLM_CallRoutine(const aFullname: PWideChar;
const aParamList: array of OleVariant): OleVariant; stdcall;
function DLM_CreateObject(const aClassName: PWideChar;
const aParamList: array of const): Integer; stdcall;
Thanks.
Delphi has two completely separate array semantics that both use the same array of code syntax for different purposes.
When array of is used to declare a data type or variable, a Dynamic Array is being used, eg:
type
TIntegerArray: array of Integer;
var
Array1: TIntegerArray;
Array2: array of Integer;
In C++, these correspond to the DynamicArray<T> template class, eg:
typedef DynamicArray<int> TIntegerArray;
TIntegerArray Array1;
DynamicArray<int> Array2;
On the other hand, when array of is used in a function parameter directly (ie, without using a typedef), then an Open Array is being used instead, ie:
procedure DoSomething(Arr: array of Integer);
procedure DoSomethingElse(Arr: array of const);
Values passed to an Open Array parameter are passed by the compiler using two separate parameters - a pointer to the actual array, and the index of the last element in the array. Delphi hides this fact so the coder only sees one parameter, and provides a simple syntax for specifying the parameter values:
DoSomething([12345, 67890]);
DoSomethingElse(['Hello', 12345, True]);
In C++, however, the two parameters used for the array are explicitally declared, and values are typically specified using the OPENARRAY() and ARRAYOFCONST() macros, eg:
// despite their names, the Size parameters are actually indexes.
// This misnaming has already been slated to be fixed in a future
// C++Builder release...
void __fastcall DoSomething(int const *Arr, const int Arr_Size);
void __fastcall DoSomethingElse(TVarRec const *Arr, const int Arr_Size);
DoSomething(OPENARRAY(int, (( 12345, 67890 )) );
DoSomethingElse(ARRAYOFCONST(( "Hello", 12345, true )) );
When creating code with interfaces to other languages it is wise to avoid Delphi specific types and conventions. Where you know your code will be interfacing with Delphi code, you may choose to provide Delphi-friendly interfaces, and "wrappers" to map Delphi-friendly types and mechanisms onto more portable ones for those other languages.
So....
Array of OLEVariant
Since you are passing an array of variants, clearly your C++ code is variant aware/capable, in which case I would pass these values in a variant array themselves (passed as a Variant itself), preserving the dynamic nature of the array, but eliminating any concerns over Delphi RTL specific "magic" types (dynamic arrays).
So you may have a Fn(array of OLEVariant) for your Delphi code, which internally re-packages the array into a Variant Array of Variant before passing the call on to the actual API code (psuedo-code):
Fn(array of OLEVariant)
begin
arr := VarArrayCreate(...);
try
// ... init arr from array of OLEVariant parameter
// call actual API fn:
APIFn(arr);
finally
// Dispose of variant array
end;
end;
Array of Const
The values end up being passed in an array of TVarRec (not directly connected to variants, tho with a similar intention). Again this is a "magic" Delphi type - you will need to research the TVarRec type and map it to some equivalent C++ type.
In this case I would determine exactly what you need to pass in the params list and adopt a mechanism that is more portable between the two languages. Perhaps a simple string containing a name/value pair delimited string of parameter names and values?
In this case, providing a Delphi friendly wrapper around the API call would involve a slightly different approach from that which you are currently using (array of const), given that array of const does not provide for named entries in the array. Or you might choose simply to provide a delimited set of type/values, encoded in a string, in which case you could continue to use array of const for the Delphi side, with a wrapper function that processes the TVarRec array to format a string approriately for the API function.
If I remember correctly, dynamic arrays in Delphi store the size of the array in the first few bytes. You would have to declare your interface function as taking a pointer, plus a size:
function DLM_CallRoutine(const aFullname: PWideChar;
aParamList: POleVariant; numParams :integer): OleVariant; stdcall;
Callers would have to pass the address of the first actual element and the number of elements, #A[0] and Length(A) in Delphi.
Beware: There are memmory management/garbage collection issues wherever OleVariant and other OLE types are involved. You have to make sure that reference counts are incremented and decremented appropiately.
Delphi Dynarrays
This is correctly documented somewhere in the Delphi help, and can probably be learned from looking at the source code of System.pas and SysUtils.pas. What follows is from memory (sorry in advance for being of such little help).
A Delphi dynarray is a record:
type
TDynArray = record
refcount :integer;
size: :integer;
content :array[size*elementSize] of byte;
end;
#A[0] is equivalent is the same as #content. To get to the address that includes the refcount you'd have to do some casting.
Having C call Delphi
You could have the clients written in C manipulate the structures for Delphi dynarrays, But why impose Delphi implementation semantics on them? If C is calling into your code, then by all means use the C way of doing it, which is:
A pointer to an array of structures.
A number-of-records parameter.
The obligation of your API to copy what was passed as parameters so the caller can free what it likes right after the call. The C code owns what it passes in parameters.
The API exposed to C can be easily implemented with calls to the existing API.
I hope that helps.
Related
I have a C based DLL that exports a function that has char*** as an argument, this is a pointer to a pointer to a pointer of char (Non-unicode) My question is, after much frustration, what is the equivalent declaration at the Delphi end?
I've tried for example:
// C Method declaration
void method (char*** arg)
TArrayOfPAnsiChar = array of PAnsiChar;
PArrayOfPAnsiChar = ^TArrayOfPAnsiChar;
PPArrayOfPAnsiChar = ^PArrayOfPAnsiChar;
// Delphi declaration
procedure method (var p : PPArrayOfPAnsiChar); cdecl;
p : PPArrayOfAnsiChar;
method (p)
But I'm not getting anything sensible back. Any suggestions? The var in the declaration is of course already a pointer so perhaps that's where I am going wrong. I've tried many variants, written down on paper what the structure is and reverse engineered a Delphi declration but to no avail. This DLL is used in other C based applications so I assume its functioning correctly.
Using XE6
Don't use array of, because that declares a dynamic array, and use AnsiChar rather than Char, since the latter is two bytes in Delphi.
PPAnsiChar = ^PAnsiChar;
PPPAnsiChar = ^PPAnsiChar;
procedure Method(Arg: PPPAnsiChar); cdecl;
or
procedure Method(var Arg: PPAnsiChar); cdecl;
If you want to index into the variable like an array, add {$POINTERMATH ON} before declaring the types
In this case char*** is a pointer to a pointer that points to an array where each element points to a string.
This implies that the callee is returning data to the caller. So you want to use:
procedure method(out Arg: PPAnsiChar); cdecl; external dllname;
where PPAnsiChar is ^PAnsiChar. Note that PPAnsiChar is defined in the System unit.
The C code has three levels of indirection. The outermost is how function passses data from callee to caller. That's represented as a Delphi out parameter. The remaining two levels of indirection are captured in PPAnsiChar. Finally C's char is an 8 bit type on Windows and so maps to AnsiChar.
You will also need to iterate over the array when the function returns. Like this:
var
StrArr: PPAnsiChar;
Str: string;
....
method(StrArr);
while StrArr^ <> nil do
begin
Str := StrArr^;
Inc(StrArr);
end;
I'm assuming that the length of the array is indicated by a terminating null pointer. If instead you are told the length, you use a for loop as should be obvious.
I am Delphi 6 developer. And trying to call C++ dll function from delphi.
Dll written in C++ and the have Char pointer as out parameter.
Eg.
Function doGetStatus(Var szStatusData : Char) : Integer; cdecl; external 'WhizCal.dll';
When I call function from DLL I send char variable 'szStatusData' and return value should be 'Down' or 'Live' but it retuens only 'D' or 'L' this is because I have passed Char variable reference.
My question is, what can I do for getting return data as 'Down' or 'Live' without changing datatype.
A char is just a single character. So you cannot expect more.
Most likely the C++ code expects to be passed a pointer to an array of characters. Perhaps it should be like this:
function doGetStatus(szStatusData: PAnsiChar): Integer; cdecl; external 'WhizCal.dll';
And you might call it like this:
var
StatusData: array [0..255] of AnsiChar;
RetVal: Integer;
....
RetVal := doGetStatus(StatusData);
// check RetVal
We cannot tell you what exactly your code should be because we don't have any details of the specified interface. Unless the caller also passes the length of the array, such a function is bait for buffer overrun errors. As written above, this is the case. How can the caller know how much space to allocate. Well designed functions also allow the caller to pass the length of the array that they supplied.
My general advice to you is that interop is a topic that demands precision and detail, and knowledge of both sides of the boundary. You need to find out that detail.
I want to write a procedure that appends to an array of Integer, but Delphi IDE gives me compile-time error 'Incompatyble types'. This is my code :
procedure appendToIntegerArray(var intArr : array of Integer; valueToAppend : Integer);
var
counter : Integer;
begin
counter := Length(intArr);
SetLength(intArr, counter + 1); // This is where it gives me the compile-time error
intArr[counter] := valueToAppend;
end;
Can anyone help me fix my procedure ?
You are facing the error because SetLength operates on dynamic arrays and that is not a dynamic array. That is an open array parameter.
You'll need to use a dynamic array instead:
procedure appendToIntegerArray(var intArr: TArray<Integer>; valueToAppend: Integer);
....
If you use an older Delphi that does not support generic arrays, you'll need to declare a type for the array:
type
TIntegerArray = array of Integer;
procedure appendToIntegerArray(var intArr: TIntegerArray; valueToAppend: Integer);
....
Or you could use the type declared in the RTL, TIntegerDynArray. This type is declared in the Types unit.
One of the irritations of Delphi's type system is that a type like TIntegerArray is not assignment compatible with, say, TIntegerDynArray because they are distinct types. That makes it harder than it should be to work with code that uses distinct array types. One of the great benefits of generic arrays is that the rules for type compatibility of generics are a little more forgiving. So, if you can use TArray<T>, do so.
If you are appending item by item, then I would generally suggest that a list class would be better. In this case you would use TList<Integer> and simply call its Add method.
It's one of the quirks of Delphi's syntax: declaring an array of whatever as a function parameter does not define it as an array like you might think, but as an open array, a "magic" type that can accept any array of the correct base type. If you want to use a specific type of array, you need a named type.
This is the sort of thing that the generic TArray<T> was designed for. Make your parameter a TArray<integer> if you can. If not, consider updating to a newer version of Delphi (you're missing out on a whole lot if you're still on an older version!) and in the meantime, declare an array type like so:
type
TMyIntegerArray = array of integer;
And use that type as your parameter type, rather than declaring array of integer there.
Just a little question, I'm not finding a specific answer so i guessed it might be faster to ask here.
The compiler rejects the code below with the following error:
incompatible types 'dynamic array' and 'array of string'
TMailInfo = record
FileName,
MailAdresse,
MailBCC,
MailCC,
MailBetreff: string;
MailText,
Anhang: array of string;
MailAcknowledge,
MailTXT: Boolean
end;
class function TEMail.SendOutlookCOMMail(aFileName, aMailAdresse,
aMailBCC, aMailCC, aMailBetreff: string;
aMailText, aAnhang: array of string;
const aMailAcknowledge, aMailTXT: Boolean): Boolean;
var
mailInfo: TMailInfo;
begin
...
mailInfo.MailBetreff := aMailBetreff; // these two lines cause the error
mailInfo.MailText := aMailText;
...
end;
What am I doing wrong? Both are arrays of string, so I don't get why one seems to be dynamic.
You cannot readily assign to MailText and Anhang because you cannot declare another object with a compatible type. That's because you used a dynamic array type inline in your record declaration. You really need to use a type that can be named. To illustrate a bit better, consider this:
X: array of Integer;
Y: array of Integer;
Now X and Y are of different types and X := Y does not compile.
The other problem is your open array parameter. An open array parameter is assignment compatible with nothing. You can copy element by element, but you cannot assign the array in one go.
The best way out of this is to declare the field like this:
MailText,
Anhang: TArray<string>;
And change the open array parameters in the function to also be of that type.
Then you need to decide whether you want to copy the reference or the array:
MailText := aMailText; // copy reference, or
MailText := Copy(aMailText); // copy array
You have two problems. First, as David says, two declarations of a type that look the same, but are declared separately, make them different, assignment incompatible types.
Weirdly enough, this is not so for generic types, like TArray<string>, so it makes sense to use that, if you version of Delphi supports it.
But the second problem is that you are confusing an open array parameter like aMailText with a dynamic array like mailInfo.MailText. aMailText, the parameter, is not necessarily a dynamic array at all, it can be any kind of array.
Take a look at the documentation: Open Arrays
Another explanation: Open array parameters and array of const
In Pascal there are two kinds of type declarations:
type aliases: type NewName = OldType
type creation: type NewType = type OldType
The former is just creating convenient shorthand, like typedef in C. The aliases are compatible one to another and to their original type. The created types are intentionally incompatible and cannot be mixed without explicit and unsafe by definition typecast.
var
nn: NewName; nt: NewType; ot: OldType;
...
nn := ot; // should work
nt := ot; // should break with type safety violation error.
nt := NewType(ot); // Disabling type safety. Should work even if
// it has no sense semantically and types really ARE incompatible.
Those are Pascal basics as i understand them.
Now let's look at one certain type and two its aliases:
System.Types.TStringDynArray = array of string;
System.TArray<T> = array of T;
in particular that means TArray<string> = array of string; by definition.
Now let's take function returning the former type alias and feed its result to the function expecting the latter one:
uses Classes, IOUtils;
TStringList.Create.AddStrings(
TDirectory.GetFiles('c:\', '*.dll') );
TStringList.Create.AddStrings(
TArray<string>( // this is required by compiler - but why ???
TDirectory.GetFiles('c:\', '*.dll') ) );
1st snippet would not compile due to types violation.
2nd one happily compiles and works, but is fragile towards future type changes and is redundant.
QC tells that compiler is right and the RTL design is wrong.
http://qc.embarcadero.com/wc/qcmain.aspx?d=106246
WHY compiler is right here ?
Why those aliases are incompatible ?
Even the very manner RTL was designed suggests that they were deemed compatible!
PS. David suggested even simplier example, without using TArray<T>
type T1 = array of string; T2 = array of string;
procedure TForm1.FormCreate(Sender: TObject);
function Generator: T1;
begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
procedure Consumer (const data: T2);
begin
with TStringList.Create do
try
AddStrings(data);
Self.Caption := CommaText;
finally
Free;
end;
end;
begin
Consumer(Generator);
end;
Same gotcha without explanation...
PPS. There are a number of doc refs now. I want to stress one thing: while this restriction might be indirectly inherited from Pascal Report of 1949, today is 2012 and Delphi used very differently from school labs of half-century ago.
I named few BAD effects of keeping this restrictions, and yet did not saw any good one.
Ironic thing, that this restricion may be lifted without breaking rules of Pascal: in Pascal there is no such non-strict beast as Open Arrays and Dynamic Arrays. So let those original fixed arrays be restricted as they wish, but Open Arrays and Dynamic Arrays are not Pascal citizens and are not obliged to be limited by its codebook!
Please, communicate Emba in QC or maybe even here, but if u just pass by without expressing your opinion - nothing would change!
The key to understanding this issue is the Type Compatibility and Identity topic in the language guide. I suggest you have a good read of that topic.
It is also helpful to simplify the example. The inclusion of generics in the example serves mainly to complicate and confuse matters.
program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}
type
TInteger1 = Integer;
TInteger2 = Integer;
TArray1 = array of Integer;
TArray2 = array of Integer;
TArray3 = TArray1;
var
Integer1: TInteger1;
Integer2: TInteger2;
Array1: TArray1;
Array2: TArray2;
Array3: TArray3;
begin
Integer1 := Integer2; // no error here
Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
Array1 := Array3; // no error here
end.
From the documentation:
When one type identifier is declared using another type identifier, without qualification, they denote the same type.
This means that TInteger1 and TInteger2 are the same type, and are indeed the same type as Integer.
A little further on in the documentation is this:
Language constructions that function as type names denote a different type each time they occur.
The declarations of TArray1 and TArray2 fall into this category. And that means that these two identifiers denote different types.
Now we need to look at the section discussing compatibility. This gives a set of rules to follow to determine whether or not two types are compatible or assignment compatible. We can in fact shortcut that discussion by referring to another help topic: Structured Types, Array Types and Assignments which states clearly:
Arrays are assignment-compatible only if they are of the same type.
This makes it clear why the assignment Array1 := Array2 results in a compiler error.
Your code looked at passing parameters, but mine focused on assignment. The issues are the same because, as the Calling Procedures and Functions help topic explains:
When calling a routine, remember that:
expressions used to pass typed const and value parameters must be assignment-compatible with the corresponding formal parameters.
.......
Delphi is a strongly typed language. That means that identical (in this case I mean their definitions look exactly the same) types are not assignment compatible.
When you write array of <type> you are defining a type and not an alias. As David already said in his comment the two identical types like
type
T1 = array of string;
T2 = array of string;
are not assignment compatible.
Same goes for
type
TStringDynArray = array of string;
TArray<T> = array of string;
Often people forget about the incompatibility of identical types and my guess would be that they did when they introduced IOUtils for example. Theoretically the definition of TStringDynArray should have been changed to TStringDynArray = TArray<string> but I guess that could have raised other problems (not saying bugs with generics...).
I also had the same problem with Delphi, where I wanted to pass values from one identical array to another. Not only did I have "incompatibility" problems with two like array assignments, but I also could not use the "Copy()" procedure. To get around this problem, I found that I could use a pointer to an type array of array of string, instead.
For example:
type RecArry = array of array of string
end;
var TArryPtr : ^RecArry;
Now, I can pass the values from any fixed array to another identical array without any compatibility or function problems. For example:
TArryPtr := #RecArry.LstArray //This works!
TArryPtr := #LstArray //This also works!
With this created array assignment template, I can now work with all two dimensional arrays without any problems. However, it should be understood, that when accessing this type of string array pointer, an extra element is created, so that when we would expect this type of array 2D array below, for example:
Two_Dimensional_Fixed_Array[10][0]
We now get an extra element adjusted array as seen here:
New_Two_Dimensional_Fixed_Array[10][1]
This means that we have to use some slightly tricky code to access the pointer array, because all the populated elements in Two_Dimensional_Fixed_Array[10][0] have moved down, so that they are offset by 1, as in New_Two_Dimensional_Fixed_Array[10][1].
Therefore where we would normally find the value 'X' in Two_Dimensional_Fixed_Array[1][0], it will now be found here in TArryPtr[0][1].
Its a trade off we all have to live with!
Another important note to bear in mind is the definition of a pointer array when it is declared. When a pointer array is type declared, the Borland compiler will not allow the Pointer array to have the same element size as the array to which it is pointing too. For example, if an array is declared as:
Orig_Arry : array [1..50,1] of string;
The pointer array which should point to it would be declared in the following fashion:
Type Pntr_Arry : array [1..50,2] of string;
Did you notice the the extra element? I am guessing the the Borland compiler has to widen the array pointer to allow for the pointer address.