How can I debug my code for the A star algorithm? - a-star

I have been trying to program different A star algorithms I found online and though they make sense, every implementation I have programmed failed.
This is my Free Pascal code:
function getHeuristic(currentXY, targetXY: array of word): word;
begin
getHeuristic:=abs(currentXY[0]-targetXY[0])+abs(currentXY[1]-targetXY[1]);
end;
function getPath(startingNodeXY, targetNodeXY: array of word; grid: wordArray3; out pathToControlledCharPtr: word; worldObjIndex: word): wordArray2;
var
openList, closedList: array of array of word; { x/y/g/h/parent x/parent y, total }
qXYGH: array[0..5] of word; { x/y/g/h/parent x/parent y }
gridXCnt, gridYCnt: longInt;
maxF, q, openListCnt, closedListCnt, parentClosedListCnt, getPathCnt, adjSquNewGScore: word;
openListIndexCnt, closedListIndexCnt, qIndexCnt, successorIndexCnt: byte;
getMaxF, successorOnClosedList, successorOnOpenList, pathFound: boolean;
begin
{ Add the starting square (or node) to the open list. }
setLength(openList, 6, length(openList)+1);
openList[0, 0]:=startingNodeXY[0];
openList[1, 0]:=startingNodeXY[1];
setLength(closedList, 6, 0);
{ Repeat the following: }
{ D) Stop when you: }
{ Fail to find the target square, and the open list is empty. In this case, there is no path. }
pathFound:=false;
{ writeLn('h1'); }
while length(openList[0])>0 do
begin
{ A) Look for the lowest F cost square on the open list. We refer to this as the current square. }
maxF:=0;
q:=0;
getMaxF:=true;
for openListCnt:=0 to length(openList[0])-1 do
begin
//writeLn(formatVal('open list xy {} {}, cnt {}, list max index {}', [openList[0, openListCnt], openList[1, openListCnt], openListCnt, length(openList[0])-1]));
{ readLnPromptX; }
if (getMaxF=true) or (maxF>openList[2, openListCnt]+openList[3, openListCnt]) then
begin
getMaxF:=false;
maxF:=openList[2, openListCnt]+openList[3, openListCnt];
q:=openListCnt;
end;
end;
for qIndexCnt:=0 to length(qXYGH)-1 do
qXYGH[qIndexCnt]:=openList[qIndexCnt, q];
{ B). Switch it to the closed list. }
setLength(closedList, length(closedList), length(closedList[0])+1);
for closedListIndexCnt:=0 to length(closedList)-1 do
closedList[closedListIndexCnt, length(closedList[0])-1]:=qXYGH[closedListIndexCnt];
{ Remove current square from open list }
if q<length(openList[0])-1 then
begin
for openListCnt:=q to length(openList[0])-2 do
begin
for openListIndexCnt:=0 to length(openList)-1 do
openList[openListIndexCnt, openListCnt]:=openList[openListIndexCnt, openListCnt+1];
end;
end;
setLength(openList, length(openList), length(openList[0])-1);
//writeLn(formatVal('q[x] {}, q[y] {}, startingNodeXY x {}, startingNodeXY y {}, targetNodeXY x {}, targetNodeXY y {}', [qXYGH[0], qXYGH[1], startingNodeXY[0], startingNodeXY[1], targetNodeXY[0], targetNodeXY[1]]));
{ readLnPromptX; }
{ D) Stop when you: }
{ Add the target square to the closed list, in which case the path has been found, or }
if (qXYGH[0]=targetNodeXY[0]) and (qXYGH[1]=targetNodeXY[1]) then
begin
pathFound:=true;
break;
end;
{ C) For each of the 8 squares adjacent to this current square … }
for gridXCnt:=qXYGH[0]-1 to qXYGH[0]+1 do
begin
for gridYCnt:=qXYGH[1]-1 to qXYGH[1]+1 do
begin
{ Adjacent square cannot be the current square }
if (gridXCnt<>qXYGH[0]) or (gridYCnt<>qXYGH[1]) then
begin
//writeLn(formatVal('gridXCnt {} gridYCnt {} qXYGH[0] {} qXYGH[1] {}', [gridXCnt, gridYCnt, qXYGH[0], qXYGH[1]]));
{ readLnPromptX; }
{ Check if successor is on closed list }
successorOnClosedList:=false;
if length(closedList[0])>0 then
begin
for closedListCnt:=0 to length(closedList[0])-1 do
begin
if (closedList[0, closedListCnt]=gridXCnt) and (closedList[1, closedListCnt]=gridYCnt) then
begin
successorOnClosedList:=true;
break;
end;
end;
end;
{ If it is not walkable or if it is on the closed list, ignore it. Otherwise do the following. }
if (gridXCnt>=0) and (gridXCnt<=length(grid[3])-1) and (gridYCnt>=0) and (gridYCnt<=length(grid[3, 0])-1) and (grid[3, gridXCnt, gridYCnt]=0) and (successorOnClosedList=false) then
begin
{ If it isn’t on the open list, add it to the open list. Make the current square the parent of this square. Record the F, G, and H costs of the square. }
successorOnOpenList:=false;
if length(openList[0])>0 then
begin
for openListCnt:=0 to length(openList[0])-1 do
begin
if (openList[0, openListCnt]=gridXCnt) and (openList[1, openListCnt]=gridYCnt) then
begin
successorOnOpenList:=true;
break;
end;
end;
end;
if successorOnOpenList=false then
begin
setLength(openList, length(openList), length(openList[0])+1);
openList[0, length(openList[0])-1]:=gridXCnt;
openList[1, length(openList[0])-1]:=gridYCnt;
openList[4, length(openList[0])-1]:=qXYGH[0];
openList[5, length(openList[0])-1]:=qXYGH[1];
if (openList[0, length(openList[0])-1]=qXYGH[0]) or (openList[1, length(openList[0])-1]=qXYGH[1]) then
begin
openList[2, length(openList[0])-1]:=openList[2, length(openList[0])-1]+10;
end
else
begin
openList[2, length(openList[0])-1]:=openList[2, length(openList[0])-1]+14;
end;
openList[3, length(openList[0])-1]:=getHeuristic([openList[0, length(openList[0])-1], openList[1, length(openList[0])-1]], [targetNodeXY[0], targetNodeXY[1]]);
end
else
begin
{ If it is on the open list already, check to see if this path to that square is better, using G cost as the measure (check to see if the G score for the adjacent square is lower if we use the current square to get there (adjacent square
new G score = current square G score + 10 (if adjacent squre is vertical or horizontal to current square) or +14 (if it is diagonal); if result is lower than adjacent square current G score then this path is better). A lower G cost means that
this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square. If you are keeping your open list sorted by F score, you may need to resort the list to account for the
change. }
adjSquNewGScore:=openList[2, openListCnt];
if (openList[0, openListCnt]=qXYGH[0]) or (openList[1, openListCnt]=qXYGH[1]) then
begin
adjSquNewGScore:=adjSquNewGScore+10;
end
else
begin
adjSquNewGScore:=adjSquNewGScore+14;
end;
if adjSquNewGScore<openList[2, openListCnt] then
begin
openList[4, openListCnt]:=qXYGH[0];
openList[5, openListCnt]:=qXYGH[1];
openList[2, openListCnt]:=adjSquNewGScore;
end;
end;
end;
end;
end;
end;
end;
{ writeLn('h2'); }
{ writeLn(pathFound); }
{ readLnHalt; }
if pathFound=true then
begin
{ Save the path. Working backwards from the target square, go from each square to its parent square until you reach the starting square. That is your path. }
closedListCnt:=length(closedList[0])-1;
setLength(getPath, 2, 0);
{ While starting node has not been added to path }
while (length(getPath[0])=0) or (getPath[0, length(getPath[0])-1]<>startingNodeXY[0]) or (getPath[1, length(getPath[0])-1]<>startingNodeXY[1]) do
begin
{ Add node from closed list to path }
setLength(getPath, 2, length(getPath[0])+1);
getPath[0, length(getPath[0])-1]:=closedList[0, closedListCnt];
getPath[1, length(getPath[0])-1]:=closedList[1, closedListCnt];
{ Find next node on closed list with coord matching parent coord of current closed list node }
for parentClosedListCnt:=length(closedList[0])-1 downto 0 do
if (closedList[0, parentClosedListCnt]=closedList[4, closedListCnt]) and (closedList[1, parentClosedListCnt]=closedList[5, closedListCnt]) then break;
closedListCnt:=parentClosedListCnt;
{ if (closedList[0, closedListCnt]=0) and (closedList[1, closedListCnt]=0) then break; }
end;
pathToControlledCharPtr:=length(getPath[0])-1;
end;
end;
The steps I'm following are:
Add the starting square (or node) to the open list.
Repeat the following:
A) Look for the lowest F cost square on the open list. We refer to this as the current square.
B). Switch it to the closed list.
C) For each of the 8 squares adjacent to this current square …
If it is not walkable or if it is on the closed list, ignore it. Otherwise do the following.
If it isn’t on the open list, add it to the open list. Make the current square the parent of this square. Record the F, G, and H costs of the square.
If it is on the open list already, check to see if this path to that square is better, using G cost as the measure. A lower G cost means that this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square. If you are keeping your open list sorted by F score, you may need to resort the list to account for the change.
D) Stop when you:
Add the target square to the closed list, in which case the path has been found, or
Fail to find the target square, and the open list is empty. In this case, there is no path.
Save the path. Working backwards from the target square, go from each square to its parent square until you reach the starting square. That is your path.

