Convert JIntArray to Array of Object - arrays

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

Related

Strange file output when using Concat in CodeSys

I'm using the CAA-File library in CodeSyS to write data to a text file. When I write a string to the file it works fine, but if I combine 2 strings with CONCAT, I get a strange file output with random characters (see below). Does anyone know how to fix this? Thank you!
For example lString = 'test' works fine, but lString = CONCAT('str1', 'str2') does not.
PROGRAM initDataLogger
VAR_INPUT
lString : STRING;
END_VAR
VAR
lString : STRING:= CONCAT('str1', 'str2');
xFileStdInit: BOOL:=FALSE;
uiFileStdState: UINT:=0;
sFileName: CAA.FILENAME:= '/var/www/html/TestFile.txt';
hFile: CAA.HANDLE;
szFileSize1: CAA.SIZE := 0;
szFileSize2: CAA.SIZE := 0;
filop: FILE.Open;
filwr: FILE.Write;
filcl: FILE.Close;
loop: INT := 0;
fString: STRING;
END_VAR
IF NOT xFileStdInit THEN
filop(xExecute:=FALSE);
filcl(xExecute:=FALSE);
filwr(xExecute:=FALSE);
xFileStdInit:=TRUE;
uiFileStdState:=0;
ELSE
CASE uiFileStdState OF
0: (* create a new file *)
filop.sFileName:=sFileName;
filop.eFileMode:=FILE.MODE.MAPPD;
filop.xExclusive:=TRUE;
filop( xExecute:=TRUE);
IF filop.xDone THEN
hFile:=filop.hFile;
uiFileStdState:=1;
END_IF
IF filop.xError THEN
(* error handling*)
;
END_IF
1:(* write text in the file *)
//fString := CONCAT('$R$L', lString);
fString := lString;
filwr.hFile:=hFile;
filwr.pBuffer:=ADR(fString);
szFileSize1:=SIZEOF(fString);
filwr.szSize:=szFileSize1;
filwr.udiTimeOut:=100000; (* 100ms Timeout *)
IF GVL.logData THEN
filwr( xExecute:=TRUE);
END_IF
IF filwr.xDone THEN
uiFileStdState:=2;
GVL.logData := FALSE;
END_IF
IF filwr.xError THEN
(* error handling*)
;
END_IF
2: (* close file - TestFile.txt *)
filcl.hFile:=hFile;
filcl( xExecute:=TRUE);
IF filcl.xDone THEN
uiFileStdState:=3;
END_IF
IF filcl.xError THEN
(* error handling*)
;
END_IF
3: (* end of example *)
xFileStdInit := FALSE;
END_CASE
END_IF
For lString = CONCAT('str1', 'str2') I expect str1str2 as output but I get this:
str1str2sys??v$??v??v?
?vd?n??
?v?`v??vd
?vK
?v?`v8
?v?v4??v?
You shouldn't both pass lString as an input and at the same time declare it as a variable with an initial value. The compiler should refuse to build the program in that form, so either your current program is a little different than your example above, or your compiler is not doing as it should and all bets are off.
Also, I note that your program will probably stay in case 1 for more than one scan, while the file is written. Are you sure the lString input is valid or the expected value while this goes on? Can you single-step the program to confirm that?
You might want to divide first and second part of case 1 into 2 case steps or change the first part to something à la the following:
IF NOT filwr.xExecute THEN
//fString := CONCAT('$R$L', lString);
fString := lString;
filwr.hFile:=hFile;
filwr.pBuffer:=ADR(fString);
szFileSize1:=SIZEOF(fString);
filwr.szSize:=szFileSize1;
filwr.udiTimeOut:=100000; (* 100ms Timeout *)
END_IF
I found it out myself. It had to do with the string size. For example: STRING(4) outputs fine with a four letter word, but with a two letter word it adds two random character's.

"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.

Cx programmer-Structured text, Array assignement

