C++ to Delphi dll call - c

I am trying to convert this code to delphi 7
LIBEXPORT int __stdcall MY_GetDLLInfo(const char **pp_version, const char **pp_release_type, const char **pp_build_date, const char **pp_load_path);
// C++ Call Example
const char *p_version = NULL;
const char *p_release_type = NULL;
const char *p_build_date = NULL;
MY_GetDLLInfo(&p_version,&p_release_type,&p_build_date,NULL);
Delphi Code
MY_GetDLLInfo: function (const pp_version:PPAnsiChar;const pp_release_type:PPAnsiChar;const pp_build_date:PPAnsiChar;const pp_load_path:PPAnsiChar): Integer; stdcall;
// Delphi Call Example
var
hHandle:THandle;
p_version,p_release_type,p_build_date,p_load_path:PPAnsiChar;
begin
hHandle := LoadLibrary(Dl_path);
#MY_GetDLLInfo:=GetProcAddress(hHandle, PChar('MY_GetDLLInfo'));
if Assigned(MY_GetDLLInfo) then begin
MY_GetDLLInfo(p_version,p_release_type,p_build_date,#null);
ShowMessage(StrPas(#p_version)); // <- I Get Strange Output
ShowMessage(StrPas(#p_release_type)); // <- I Get Strange Output
ShowMessage(StrPas(#p_build_date)); // <- I Get Strange Output
end;
end;
With my converted code i get strange output
I think i am doing wrong at using strpas ?

char* in C/C++ is PAnsiChar in Delphi, and char** is PPAnsiChar.
Your local string variables need to be declared as PAnsiChar, not as PPAnsiChar, and then you need to use the # address operator (Delphi's equivilent of C/C++'s & address operator) to pass the variables into the function.
Also, null is a Variant in Delphi. To assign a null to a pointer, you need to use nil instead.
Try this:
// Delphi Call Example
var
hHandle: THandle;
MY_GetDLLInfo: function(const pp_version, pp_release_type, pp_build_date, pp_load_path: PPAnsiChar): Integer; stdcall;
p_version, p_release_type, p_build_date: PAnsiChar;
begin
hHandle := LoadLibrary(Dl_path);
if hHandle <> 0 then
begin
#MY_GetDLLInfo := GetProcAddress(hHandle, 'MY_GetDLLInfo');
if Assigned(MY_GetDLLInfo) then
begin
p_version := nil;
p_release_type := nil;
p_build_date := nil;
MY_GetDLLInfo(#p_version, #p_release_type, #p_build_date, nil);
ShowMessage(p_version);
ShowMessage(p_release_type);
ShowMessage(p_build_date);
end;
FreeLibrary(hHandle);
end;
end;

Related

How get correct total number of elements of a PByteArray?

On following code i'm trying convert a binary file to your correspondent bytes representation in Delphi.
I'm with difficulty to get the correct total number of elements of array (declared as PByteArray) and this returns a limited number (32767) like already discussed here. Then exists a alternative way to achieve it?
This is code:
type
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function File2ByteArray(const FileName: string; out ByteArray: PByteArray): Boolean;
var
hFile: HWND;
dwSize, dwRead: DWORD;
begin
Result := False;
hFile := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hFile = INVALID_HANDLE_VALUE then
Exit;
dwSize := GetFileSize(hFile, nil);
ByteArray := AllocMem(dwSize);
ReadFile(hFile, ByteArray^, dwSize, dwRead, nil);
Result := True;
CloseHandle(hFile);
end;
procedure TForm1.btn1Click(Sender: TObject);
var
ByteArray: PByteArray;
MyList: TStringList;
I: Integer;
begin
if File2ByteArray(ParamStr(0), ByteArray) then
begin
try
MyList := TStringList.Create;
MyList.Add('Const');
MyList.Add(' aSize = ' + IntToStr(High(ByteArray^)) + ';');
MyList.Add('stub_: Array [' + IntToStr(Low(ByteArray^)) + ' .. ' + IntToStr(High(ByteArray^) - 1) + '] of Byte =');
MyList.Add('(');
MyList.Add('');
for I := Low(ByteArray^) to High(ByteArray^) - 1 do
MyList.Add('$' + IntToHex(ByteArray[I], 2) + ',');
MyList.Add(');');
MyList.SaveToFile('C:\File2ByteArray.txt');
finally
MyList.Free;
end;
end;
end;
end.
A pointer to an array allocated as you have done so, does not contain length information. You cannot query that pointer for the length of the array. Instead you need to modify File2ByteArray so that it returns that length, in addition to the pointer.
We can't see what type PByteArray is, but I would normally expect to see PByte used to represent a pointer to an array of bytes.
It's also worth noting that your code leaks the array. I would resolve that, and resolve the issue of length, by returning a TBytes dynamic array instead of a manually allocated array.

"Operator is not overloaded" when checking for a byte in an array of bytes

I have an array[0..2] of byte. I need to check if a byte is in that array or not. However, when using if ($52 in byteArray) then, I run into the error "Operator is not overloaded". I've tried setting an additional variable as a byte and then using that in the statement, but still get the error. Here's an incredibly simple program showing this:
program overloaded;
var
byteArray: array[0..2] of Byte;
begin
byteArray[0] := $00;
byteArray[1] := $69;
byteArray[2] := $52;
if ($52 in byteArray) then
writeLn('We will not get to this point');
end.
This will fail to compile with the above error in FPC 3.0.2.
You have two alternatives, overloading the operator or using for.
program ByteArrayIteration;
var
ByteArray: array[0..2] of Byte = ($00, $69, $52);
SomeByte : Byte = $52;
begin
for SomeByte in byteArray do
if SomeByte = $52 then
begin
WriteLn('We will get to this point');
Break;
end;
end.
And the overload alternative:
program OverloadInOperator;
uses SysUtils;
operator in(const A: Byte; const B: TBytes): Boolean;
var
i: Integer;
begin
Result := True;
for i := Low(B) to High(B) do if A = B[i] then Exit;
Result := False;
end;
var
Bytes : TBytes;
AByte : Byte = $52;
begin
Bytes := TBytes.Create($41, $52, $90);
if AByte in Bytes then WriteLn('Done');
end.
If your use case is limited to 255 items per array, consider using sets instead.
program SetOfByte;
var
Bytes : set of Byte = [$41, $52, $90];
AByte : Byte = $52;
begin
if AByte in Bytes then WriteLn('Done');
end.

how to use tencoding's getstring with a part of static array w/o block copying

I'm trying to apply Tencoding.UTF8.Getstring on a part of a static bytes array without copying its content to a dynamic array. If the array is dynamic, i have no problem using:
stringvar:=Tencoding.utf8.Getstring(dynbytearray,offset,length);
however, when i have a static predefined length array, the syntax doesn't work and all i could figure is to declare a new dynamic array, set its length, and copy the bytes. I don't like doing the needless copying since i suspect i just miss a syntax trick. My attempts like "setlength(newdynarr,whatever); newdynarr:=#staticarr [optional offset]" failed so far. Thanks.
The public TEncoding.GetString() method only supports dynamic arrays, but you can use the protected PByte overloads of TEncoding.GetCharCount() and TEncoding.GetChars() instead, eg:
type
TEncodingHelper = class(TEncoding)
public
function GetString(Bytes: PByte; ByteCount: Integer): String;
end;
function TEncodingHelper.GetString(Bytes: PByte; ByteCount: Integer): String;
begin
SetLength(Result, GetCharCount(Bytes, ByteCount));
GetChars(Bytes, ByteCount, PChar(Result), Length(Result));
end;
var
S: string;
begin
S := TEncodingHelper(TEncoding.UTF8).GetString(PByte(#arr[index]), ByteCount);
end;
Or:
type
TEncodingHelper = class helper for TEncoding
public
function GetString(Bytes: PByte; ByteCount: Integer): String;
end;
function TEncodingHelper.GetString(Bytes: PByte; ByteCount: Integer): String;
begin
SetLength(Result, Self.GetCharCount(Bytes, ByteCount));
Self.GetChars(Bytes, ByteCount, PChar(Result), Length(Result));
end;
var
S: string;
begin
S := TEncoding.UTF8.GetString(PByte(#arr[index]), ByteCount);
end;
You can use System.UnicodeFromLocaleChars. For instance like this:
uses
SysUtils, SysConst, Windows;
function Utf8BytesToString(Bytes: PByte; ByteCount: Integer): string;
var
Len: Integer;
begin
Len := UnicodeFromLocaleChars(CP_UTF8, MB_ERR_INVALID_CHARS, Pointer(Bytes),
ByteCount, nil, 0);
if (ByteCount>0) and (Len=0) then begin
raise EEncodingError.CreateRes(#SNoMappingForUnicodeCharacter);
end;
SetLength(Result, Len);
UnicodeFromLocaleChars(CP_UTF8, MB_ERR_INVALID_CHARS, Pointer(Bytes),
ByteCount, Pointer(Result), Len);
end;
The System.UnicodeFromLocaleChars function wraps MultiByteToWideChar on Windows and UnicodeFromLocaleChars on POSIX systems. The TEncoding class makes use of System.UnicodeFromLocaleChars to perform its conversions. Should you wish to travel in the opposite direction there is System.LocaleCharsFromUnicode.

How do I speedup the copy of a generic array?

I have a simple array:
Myarray<T> = array[0..100] of T;
How do I copy this as fast as possible into another array.
Note that the source is always a plain generic array and the dest can be a dynamic array. (but I doubt that matters).
I tried
move(Source[0], dest[0], count * sizeof(T));
But that does not play nice with managed types.
I'm hoping to get something faster than:
for i:= 0 to count -1 do D[i]:= S[i];
What options do I have?
Background
The array is part of a BTree structure and there is a lot of copying going on.
I need the speed.
I asked the same question on Google+ - https://plus.google.com/u/0/116430453567926016001/posts/b1u1shzkmrW
The code is by Stefan Glienke and I have re-posted the code here with to minor changes to two variable types, so it is 64 bit compatible.
uses
TypInfo,
SysUtils;
type
TArray = record
class function Clone<T>(const source: T): T; static;
end;
function DynArrayLength(const A: Pointer): NativeInt;
type
PDynArrayRec = ^TDynArrayRec;
TDynArrayRec = packed record
{$IFDEF CPUX64}
_Padding: LongInt;
{$ENDIF}
RefCnt: LongInt;
Length: NativeInt;
end;
begin
Result := 0;
if A <> nil then
Result := PDynArrayRec(PByte(A) - SizeOf(TDynArrayRec))^.Length;
end;
{$POINTERMATH ON}
type
PArray = ^Pointer;
{$POINTERMATH OFF}
procedure CopyArrayDeep(Dest, Source: PArray; TypeInfo: Pointer; Count: NativeInt);
var
typeData: PTypeData;
elType: PPTypeInfo;
i: Integer;
len : NativeInt;
begin
if (Source = nil) or (Count = 0) then
Exit;
typeData := GetTypeData(TypeInfo);
elType := typeData.elType;
if (elType <> nil) and (elType^.kind = tkDynArray) then
begin
for i := 0 to Count-1 do
begin
len := DynArrayLength(Source[i]);
DynArraySetLength(Dest[i], elType^, 1,#len);
CopyArrayDeep(Dest[i], Source[i], elType^, len);
end;
end
else
if elType <> nil then
CopyArray(Dest, Source, elType^, Count)
else
Move(Source^, Dest^, Count * typeData.elSize);
end;
class function TArray.Clone<T>(const source: T): T;
var
p: PTypeInfo;
count: NativeInt;
i: Integer;
begin
p := TypeInfo(T);
if p.Kind <> tkDynArray then
raise ENotSupportedException.Create('type not supported');
count := DynArrayLength(PArray(#source)^);
DynArraySetLength(PArray(#Result)^, p, 1,#count);
CopyArrayDeep(PArray(#Result)^, PArray(#source)^, p, count);
end;

Convert JIntArray to Array of Object

function Java_com_erm_controller_ARMReports_S35(PEnv: PJNIEnv; Obj: JObject; ex_UserRowID, ex_BSID : Integer; ex_RevalDate : JString;
ex_AFS, ex_HTM, ex_HFT : Boolean;
ex_IsMcCaulay_PNL: Boolean;
ex_Maturity, ex_Scale : JIntArray
): Integer; stdcall; export;
var objRpt : TARMReports;
I : Integer;
Len : JInt; //just a renamed delphi integer
aMaturity:array of Integer;
aScale:array of Integer;
begin
DLLErrorLog('CASH -S35');
objRpt := TARMReports.Create; JVM := TJNIEnv.Create(PEnv); ex_RevalDate_J := JVM.JStringToString(ex_RevalDate);
Len:=PEnv^.GetArrayLength(PEnv, ex_Maturity);
SetLength(aMaturity, Len);
Len:=PEnv^.GetArrayLength(PEnv, ex_Scale);
SetLength(aScale, Len);
DLLErrorLog('ex_Maturity Length'+ intToStr(Len));
for I := 0 to Len-1 do
begin
PEnv^.GetIntArrayRegion(PEnv, ex_Maturity, I, Len, #aMaturity[I]);
DLLErrorLog('ex_Maturity '+ IntToStr(aMaturity[I]));
PEnv^.GetIntArrayRegion(PEnv, ex_Scale, I, Len, #aScale[I]);
DLLErrorLog('ex_Scale '+ IntToStr(aScale[I]));
end;
Result := objRpt.S35(ex_UserRowID, ex_BSID, ex_RevalDate_J,
ex_AFS, ex_HTM, ex_HFT ,
ex_IsMcCaulay_PNL,
aMaturity, aScale
);
DLLErrorLog('CASH2 Ends -S35');
JVM.Free; objRpt.Free;
end;
Need to convert ex_Maturity, ex_Scale to objects to Delphi's Array of Integer.
Now while calling from Java it throws java.lang.ArrayIndexOutOfBoundsException
While printing in Log array values are getting . Please suggest us to work for me.
There are a couple of ways, depending on what, exactly your JIntArray is.
Firstly, if its an array of int (as in the primitive java type) then a get the length of the array via JNI, allocate a delphi array of integers and then get JNI to copy the data from the java array
Uses
AndroidAPI.JNI;
Var
Len:JNIInt; //just a renamed delphi integer
aMaturity:array of integer;
begin
Len:=PEnv^.GetArrayLength(PEnv, ex_Maturity);
//allocate the receiving array
SetLength(aMaturity, Len);
//now get the array data - note we are passing the address of the first element
//not the address of the array itself!
PEnv^.GetIntArrayRegion(PEnv, ex_Maturity, 0, Len, #aMaturity[0]);
//do stuff
end;
If you are dealing with an array of Integer (thats the Java class "Integer") then you need to get the array of objects from JNI one element at a time and use TJNIResolver to get the raw value;
Uses
AndroidAPI.JNI, AndroidAPI.JNIBridge;
Var
Len:JNIInt; //just a renamed delphi integer
Count:Integer;
Current:JNIObject;
CurrentValue:integer;
aMaturity:array of integer;
begin
Len:=PEnv^.GetArrayLength(PEnv, ex_Maturity);
//allocate the receiving array
SetLength(aMaturity, Len);
For Count:=0 to Len-1 do
begin
Current:=PEnv^.GetObjectArrayElement(PEnv, ex_Maturity, Count);
if assigned(Current) then
begin
CurrentValue:=TJNIResolver.GetRawValueFromJInteger(Current);
//Yes, you can inline this but the point is, here you do stuff with
//the element
aMaturity[Count]:=CurrentValue;
end;
end;
end;
Obviously the first method is much faster as crossing the JNI barrier is slow and you are only doing it once, whereas with the array of Java Integers you are doing it multiple times for each element.
You should also watch out for errors - I'm not checking for Java exceptions at any point which could crash and burn your app if you don't deal with them.
Edit : The OP has ready my answer and tried to work with it, which is nice. They have gotten a out of bounds exception in their code.
function Java_com_erm_controller_ARMReports_S35(PEnv: PJNIEnv; Obj: JObject; ex_UserRowID, ex_BSID : Integer; ex_RevalDate : JString;
ex_AFS, ex_HTM, ex_HFT : Boolean;
ex_IsMcCaulay_PNL: Boolean;
ex_Maturity, ex_Scale : JIntArray
): Integer; stdcall; export;
var objRpt : TARMReports;
I : Integer;
Len : JInt; //just a renamed delphi integer
aMaturity:array of Integer;
aScale:array of Integer;
begin
DLLErrorLog('CASH -S35');
objRpt := TARMReports.Create; JVM := TJNIEnv.Create(PEnv); ex_RevalDate_J := JVM.JStringToString(ex_RevalDate);
//you only have 1 length defined and possibly different array lengths
//process arrays seperately
Len:=PEnv^.GetArrayLength(PEnv, ex_Maturity);
SetLength(aMaturity, Len);
DLLErrorLog('ex_Maturity Length'+ intToStr(Len));
//only call this once, also watch the parameters you are passing in
PEnv^.GetIntArrayRegion(PEnv, ex_Maturity, 0, Len, #aMaturity[0]);
Len:=PEnv^.GetArrayLength(PEnv, ex_Scale);
SetLength(aScale, Len);
DLLErrorLog('ex_Scale Length'+ intToStr(Len));
PEnv^.GetIntArrayRegion(PEnv, ex_Scale, 0, Len, #aScale[0]);
Result := objRpt.S35(ex_UserRowID, ex_BSID, ex_RevalDate_J,
ex_AFS, ex_HTM, ex_HFT ,
ex_IsMcCaulay_PNL,
aMaturity, aScale
);
DLLErrorLog('CASH2 Ends -S35');
JVM.Free; objRpt.Free;
end;
What you were doing was getting the length twice, setting the delphi arrays correctly but then looping over them both in the same loop not taking into account that they could be different lengths. Your call to getinarrayregion was also passing the complete length in for aScale on the second parameter for both calls - if you really want to get each one in a loop like that then you need to pass the count and a length of 1 to only return 1 element - this is most likely what was causing the exception.
If you want to report the contents then create a procedure to do it, rather than using a loop inside your current procedure, you would have to copy and paste the loop to do it otherwise which is, franky bad coding practice and we don't want that now do we?
Sarcasm on
Not that expecting someone who has tried to help you to correct the your code rather than actually understanding the problem is any better, but ho hum.
Sarcasm off

Resources