Solved it!
This is the working code:
function getHeuristic(currentXY, targetXY: array of word): word;
begin
getHeuristic:=abs(currentXY[0]-targetXY[0])+abs(currentXY[1]-targetXY[1]);
end;
function getPath(startingNodeXY, targetNodeXY: array of word; grid: wordArray3; out pathToControlledCharPtr: word; worldObjIndex: word): wordArray2;
var
openList, closedList: array of array of word; { x/y/g/h/parent x/parent y, total }
qXYGH: array[0..5] of word; { x/y/g/h/parent x/parent y }
gridXCnt, gridYCnt: longInt;
maxF, q, openListCnt, closedListCnt, parentClosedListCnt, getPathCnt, adjSquNewGScore: word;
openListIndexCnt, closedListIndexCnt, qIndexCnt, successorIndexCnt: byte;
getMaxF, successorOnClosedList, successorOnOpenList, pathFound: boolean;
begin
{ Add the starting square (or node) to the open list. }
setLength(openList, 6, length(openList)+1);
openList[0, 0]:=startingNodeXY[0];
openList[1, 0]:=startingNodeXY[1];
setLength(closedList, 6, 0);
{ Repeat the following: }
{ D) Stop when you: }
{ Fail to find the target square, and the open list is empty. In this case, there is no path. }
pathFound:=false;
{ writeLn('h1'); }
while length(openList[0])>0 do
begin
{ A) Look for the lowest F cost square on the open list. We refer to this as the current square. }
maxF:=0;
q:=0;
getMaxF:=true;
for openListCnt:=0 to length(openList[0])-1 do
begin
//writeLn(formatVal('open list xy {} {}, cnt {}, list max index {}', [openList[0, openListCnt], openList[1, openListCnt], openListCnt, length(openList[0])-1]));
{ readLnPromptX; }
if (getMaxF=true) or (maxF>openList[2, openListCnt]+openList[3, openListCnt]) then
begin
getMaxF:=false;
maxF:=openList[2, openListCnt]+openList[3, openListCnt];
q:=openListCnt;
end;
end;
for qIndexCnt:=0 to length(qXYGH)-1 do
qXYGH[qIndexCnt]:=openList[qIndexCnt, q];
{ B). Switch it to the closed list. }
setLength(closedList, length(closedList), length(closedList[0])+1);
for closedListIndexCnt:=0 to length(closedList)-1 do
closedList[closedListIndexCnt, length(closedList[0])-1]:=qXYGH[closedListIndexCnt];
{ Remove current square from open list }
if q<length(openList[0])-1 then
begin
for openListCnt:=q to length(openList[0])-2 do
begin
for openListIndexCnt:=0 to length(openList)-1 do
openList[openListIndexCnt, openListCnt]:=openList[openListIndexCnt, openListCnt+1];
end;
end;
setLength(openList, length(openList), length(openList[0])-1);
//writeLn(formatVal('q[x] {}, q[y] {}, startingNodeXY x {}, startingNodeXY y {}, targetNodeXY x {}, targetNodeXY y {}', [qXYGH[0], qXYGH[1], startingNodeXY[0], startingNodeXY[1], targetNodeXY[0], targetNodeXY[1]]));
{ readLnPromptX; }
{ D) Stop when you: }
{ Add the target square to the closed list, in which case the path has been found, or }
if (qXYGH[0]=targetNodeXY[0]) and (qXYGH[1]=targetNodeXY[1]) then
begin
pathFound:=true;
break;
end;
{ C) For each of the 8 squares adjacent to this current square … }
for gridXCnt:=qXYGH[0]-1 to qXYGH[0]+1 do
begin
for gridYCnt:=qXYGH[1]-1 to qXYGH[1]+1 do
begin
{ Adjacent square cannot be the current square }
if (gridXCnt<>qXYGH[0]) or (gridYCnt<>qXYGH[1]) then
begin
//writeLn(formatVal('gridXCnt {} gridYCnt {} qXYGH[0] {} qXYGH[1] {}', [gridXCnt, gridYCnt, qXYGH[0], qXYGH[1]]));
{ readLnPromptX; }
{ Check if successor is on closed list }
successorOnClosedList:=false;
if length(closedList[0])>0 then
begin
for closedListCnt:=0 to length(closedList[0])-1 do
begin
if (closedList[0, closedListCnt]=gridXCnt) and (closedList[1, closedListCnt]=gridYCnt) then
begin
successorOnClosedList:=true;
break;
end;
end;
end;
{ If it is not walkable or if it is on the closed list, ignore it. Otherwise do the following. }
if (gridXCnt>=0) and (gridXCnt<=length(grid[3])-1) and (gridYCnt>=0) and (gridYCnt<=length(grid[3, 0])-1) and (grid[3, gridXCnt, gridYCnt]=0) and (successorOnClosedList=false) then
begin
{ If it isn’t on the open list, add it to the open list. Make the current square the parent of this square. Record the F, G, and H costs of the square. }
successorOnOpenList:=false;
if length(openList[0])>0 then
begin
for openListCnt:=0 to length(openList[0])-1 do
begin
if (openList[0, openListCnt]=gridXCnt) and (openList[1, openListCnt]=gridYCnt) then
begin
successorOnOpenList:=true;
break;
end;
end;
end;
if successorOnOpenList=false then
begin
setLength(openList, length(openList), length(openList[0])+1);
openList[0, length(openList[0])-1]:=gridXCnt;
openList[1, length(openList[0])-1]:=gridYCnt;
openList[4, length(openList[0])-1]:=qXYGH[0];
openList[5, length(openList[0])-1]:=qXYGH[1];
if (openList[0, length(openList[0])-1]=qXYGH[0]) or (openList[1, length(openList[0])-1]=qXYGH[1]) then
begin
openList[2, length(openList[0])-1]:=openList[2, length(openList[0])-1]+10;
end
else
begin
openList[2, length(openList[0])-1]:=openList[2, length(openList[0])-1]+14;
end;
openList[3, length(openList[0])-1]:=getHeuristic([openList[0, length(openList[0])-1], openList[1, length(openList[0])-1]], [targetNodeXY[0], targetNodeXY[1]]);
end
else
begin
{ If it is on the open list already, check to see if this path to that square is better, using G cost as the measure (check to see if the G score for the adjacent square is lower if we use the current square to get there (adjacent square
new G score = current square G score + 10 (if adjacent squre is vertical or horizontal to current square) or +14 (if it is diagonal); if result is lower than adjacent square current G score then this path is better). A lower G cost means that
this is a better path. If so, change the parent of the square to the current square, and recalculate the G and F scores of the square. If you are keeping your open list sorted by F score, you may need to resort the list to account for the
change. }
adjSquNewGScore:=openList[2, openListCnt];
if (openList[0, openListCnt]=qXYGH[0]) or (openList[1, openListCnt]=qXYGH[1]) then
begin
adjSquNewGScore:=adjSquNewGScore+10;
end
else
begin
adjSquNewGScore:=adjSquNewGScore+14;
end;
if adjSquNewGScore<openList[2, openListCnt] then
begin
openList[4, openListCnt]:=qXYGH[0];
openList[5, openListCnt]:=qXYGH[1];
openList[2, openListCnt]:=adjSquNewGScore;
end;
end;
end;
end;
end;
end;
end;
{ writeLn('h2'); }
{ writeLn(pathFound); }
{ readLnHalt; }
if pathFound=true then
begin
{ Save the path. Working backwards from the target square, go from each square to its parent square until you reach the starting square. That is your path. }
closedListCnt:=length(closedList[0])-1;
setLength(getPath, 2, 0);
{ While starting node has not been added to path }
while (length(getPath[0])=0) or (getPath[0, length(getPath[0])-1]<>startingNodeXY[0]) or (getPath[1, length(getPath[0])-1]<>startingNodeXY[1]) do
begin
{ Add node from closed list to path }
setLength(getPath, 2, length(getPath[0])+1);
getPath[0, length(getPath[0])-1]:=closedList[0, closedListCnt];
getPath[1, length(getPath[0])-1]:=closedList[1, closedListCnt];
//writeLn(formatVal('path found {} {}, start {} {}, target {} {}', [getPath[0, length(getPath[0])-1], getPath[1, length(getPath[0])-1], startingNodeXY[0], startingNodeXY[1], targetNodeXY[0], targetNodeXY[1]]));
{ readLnPromptX; }
{ Find next node on closed list with coord matching parent coord of current closed list node }
for parentClosedListCnt:=length(closedList[0])-1 downto 0 do
if (closedList[0, parentClosedListCnt]=closedList[4, closedListCnt]) and (closedList[1, parentClosedListCnt]=closedList[5, closedListCnt]) then break;
closedListCnt:=parentClosedListCnt;
{ if (closedList[0, closedListCnt]=0) and (closedList[1, closedListCnt]=0) then break; }
end;
pathToControlledCharPtr:=length(getPath[0])-1;
end;
end;

Related

Eiffel - How do I make my classes readable?

I'm new to Eiffel and I'm trying to use the LINKED_LIST class for organizing instances of other class "MONOMIO" I've made. I added a function for ordering this elements and I use the remove and the cursor movement features and when I try to execute the code it raises an exception saying that the objects contained should be readable and writable. I would like to know how to do it, this is my class:
class
MONOMIO
feature --Initialization
make (coef:INTEGER; expX:INTEGER; expY:INTEGER)
do
coeficiente := coef
exponenteX := expX
exponenteY := expY
end
feature
evaluar(valX: INTEGER; valY: INTEGER): REAL_64
do
Result := coeficiente*(valX^exponenteX)*(valY^exponenteY)
end;
coeficiente: INTEGER;
exponenteX: INTEGER;
exponenteY: INTEGER;
feature --setter
set_coeficiente(val: INTEGER)
do
coeficiente := val
end;
end
I think the exception raises because of this feature I've made for a class that has as a feature the LINKED_LIST[MONOMIO] and it's called "contenido":
simplificar
local
tamanio_polinomio: INTEGER -- Número de monomios que tiene el polinomio
contador: INTEGER
monomio_a_comparar: MONOMIO -- Auxiliar
coeficiente_total:INTEGER -- Auxiliar
indice_monomio_en_revision:INTEGER
do
from
contenido.start
indice_monomio_en_revision := 0
tamanio_polinomio := contenido.count
until
indice_monomio_en_revision = tamanio_polinomio
loop
contenido.start
contenido.move (indice_monomio_en_revision)
monomio_a_comparar := contenido.item
from
contador := indice_monomio_en_revision
coeficiente_total := monomio_a_comparar.coeficiente
contenido.forth
until
contador = tamanio_polinomio
loop
if
(monomio_a_comparar.exponentex = contenido.item.exponentex) and
(monomio_a_comparar.exponentey = contenido.item.exponentey)
then
coeficiente_total := coeficiente_total + contenido.item.coeficiente
contenido.remove -- Mueve el cursor a la derecha
tamanio_polinomio := tamanio_polinomio - 1
contador := contador - 1
else
if
not contenido.islast
then
contenido.forth
end
end
contador := contador + 1
end
contenido.start
contenido.move (indice_monomio_en_revision)
contenido.item.set_coeficiente (coeficiente_total)
indice_monomio_en_revision := indice_monomio_en_revision + 1
end
end;
I hope anyone can help me with this problem. Thanks.
Suppose you have a list with 1 element. Then we enter the outer loop and move to the first element. Then we execute contador := indice_monomio_en_revision that is still 0 at this point and do contenido.forth. Now we are beyond the list because there is only one element. However contador = tamanio_polinomio is false (0 = 1), so we enter the inner loop and try to retrieve the second (non-existing) item. BOOM!
Other issues include:
There are multiple calls like contenido.start followed by contenido.move. You could use a single call to go_i_th instead.
Instead of counting number of items in the list I would look at the feature after. It tells when you reach an end of the list. It would simplify the logic of your loop (e.g. the call to islast would be removed) and let you to remove some local variables.
Taking the last point into account I would write the inner loop condition as
contenido.after
At least this would avoid the crash you experience. As to the logic, you may need to check features start, after, forth and remove to see what effect they have. The usual way to write loops in such cases is like
from
l.start
until
l.after
loop
... -- Use l.item
l.forth
end
In case of remove probably you do not need to call forth.

Copying an address from a pointer to a different memory address

I have a C DLL with a number of functions I'm calling from Delphi. One of the functions (say Func1) returns a pointer to a struct - this all works fine. The structs created by calling Func1 are stored in a global pool within the DLL. Using a second function (Func2) I get a pointer to a block of memory containing an array of pointers, and I can access the array elements using an offset.
I need to be able copy the address in the returned pointer for a struct (from Func1) to any of the memory locations in the array (from Func2). The idea is that I can build arrays of pointers to pre-defined structs and access the elements directly from Delphi using pointer offsets.
I tried using:
CopyMemory(Pointer(NativeUInt(DataPointer) + offset), PStruct, DataSize);
where DataPointer is the start of my array and PStruct is returned from Func1, but that doesn't copy the address I need.
In .NET it works using Marshal.WriteIntPtr and looking at the underlying code for this using Reflector I think I need something trickier than CopyMemory. Anyone got any ideas for doing this in Delphi?
Edit: This is part of a wrapper around vector structures returned from the R language DLL. I have a base vector class from which I derive specific vector types. I've got the wrapper for the numeric vector working, so my base class looks fine and this is where I get DataPointer:
function TRVector<T>.GetDataPointer: PSEXPREC;
var
offset: integer;
h: PSEXPREC;
begin
// TVECTOR_SEXPREC is the vector header, with the actual data behind it.
offset := SizeOf(TVECTOR_SEXPREC);
h := Handle;
result := PSEXPREC(NativeUInt(h) + offset);
end;
Setting a value in a numeric vector is easy (ignoring error handling):
procedure TNumericVector.SetValue(ix: integer; value: double);
var
PData: PDouble;
offset: integer;
begin
offset := GetOffset(ix); // -- Offset from DataPointer
PData := PDouble(NativeUInt(DataPointer) + offset);
PData^ := value;
end;
For a string vector I need to (i) create a base vector of pointers with a pre-specified length as for the numeric vector (ii) convert each string in my input array to an R internal character string (CHARSXP) using the R mkChar function (iii) assign the address of the character string struct to the appropriate element in the base vector. The string array gets passed into the constructor of my vector class (TCharacterVector) and I then call SetValue (see below) for each string in the array.
I should have thought of PPointer as suggested by Remy but neither that or the array approach seem to work either. Below is the code using the array approach from Remy and with some pointer vars for checking addresses. I'm just using old-fashioned pointer arithmetic and have shown addresses displayed for a run when debugging:
procedure TCharacterVector.SetValue(ix: integer; value: string);
var
PData: PSEXPREC;
offset: integer;
offset2: integer;
PTest: PSEXPREC;
PPtr: Pointer;
PPtr2: Pointer;
begin
offset := GetOffset(ix);
PPtr := PPointer(NativeUInt(DataPointer) + offset); // $89483D8
PData := mkChar(value); // $8850258
// -- Use the following code to check that mkChar is working.
offset2 := SizeOf(TVECTOR_SEXPREC);
PTest := PSEXPREC(NativeUInt(PData) + offset);
FTestString := FTestString + AnsiString(PAnsiChar(PTest));
//PPointerList(DataPointer)^[ix] := PData;
//PPtr2 := PPointer(NativeUInt(DataPointer) + offset); // Wrong!
PPointerArray(DataPointer)^[ix] := PData;
PPtr2 := PPointerArray(DataPointer)^[ix]; // $8850258 - correct
end;
I'd have thought the address in PData ($8850258) would now be in PPtr2 but I've been staring at this so long I'm sure I'm missing something obvious.
Edit2: The code for SetValue used in R.NET is as follows (ignoring test for null string):
private void SetValue(int index, string value)
{
int offset = GetOffset(index);
IntPtr stringPointer = mkChar(value);
Marshal.WriteIntPtr(DataPointer, offset, stringPointer);
}
From reflector, Marshal.WriteIntPtr uses the following C:
public static unsafe void WriteInt32(IntPtr ptr, int ofs, int val)
{
try
{
byte* numPtr = (byte*) (((void*) ptr) + ofs);
if ((((int) numPtr) & 3) == 0)
{
*((int*) numPtr) = val;
}
else
{
byte* numPtr2 = (byte*) &val;
numPtr[0] = numPtr2[0];
numPtr[1] = numPtr2[1];
numPtr[2] = numPtr2[2];
numPtr[3] = numPtr2[3];
}
}
catch (NullReferenceException)
{
throw new AccessViolationException();
}
}
You say you want to copy the struct pointer itself into the array, but the code you have shown is trying to copy the struct data that the pointer is pointing at. If you really want to copy just the pointer itself, don't use CopyMemory() at all. Just assign the pointer as-is:
const
MaxPointerList = 255; // whatever max array count that Func2() allocates
type
TPointerList = array[0..MaxPointerList-1] of Pointer;
PPointerList = ^TPointerList;
PPointerList(DataPointer)^[index] := PStruct;
Your use of NativeUInt reveals that you are using a version of Delphi that likely supports the {$POINTERMATH} directive, so you can take advantage of that instead, eg:
{$POINTERMATH ON}
PPointer(DataPointer)[index] := PStruct;
Or, use the pre-existing PPointerArray type in the System unit:
{$POINTERMATH ON}
PPointerArray(DataPointer)[index] := PStruct;

How to access TBitmap pixels directly in FMX2 (TBitmap.ScanLine replacement)?

The FMX.Types.TBitmap class has the ScanLine property in FMX (FireMonkey), but it seems this property was removed, and is missing in FMX2 (FireMonkey FM2).
Is there any workaround ? How do we supposed to access TBitmap content directly in FMX2 ?
For direct access you are expect to use the Map method. The documentation includes a number of examples, such as FMX.AlphaColorToScanline:
function TForm1.TestAlphaColorToScanline(ABitmap: TBitmap;
start, count: integer): TBitmap;
var
bitdata1, bitdata2: TBitmapData;
begin
Result := TBitmap.Create(Round(ABitmap.Width), Round(count));
if (ABitmap.Map(TMapAccess.maRead, bitdata1) and
Result.Map(TMapAccess.maWrite, bitdata2)) then
begin
try
AlphaColorToScanline(#PAlphaColorArray(bitdata1.Data)
[start * (bitdata1.Pitch div GetPixelFormatBytes(ABitmap.PixelFormat))],
bitdata2.Data, Round(Result.Height * Result.Width),
ABitmap.PixelFormat);
finally
ABitmap.Unmap(bitdata1);
Result.Unmap(bitdata2);
end;
end;
end;
Here is an example for C++Builder (the current docs are completely missing such):
int X, Y;
TBitmapData bm;
// get bitmap data access !
if ( Image1->Bitmap->Map(TMapAccess::maReadWrite, bm) )
{
unsigned int* data = (unsigned int*)bm.Data;
// i.e. clear data with alpha color
memset(data, 0,
Image1->Width * Image1->Height * sizeof(unsigned int));
// test direct pixel access here
for (X = 20; X <= 200; X++)
{
for (Y = 10; Y <= 100; Y++)
{
//MyBitmap->Pixels[X][Y] = claLime; // does not work anymore !
bm.SetPixel(X, Y, claLime);
}
}
// now write back the result !
Image1->Bitmap->Unmap(bm);
}
else
{
MessageDlg("Could not map the image data for direct access.",
TMsgDlgType::mtWarning, TMsgDlgButtons() << TMsgDlgBtn::mbOK, 0);
}

How to display values from a VARIANT with a SAFEARRAY of BSTRs

I am working on a COM Object library with function that returns a VARIANT with a SAFEARRAY of BSTRs. How can I display the values from this VARIANT instance and save it inside a TStringList? I tried searching the net with no clear answer.
I tried the following with no success:
Variant V;
String mystr;
VarClear(V);
TVarData(V).VType = varOleStr;
V = ComFunction->GetValues(); //<<<<----- V is empty
mystr = (wchar_t *)(TVarData(V).VString);
Memo1->Lines->Add(mystr);
VarClear(V);
You can use TWideStringDynArray and let Delphi do the conversion:
procedure LoadStringsFromVariant(const Values: TWideStringDynArray; Strings: TStrings);
var
I: Integer;
begin
Strings.BeginUpdate;
try
for I := Low(Values) to High(Values) do
Strings.Add(Values[I]);
finally
Strings.EndUpdate;
end;
end;
When you call this with your Variant safearray of BSTRs it will be converted to TWideStringDynArray automatically. An incompatible Variant will cause the runtime error EVariantInvalidArgError.
To check if a Variant holds a safe array of BSTR you can do this:
IsOK := VarIsArray(V) and (VarArrayDimCount(V) = 1) and (VarType(V) and varTypeMask = varOleStr);
uses ActiveX;
var
VSafeArray: PSafeArray;
LBound, UBound, I: LongInt;
W: WideString;
begin
VSafeArray := ComFunction.GetValues();
SafeArrayGetLBound(VSafeArray, 1, LBound);
SafeArrayGetUBound(VSafeArray, 1, UBound);
for I := LBound to UBound do
begin
SafeArrayGetElement(VSafeArray, I, W);
Memo1.Lines.Add(W);
end;
SafeArrayDestroy(VSafeArray); // cleanup PSafeArray
if you are creating ComFunction via late binding (CreateOleObject) you should use:
var
v: Variant;
v := ComFunction.GetValues;
for i := VarArrayLowBound(v, 1) to VarArrayHighBound(v, 1) do
begin
W := VarArrayGet(v, [i]);
Memo1.Lines.Add (W);
end;
How can I display the values from this VARIANT instance and save it inside a TStringList?
The COM VARIANT struct has parray and pparray data members that are pointers to a SAFEARRAY, eg:
VARIANT V;
LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);
The VCL Variant class, on the other hand, has an LPSAFEARRAY conversion operator defined, so you can assign it directly (but only if the Variant.VType field that not have the varByRef flag present, that is), eg:
Variant V;
LPSAFEARRAY sa = V;
Either way, once you have the SAFEARRAY pointer, use the SafeArray API to access the BSTR values, eg:
bool __fastcall VariantToStrings(const Variant &V, TStrings *List)
{
// make sure the Variant is holding an array
if (!V_ISARRAY(&V)) return false;
// get the array pointer
LPSAFEARRAY sa = V_ISBYREF(&V) ? V_ARRAYREF(&V) : V_ARRAY(&V);
// make sure the array is holding BSTR values
VARTYPE vt;
if (FAILED(SafeArrayGetVartype(sa, &vt))) return false;
if (vt != VT_BSTR) return false;
// make sure the array has only 1 dimension
if (SafeArrayGetDim(sa) != 1) return false;
// get the bounds of the array's sole dimension
LONG lBound = -1, uBound = -1;
if (FAILED(SafeArrayGetLBound(sa, 0, &lBound))) return false;
if (FAILED(SafeArrayGetUBound(sa, 0, &uBound))) return false;
if ((lBound > -1) && (uBound > -1))
{
// access the raw data of the array
BSTR *values = NULL;
if (FAILED(SafeArrayAccessData(sa, (void**)&values))) return false;
try
{
List->BeginUpdate();
try
{
// loop through the array adding the elements to the list
for (LONG idx = lBound; l <= uBound; ++idx)
{
String s;
if (values[idx] != NULL)
s = String(values[idx], SysStringLen(values[idx]));
List->Add(s);
}
}
__finally
{
List->EndUpdate();
}
}
__finally
{
// unaccess the raw data of the array
SafeArrayUnaccessData(sa);
}
}
return true;
}
VarClear(V);
TVarData(V).VType = varOleStr;
You don't need those at all. The VCL Variant class initializes itself to a blank state, and there is no need to assign the VType since you are assigning a new value to the entire Variant immediately afterwards.
V = ComFunction->GetValues(); //<<<<----- V is empty
If V is empty, then GetValues() is returning an empty Variant to begin with.
mystr = (wchar_t *)(TVarData(V).VString);
TVarData::VString is an AnsiString& reference, not a wchar_t* pointer. To convert a VCL Variant (not a COM VARIANT) to a String, just assign it as-is and let the RTL works out the detail for you:
String mystr = V;

