How to translate C++ char** to Delphi? - delphi

I have a C++ DLL:
int __stdcall Hello(int numFiles, char **inFiles)
and I don't know how to translate this char **.
I tried:
function Hello(numFiles: Integer; InFiles: PPChar): Integer; stdcall; external 'dll2.dll';
and then:
Files: array of PChar;
begin
SetLength(Files, 2);
Files[0] := PChar('siema.jpg');
Files[1] := PChar('siema2.jpg');
c := Hello(2, #Files[0]);
but all I get is "Access violation"

On the face of it, given the information in the question, your code seems basically fine. However, a rookie mistake, made by many beginners with interop, is to believe that a function's signature is enough to define the interface. It is not. For instance, a parameter of type char** could be used for many different things. So, to specify the information you must also specify the semantics of the parameters, not just the syntax.
Given
int __stdcall Hello(int numFiles, char **inFiles)
let us assume the following:
The return value is an error code, with 0 indicating success.
The first parameter is the number of files.
The second parameter is an array of files, of length specified by the first parameter. Each element of the array is a C string, a pointer to a null-terminated array of 8 bit characters.
With those assumptions in place, I would write the code like this:
function Hello(numFiles: Integer; InFiles: PPAnsiChar): Integer; stdcall;
external 'dll2.dll';
The function would be called like this:
var
Files: array of PAnsiChar;
retval: Integer;
....
SetLength(Files, 2);
Files[0] := PAnsiChar('siema.jpg');
Files[1] := PAnsiChar('siema2.jpg');
retval := Hello(Length(Files), PPAnsiChar(Files));
if retval <> 0 then
... handle error
You might prefer to write the final parameter as #Files[0] if you prefer. I prefer the cast because it allows me to pass an empty array even when range checking is enabled.
Note that I used PAnsiChar to match with char*. The PChar type is an alias to either PAnsiChar or PWideChar depending on the Delphi version. It is better to be explicit.
Of course, my assumptions may be wrong. You should confirm them by consulting the library's documentation.

Related

What is the Delphi declaration for char***

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.

Unable to read memory address of variable that pass to function as call by reference

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.

Delphi XE Create structured array using MCR for use in Matlab Compiled dll

I'm using Delphi XE and the Matlab 2012B compiler on Windows 7.
I'm trying to write several wrapper functions so DLL files created with the Matlab 2012b Compiler can be more easily called from Delphi XE. I found that I should use the _proxy functions when using the MCR, which indeed allowed me to call several functions successfully. I can also pass strings to Matlab without problems by passing them as PAnsiChar.
I'm currently trying to create a StructArray with some field names.
As I've already successfully created numeric arrays and matrices, I'm pretty sure the first 2 parameters are OK. I expect the last one is causing the error, but I don't know how to solve this (yet). Looking at the Matlab help and example files I'm doing what should be done. Obviously I'm wrong...
I know that with Matlab r13 we had to pass the fieldnames as an array[0..n] of pAnsiChar instead of an array of pAnsiChar. I tried this here as well to no avail.
Can someone tell me if I have indeed made the correct function mapping to mxCreateStructArray(_730_proxy) and if I'm passing the parameters as expected?
type
mxArray = pointer;
// mxArray *mxCreateStructArray(mwSize ndim, const mwSize *dims, int nfields, const char **fieldnames);
function MCRdll_CreateStructArray(aDimCount: integer; aDims: pointer; aFieldCount: integer; aFields: PPAnsiChar): mxArray; cdecl; external 'mclmcrrt8_0.dll' name 'mxCreateStructArray_730_proxy';
function MCR_CreateStructArray(aFieldNames: TArray<string>): mxArray;
var
i: integer;
lstDims: array of integer;
lstNames: array of pAnsiChar;
begin
SetLength(lstNames, Length(aFieldNames));
for i := 0 to Length(aFieldNames) - 1 do
lstNames[i] := ToPAnsiChar(aFieldNames[i]); //Creates a new PAnsiChar with the content of aFieldNames[i]
SetLength(lstDims, 2);
lstDims[0] := 1;
lstDims[1] := Length(aFieldNames);
//This call raises an "External Exception" from Matlab.
Result := MCRdll_CreateStructArray(Length(lstDims), #lstDims, Length(lstNames), #lstNames);
end;
The MATLAB C API function is:
mxArray *mxCreateStructArray(mwSize ndim, const mwSize *dims,
int nfields, const char **fieldnames);
As I understand it, mwSize is by default the same as int. That translates to Integer in Delphi. The const char** parameter is the address of an array of const C strings. Translate that to Delphi and you have:
function MCRdll_CreateStructArray(ndim: Integer; dims: PInteger;
nFields: Integer; fieldnames: PPAnsiChar): mxArray; cdecl;
external 'mclmcrrt8_0.dll' name 'mxCreateStructArray_730_proxy';
Now, how to get the parameters. Well, assuming you want a vector, dims is an array of length 2, and ndim is that length. I'd declare that as a static array:
var
dims: array [0..1] of Integer;
As for the field names, those are variable length. So you need a dynamic array of PAnsiChar. That is:
var
fieldnames: array of PAnsiChar;
You also need to pass the vector length for your struct array to your function. That makes your function be something like this:
function MCR_CreateStructArray(len: Integer;
const aFieldNames: array of AnsiString): mxArray;
var
i: integer;
dims: array [0..1] of Integer;
fieldnames: array of PAnsiChar;
begin
if Length(aFieldNames)=0 then
begin
Result := nil;
exit;
end;
dims[0] := 1;
dims[1] := len;
SetLength(fieldnames, Length(aFieldNames));
for i := 0 to high(fieldnames) do
fieldnames[i] := PAnsiChar(aFieldNames[i]);
Result := MCRdll_CreateStructArray(Length(dims), #lstDims[0],
Length(fieldnames), #fieldnames[0]);
end;
An alternative to the final parameter is to pass PPAnsiChar(fieldnames). That works because a dynamic array variable is the address of the first element.
So, what was wrong with your version? The biggest mistake you made was to use untyped pointers for the two arrays that you pass to MCRdll_CreateStructArray. This means that the compiler cannot check that you got the indirection correct. And you did not.
First of all in your code you pass #lstDims to the second parameter. Now lstDims is a dynamic array in your code. The implementation of that has lstDims being a pointer to the first element. So, informally, lstDims has type ^Integer. And therefore #lstDims has type ^^Integer. That's one level of indirection too far. And you made the exact same mistake in the final parameter.
One final point. I've change the signature of the function to receive an array of AnsiString. That's the easy way for me to write the code because I don't need to worry about the UTF-16 to ANSI conversion, and can use a simple PAnsiChar cast. You'd probably benefit from this helper:
function ToAnsiStringArray(const arr: array of string): TArray<AnsiString>;
var
i: Integer;
begin
SetLength(Result, Length(arr));
for i := 0 to high(Result) do
Result[i] := AnsiString(arr[i]);
end;
I've not compiled any of this so there may be some imprecision. I trust you'll not be put off by that.

Assign String to Array of Characters

Question One
I have
var example : array[0..15] of char;
I want to assign the value from an input to that variable
example := inputbox('Enter Name', 'Name', '');
In the highscores unit I have record and array
type
points = record
var
_MemoryName : array[0..15] of char;
_MemoryScore : integer;
end;
var
rank : array[1..3] of points;
var s: string;
a: packed array[0..15] of char;
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a)) ;
returns -> (186): E2010 Incompatible types: 'array[0..15] of Char' and 'PWideChar'
var s: string;
a: packed array[0..15] of char;
s := InputBox('caption', 'Caption', 'Caption');
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(a) * sizeof(char));
scores.rank[1]._MemoryName := <<tried both s and a>> ;
returns (189): E2008 Incompatible types
Question One
There are many ways. One is:
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
a: packed array[0..15] of char;
begin
s := InputBox(Caption, Caption, Caption);
assert(length(s) <= 16);
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(s) * sizeof(char));
end;
But there might be a more elegant solution to your original problem, I suspect.
Question Two
Every time you wish a function/procedure didn't have a particular argument, you should realize that there might be a problem with the design of the project. Nevertheless, it isn't uncommon that Sender parameters are superfluous, because they are almost omnipresent because of the design of the VCL (in particular, the TNotifyEvent). If you know that the receiving procedure doesn't care about the Sender parameter, simply give it anything, like Self or nil.
Question Three
Consider this code:
procedure TForm4.FormCreate(Sender: TObject);
var
a: packed array[0..15] of char;
b: packed array[0..15] of char;
begin
a := b;
end;
This doesn't work. You cannot treat arrays like strings; in particular, you cannot assign static arrays like this (a := b).
Instead, you have to do something like...
Move(b[0], a[0], length(a) * sizeof(char));
...or simply loop and copy one value at a time. But the above simple assignment (a := b) does work if you declare a static array type:
type
TChrArr = packed array[0..15] of char;
procedure TForm4.FormCreate(Sender: TObject);
var
a: TChrArr;
b: TChrArr;
begin
b := a;
end;
Andreas has you covered for question 1.
Question 2
I would arrange that your event handler called another method:
procedure TForm5.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
RespondToEditControlKeyPress;
end;
That way you can just call RespondToEditControlKeyPress directly.
I'd guess that you want to call it with no parameters because you want code to run when the edit control's text is modified. You could perhaps use the OnChange event instead. And it may be that OnChange is more appropriate because pressing a key is not the only way to get text into an edit control.
By the way, it's better to ask one question at a time here on Stack Overflow.
For a quick way to copy string-type values into array-of-character type values. I suggest a small helper function like this:
procedure StrToCharArray( inputStr:String; var output; maxlen:Integer);
type
ArrayChar = Array[0..1] of Char;
begin
StrLCopy( PChar(#ArrayChar(output)[0]),PChar(inputStr),maxlen);
end;
Each time you call it, pass in the maximum length to be copied. Remember that if the buffer length is 15, you should pass in 14 as the maxlen, so that you leave room for the terminating nul character, if you intend to always terminate your strings:
StrToCharArray( UserInputStr, MyRecord.MyField, 14 );
This function will ensure that the data you copy into the record is null terminated, assuming that's what you wanted. Remember that in a fixed length character array it's up to you to decide what the rules are. Null terminated? Fully padded with spaces or null characters.... Strings and arrays-of-characters are so different, that there exist multiple possible ways of converting between the two.
If you don't intend to terminate your strings with nul, then you should use the FillChar+Move combination shown in someone else's answer.
The obvious answer is of course.
Don't use a packed array of char.
Use a string instead.
If you use ansistring, 1 char will always take 1 byte.
If you use shortstring ditto.
Ansistring is compatible with Pchar which is a pointer to a packed array of char.
So you can write
function inputbox(a,b,c: ansistring): pchar;
begin
Result:= a+b+c;
end;
var s: ansistring;
begin
s:= inputbox('a','b','c');
end;
Some advice
It looks like your are translating code from c to Delphi.
a packed array of char is exactly the same as the old (1995) shortstring minus the length byte at the beginning of shortstring.
The only reason I can think of to use packed array of char is when you are reading data to and from disk, and you have legacy code that you don't want to change.
I would keep the legacy code to read and write from disk and then transfer the data into an ansistring and from there on only use ansistring.
It's soooooooo much easier, Delphi does everything for you.
And... ansistring is much faster, gets automatically created and destroyed, can have any length (up to 2GB), uses less memory --because identical strings only get stored once (which means stringa:= stringb where a string is 20 chars is at least 5x faster using ansistrings than array's of char).
And of course best of all, buffer overflow errors are impossible with ansistring.
What about unicodestring?
Unicodestring is fine to use, but sometimes translation of chars happens when converting between packed array of char and unicodestring, therefore I recommend using ansistring in this context.
What you try to do is impossible, indeed:
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a));
That tries to assign a pointer (the result of StrPLCopy, a PWideChar in the last few versions of Delphi) to an array, which is indeed impossible. You can't copy an array like that. I would do:
StrLCopy(highscoresdata.position[1]._MemoryName, PChar(s),
Length(highscoresdata.position[1]._MemoryName));
That should work, and is IMO the simplest solution to copy a string to an array of characters. There is no need to use a as some kind of intermediate, and using Move is, IMO, rather low level and therefore a little tricky (it is easy to forget to multiply by the size of a character, it is unchecked, it does not add a #0, etc.), especially if you don't know what exactly you are doing.
This solution should even work for versions of Delphi before Delphi 2009, as it does not rely on the size of the character.
FWIW, I would not use packed arrays. Packed doesn't have a meaning in current Delphi, but could confuse the compiler and make the types incompatible.

Delphi compiler error E2064 left side cannot be assigned to

I inherited a Delphi application and I know nothing about object pascal.
It's a BPL that I need to compile into the new version of C++ Builder XE.
When I run a make I get the error:
E2064 left side cannot be assigned to.
I've learned enough obj pascal to know I have a constant that is trying to be assigned a value.
But, apparently, you can over ride this behanvior; essentially turning constants into vars by going into Build options under the Delphi compiler and turning on "Assignable Typed constants".
I did that and I continue to get the same error.
I tried surrounding my code with {$J+} and {$J-} and still it will not compile.
procedure TChunkIDAT.CopyInterlacedRGB8(const Pass: Byte;
Src, Dest, Trans{$IFDEF Store16bits}, Extra{$ENDIF}: pChar );
var
Col: Integer;
begin
{Get first column and enter in loop}
Col := ColumnStart[Pass];
Dest := pChar(Longint(Dest) + Col * 3);
repeat
{Copy this row}
Byte(Dest^) := fOwner.GammaTable[pByte(Longint(Src) + 2)^]; inc(Dest);
Get the error on last line. If I change the const to a var, I then get the error that the declaration differs from the previous declaration but I have no idea where the previous declaration is....
You're type-casting a two-byte thing (Char) into a one-byte thing (Byte). Reading that value is easy to define, but making that value writable is tricky, probably for the same reason the types of formal and actual "var" parameters need to be identical.
Maybe you wanted to type-cast it to a two-byte thing, such as Word. Or maybe you want GammaTable to be an array of Char so you don't have to type-cast at all. Or maybe, if this code was originally written for a Delphi version earlier than 2009, you want those PChar declarations to be PAnsiChar — character types have gotten wider. Another option is to type-cast Dest to PByte, and then dereference the result. That's probably a bad idea, though, because you'll only be overwriting every other byte of the buffer.
Based on the name of the function, it sounds like PChar was never the right data type to use. That type is for character data, but I think this code is dealing with bytes. The correct thing to do is probably to change PChar to PByte, and then you don't need to type-cast Dest at all.
The $J directive is irrelevant; it controls whether the compiler will allow you to assign values to typed constants. You don't have any of those in this code.
The reason is that as of Delphi 2009, Char, PChar, and String are Unicode, and store more than one byte per character.
You should not cast those pointers to bytes, and the compiler prevents you from assigning them if you cast the left side of an assignment to a byte.
This compiles:
procedure CopyInterlacedRGB8(const Pass: Byte; Dest: pAnsiChar); overload;
begin
Byte(Dest^) := Pass;
end;
This doesn't:
procedure CopyInterlacedRGB8(const Pass: Byte; Dest: pChar); overload;
begin
Byte(Dest^) := Pass;
end;
Instead of pChar, you should use pByte, which makes the code simpler:
procedure CopyInterlacedRGB8(const Pass: Byte; Dest: PByte); overload;
begin
Dest^ := Pass;
end;
--jeroen
That looks like you're working with Gustavo Daud's TPngImage library. You don't need that code in an external BPL because it's been included in the RTL since D2009. Remove that unit from the BPL and you should be able to get at the updated version via the PngImage unit.

Resources