i'm learning structured text, to program with Cx-programmer, an Omron software.
i' ve looked around but i can't find a way to assign multiple element to an array,
i've tried this soluion, but it doesn't work,
this is Arrays declaration(internals variables):
Name Data type Initial value
SenCheck_Array BOOL[8] FALSE
SEN INT[2] 0
Array of INT:
SEN[1...2]:=[1,2];
Array of BOOL:
SenCheck_Array[0...7] := [ S_LF,S_LS,S_LH2O,S_LO,S_Col ,S_BAR,S_TAP,S_ET ] ;
The error is the same:
ERROR: Missing ]
i succeded in assigning element singularly, but i need to assign them in a single line.
Any help is apreciated:)
P.S: i'm using cx programmer educational edition.
Edit: This example (showing the declaration part of the SCL block code) is only valid for Siemens PLCs.
To initialize an array the values must be separated by a comma without square brackets:
CONST
// Constants
S_LF := TRUE;
S_LS := FALSE;
S_LH2O := FALSE;
S_LO := FALSE;
S_Col := TRUE;
S_BAR := TRUE;
S_TAP := TRUE;
S_ET := TRUE;
END_CONST
VAR
// Static Variables
SEN: ARRAY[1..2] OF INT := 1, 2;
SenCheck_Array: ARRAY[0..7] OF BOOL := S_LF, S_LS, S_LH2O, S_LO, S_Col , S_BAR, S_TAP, S_ET;
END_VAR

Ada aspects which are private to a package

Let's say I have the stupidest ring buffer in the world.
size: constant := 16;
subtype T is integer;
package RingBuffer is
procedure Push(value: T);
function Pop return T;
end;
package body RingBuffer is
buffer: array(0..size) of T;
readptr: integer := 0;
writeptr: integer := 1;
procedure Push(value: T) begin
buffer(writeptr) := value;
writeptr := (writeptr + 1) mod size;
end;
function Pop return T
begin
readptr := (readptr + 1) mod size;
return buffer(readptr);
end;
end;
Because my code sucks, I want to add preconditions and postconditions to make I don't misuse this sure. So I change the implementation of Push as follows:
procedure Push(value: T) with
pre => readptr /= writeptr
is begin
buffer(writeptr) := value;
writeptr := (writeptr + 1) mod size;
end;
However, I get a compile error because I need to put the aspect definitions in the declaration of the procedure, not in the implementation.
The thing is, this is a package. My declaration is public. The values that the precondition is depending on belong to the package body, which isn't visible to the declaration. In order to put the aspect definition in the declaration I'm going to have to refactor my code to expose implementation details into the public part of the package (in this case, readptr and writeptr). And I don't want to do that.
I can think of a few ways round this, such as having my implementation of Push() call a private PushImpl() procedure defined only in the body which actually has the precondition... but that's horrible. What's the right way to do this?
I think this is always going to be a problem when the validation checks are private, and that the solution is to declare a function to do the check:
package RingBuffer is
function Is_Full return Boolean;
procedure Push(value: T) with Pre => not Is_Full;
function Pop return T;
(Is_Full is probably useful anyway; in other cases it might not be so).
If you leave the implementation in the package body, you’ll need to put Is_Full there too, but you could move them to the spec and use an expression function:
package RingBuffer is
function Is_Full return Boolean;
procedure Push(value: T) with Pre => not Is_Full;
function Pop return T;
private
buffer: array(0..size) of T;
readptr: integer := 0;
writeptr: integer := 1;
function Is_Full return Boolean is (Readptr = Writeptr);
end RingBuffer;
The contract aspects are intended to be used in the public view of (sub)types and subprograms.
If you want to keep the check in the private view, then it is simple to write it as the first statement in the subprogram:
begin
if Is_Full then
raise Constraint_Error with "Ring buffer is full.";
end if;
...
Some unsolicited advise:
Make the contracts public, so the users of the package can see how it should be used.
Insert a similar Is_Empty check when popping an item from the buffer.
Make your index type modular: type Indexes is mod 16;

Sharing data array between two applications in Delphi