OpenOffice Calc automation how alter a chart label of a scatter diagram

Hello could you please help me with the following. I have created a scattered chart and draw a chart from data of a column. The used data is not just after the cell which determines the label:
Column O:
Pwm1 <-- This is the cell I want to see as the label
27114 <-- not used data for graph
27055 <-- etc
27092
27070 <-- data for graph starts here
27105
27024
27092 <-- data for graph ends here
I would like the LABEL cell to appear as the Y column label name (Is now 'Column O'), but how?
This as far as I got (code is Delphi but if someone could help me with a basic example that's ok too):
(* Turn the symbol of the data points off *)
oChart.Diagram.SymbolType := _chartChartSymbolTypeNONE;
oDataSeries := oChart.getUsedData;
oDataSequences := oDataSeries.getDataSequences;
ShowMessage(oDataSequences[1].Label.SourceRangeRepresentation);
SourceRangeRepresentation returns the current label, but how to change?
Thanks Ad
This did it:
(*
creat new DataSequence from range representaion
that provides real data and its role in the series
oDataProvider: com.sun.star.chart2.data.XDataProvider
sRangeRepresentation: range address e.g. Sheet1.A1:B2
sRole: role is defined in com.sun.star.chart2.data.DataSequenceRole
*)
Function CreateDataSequence( oDataProvider : Variant; sRangeRepresentation : String; sRole :String ) : Variant;
Var
oDataSequence : Variant;
Begin
(* create .chart2.data.DataSequence from range representation *)
oDataSequence := oDataProvider.createDataSequenceByRangeRepresentation(sRangeRepresentation);
If NOT VarIsEmpty(oDataSequence) Then
oDataSequence.Role := sRole;
Result := oDataSequence;
End;
oNewLabel := CreateDataSequence(oChart.getDataProvider, '$Sheet1.$O$7', 'label');
oDataSequences[1].setLabel(oNewLabel);

Resources