I want to share array data between two applications. In my mind, first program create the array and the second program can read the array from already allocated memory area. The array is not a dynamic array.
I found a way to share pointer using OpenFileMapping and MapViewOfFile. I have no luck to implement array sharing and I think i don't want to use IPC method yet.
Is it possible to plan a scheme like this (sharing array)? My purpose is to minimize memory usage and reading data quickly.
Scratched my head thinking of what a short-but-complete example of sharing memory between two applications might be. The only option is a console application, GUI applications require a minimum of 3 files (DPR + PAS + DFM). So I cooked up a small example where one integers array is shared using a memory mapped file (backed by the page file so I don't need to have a phisical file on disk for this to work). The console application responds to 3 commands:
EXIT
SET NUM VALUE Changes the value at index NUM in the array to VALUE
DUMP NUM displays the value in the array at index NUM
DUMP ALL displays the whole array
Of course, the command processing code takes up about 80% of the whole application. To test this compile the following console application, find the executable and start it twice. Go to the first window and enter:
SET 1 100
SET 2 50
Go to the second console and enter this:
DUMP 1
DUMP 2
DUMP 3
SET 1 150
Go to the first console and enter this:
DUMP 1
There you have it, you've just witnessed sharing memory between two applications.
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, Classes;
type
TSharedArray = array[0..10] of Integer;
PSharedArray = ^TSharedArray;
var
hFileMapping: THandle; // Mapping handle obtained using CreateFileMapping
SharedArray: PSharedArray; // Pointer to the shared array
cmd, s: string;
num, value, i: Integer;
L_CMD: TStringList;
function ReadNextCommand: string;
begin
WriteLn('Please enter command (one of EXIT, SET NUM VALUE, DUMP NUM, DUMP ALL)');
WriteLn;
ReadLn(Result);
end;
begin
try
hFileMapping := CreateFileMapping(0, nil, PAGE_READWRITE, 0, SizeOf(TSharedArray), '{C616DDE6-23E2-425C-B871-9E0DA54D96DF}');
if hFileMapping = 0 then
RaiseLastOSError
else
try
SharedArray := MapViewOfFile(hFileMapping, FILE_MAP_READ or FILE_MAP_WRITE, 0, 0, SizeOf(TSharedArray));
if SharedArray = nil then
RaiseLastOSError
else
try
WriteLn('Connected to the shared view of the file.');
cmd := ReadNextCommand;
while UpperCase(cmd) <> 'EXIT' do
begin
L_CMD := TStringList.Create;
try
L_CMD.DelimitedText := cmd;
for i:=0 to L_CMD.Count-1 do
L_CMD[i] := UpperCase(L_CMD[i]);
if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and TryStrToInt(L_CMD[1], num) then
WriteLn('SharedArray[', num, ']=', SharedArray^[num])
else if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and (L_CMD[1] = 'ALL') then
begin
for i:= Low(SharedArray^) to High(SharedArray^) do
WriteLn('SharedArray[', i, ']=', SharedArray^[i]);
end
else if (L_CMD.Count = 3) and (L_CMD[0] = 'SET') and TryStrToInt(L_CMD[1], num) and TryStrToInt(L_CMD[2], value) then
begin
SharedArray^[num] := Value;
WriteLn('SharedArray[', num, ']=', SharedArray^[num]);
end
else
WriteLn('Error processing command: ' + cmd);
finally L_CMD.Free;
end;
// Requst next command
cmd := ReadNextCommand;
end;
finally UnmapViewOfFile(SharedArray);
end;
finally CloseHandle(hFileMapping);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
A Named File Mapping would be the easiest solution, here is some short example code.
In this sample there is a main program that writes some data and reader(s) that only read from it.
Main:
type
TSharedData = record
Handle: THandle;
end;
PSharedData = ^TSharedData;
const
BUF_SIZE = 256;
var
SharedData: PSharedData;
hFileMapping: THandle; // Don't forget to close when you're done
function CreateNamedFileMapping(const Name: String): THandle;
begin
Result := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
BUF_SIZE, PChar(Name));
Win32Check(Result > 0);
SharedData := MapViewOfFile(Result, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
Win32Check(Assigned(SharedData));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
hFileMapping := CreateNamedFileMapping('MySharedMemory');
Win32Check(hFileMapping > 0);
SharedData^.Handle := CreateHiddenWindow;
end;
reader:
var
hMapFile: THandle; // Don't forget to close
function GetSharedData: PSharedData;
begin
hMapFile := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MySharedMemory');
Win32Check(hMapFile > 0);
Result := MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
Win32Check(Assigned(Result));
end;

Resources