1

I am trying to use the ANN (approximate nearest neighbor) library (the .dll) within my Delphi code. The library is written in C++, and although the data types are fairly simple I am having some trouble.

I used h2pas to convert the C++ header file as far as I could get it. I ended up with the following data types (C++ on the left, Delphi on the right):

enum ANNbool {ANNfalse = 0, ANNtrue = 1}; --> ANNbool = longint;                
typedef double  ANNcoord;                 --> ANNcoord = double;                
typedef double  ANNdist;                  --> ANNdist = double;                 
typedef int     ANNidx;                   --> ANNidx = longint;                 

typedef ANNcoord* ANNpoint;               --> ANNpoint = ^ANNcoord;             
typedef ANNpoint* ANNpointArray;          --> ANNpointArray = ^ANNpoint;        
typedef ANNdist*  ANNdistArray;           --> ANNdistArray = ^ANNdist;          
typedef ANNidx*   ANNidxArray;            --> ANNidxArray = ^ANNidx;

For starters I want to sucessfully port the sample included with the ANN library. The C++ code for this sample is linked here without comments (if you want the comments, just download the library from ANN's webpage).

Even more specifically, here is the excerpt that I am having trouble with (C++ code..):

...
while (nPts < maxPts && readPt(*dataIn, dataPts[nPts]))  nPts++;
...
bool readPt(istream &in, ANNpoint p)
{
 for (int i = 0; i < dim; i++) {
 if(!(in >> p[i])) return false;
 }
 return true;
}

A member on stackoverflow helped me with a partial conversion of the readPt function - but I am not sure if it is completely correct (Delphi code..):

function readPt(inStr: TStream; p: ANNpoint): boolean;
var
  Size: longint; // number of bytes to read
begin
  inStr.Size := SizeOf(ANNcoord) * dim;
  Result := inStr.Read(p^, Size) = Size;
end; 

Here is my unsuccessful attempt at implementing that while loop in Delphi (irrelevant code omitted):

var
  dataPts: AnnPointArray;
  BinStream: TMemoryStream;    
  dim,maxPts,nPts: LongInt;


begin
  dim := 2; 
  maxPts := 1000;  
  dataPts := annAllocPts(maxPts, dim);      

  //GENERATE TStream data
  ///////////////////////////
  BinStream := TMemoryStream.Create;
  BinStream.SetSize(1001);
  for nPts := 0 to 1000 do
  begin
    BinStream.Write(nPts, 1);
  end;                       
  BinStream.Position := 0
  ///////////////////////////                     

  nPts:=0;
  while nPts < maxPts do
  begin
    readPt(BinStream, dataPts[nPts]);
    nPts:=nPts+1;
  end;  
end;   

When I attempt to run this I get a segmentation fault at readPt(BinStream, dataPts[nPts]);

I will be extremely grateful if someone would take the time to help me out here!

Edit: In case the C++ function for annAllocPts() is important.. here it is:

ANNpointArray annAllocPts(int n, int dim)       // allocate n pts in dim
{
    ANNpointArray pa = new ANNpoint[n];         // allocate points
    ANNpoint      p  = new ANNcoord[n*dim];     // allocate space for coords
    for (int i = 0; i < n; i++) {
        pa[i] = &(p[i*dim]);
    }
    return pa;
}

And here is how I implemented it:

function annAllocPts(n: longint; dim: longint): ANNpointArray cdecl;
  external 'ANN.dll' index 33;     

Edit 2: I am still having trouble properly filling the input stream (I think)...

var
 ThisDouble: Double;
begin
  binstream := TMemoryStream.Create;
  ThisDouble :=1.001;
  for nPts := 0 to maxPts*dim do
  begin
    binstream.Write(ThisDouble,SizeOf(ThisDouble));
  end;
  BinStream.Position := 0;

  nPts:=0;
  while nPts < maxPts do
  begin
   if not readPt(BinStream, dataPts[nPts]) then
    Break;
  nPts:=nPts+1;
end;                    
Mike Furlender
  • 3,869
  • 5
  • 47
  • 75
  • 2
    For one thing, you need to set the position of the stream back to the beginning after you've filled it (`BinStream.Position := 0`) – Nat Aug 17 '11 at 05:11
  • @Nat gotcha; fixed. Still the same error :-/ – Mike Furlender Aug 17 '11 at 05:16
  • @Nat I think my main point of confusion is accessing the `nPts`th element of `dataPts` – Mike Furlender Aug 17 '11 at 05:18
  • Also, should you be setting the streams size in `inStr.Size := SizeOf(ANNcoord) * dim;`? I would have thought that that line should read something like `Size := SizeOf(AnnCoord) * dim;` – Nat Aug 17 '11 at 05:23
  • Yeah, I got that, I'm just trying to make sense of the code, and these are the things that are jumping out early on... – Nat Aug 17 '11 at 05:25
  • @Nat I honestly don't quite understand that statement. I have tried removing it entirely and removing the `* dim` part; neither worked. – Mike Furlender Aug 17 '11 at 05:26
  • It's working out how much to read from the stream... Do you get the seg fault on the very first iteration of the while loop? – Nat Aug 17 '11 at 05:28
  • @Nat Yes - first one. I added the C++ definition for annAllocPts at the bottom of my question (in case that is relevant). – Mike Furlender Aug 17 '11 at 05:32
  • "Segmentation fault" is a Unix error, not a Windows error, but Delphi is a Windows-only development tool. Do you mean *access violation*? Please be precise. – Rob Kennedy Aug 17 '11 at 06:06
  • @Rob I am actually using Lazarus/FreePascal (not Delphi) on win64 - the error is "Project ANNConnect.exe raised exception class 'External: SIGSEGV'." – Mike Furlender Aug 17 '11 at 06:58
  • @Rob I was honestly convinced that the function itself was correct. I figured that I would figure out the pointer/array stuff along the way. I'm afraid that I am not quite experienced enough to make that sort of judgement call yet :) – Mike Furlender Aug 17 '11 at 06:59

2 Answers2

4

Your code calls the DLL function to allocate a array of 1000 2-element arrays of Double. Then you fill a stream with 1001 bytes. You're trying to populate 16000 bytes of storage with only 1001 bytes of data.

Furthermore, the data you're putting in the stream doesn't form meaningful Double values anyway.

Also, your version of readPt is wrong. You're assigning inStr.Size instead of the local Size variable. The compiler should have warned you that Size was used without being initialized. Your code has the effect to truncating the input stream to the length of one array segment and then attempting to read an unknown number of bytes from the stream. Use the version you got from your other question.

Community
  • 1
  • 1
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • 1
    Thanks, Rob, exactly what I see. – Rudy Velthuis Aug 17 '11 at 06:38
  • @Rob Thank you Rob, that definitely cleared it up for me. Embarrassingly I am still having trouble filling the stream and then writing it to the pointer. I updated my question.. am I on the right track? – Mike Furlender Aug 17 '11 at 09:26
  • Yeah, that looks OK. It ultimately seems a little pointless now; you put a bunch of data in a stream buffer and then immediately read it back out to put it in your arrays. May as well just store your data in the arrays directly and skip the stream. – Rob Kennedy Aug 17 '11 at 15:41
  • @Rob it is absolutely pointless. I am just trying to understand this so I can proceed. What I really need is to populate the array with a in-memory database. – Mike Furlender Aug 17 '11 at 15:59
  • @Rob I still cannot get it to avoid crashing when I attempt to access `dataPts[anything]`. This seems wrong to me..... this datatype is not an array, its just a pointer. Is @Nat correct about making the array static? – Mike Furlender Aug 17 '11 at 16:02
  • If it weren't allowed to be treated like an array, then it never would have compiled. Nat's suggestion affects nothing. Delphi lets you treat pointers like arrays as of Delphi 2009, I think. Are you sure it's the array access that crashes? If you execute that expression in a statement by itself, does the program still crash? – Rob Kennedy Aug 17 '11 at 16:09
  • @Rob, I inserted `readPt(BinStream, dataPts[0]);` after `BinStream.Position := 0;` and I got the same error. – Mike Furlender Aug 17 '11 at 16:16
  • @Rob Could it maybe be my use of "longint" instead of "integer"? Maybe annAllocPts is not properly allocating memory because of that?.. – Mike Furlender Aug 17 '11 at 16:23
  • LongInt, Integer, and int are all equivalent types. When I said to execute the statement by itself, I meant without any other function calls wrapping it. Declare an ANNpoint variable x, and then execute `x := dataPts[0]; ShowMessage(Format('%p', [x]))`. Does it crash? If not, then `dataPts[anything]` is not the problem. Keep searching. – Rob Kennedy Aug 17 '11 at 17:22
  • @Rob Just tried what you wrote - it crashed at x:= dataPts[0] :-/ – Mike Furlender Aug 17 '11 at 21:05
1

The code in readPt() is correct. But it reads more than one double in one go, just like the C++ example in the other question.

Your problem is that you write maxPts (=1000) doubles to the memory stream, but then, in a loop (of MaxPts = 1000 iterations), you read dim (= 2) doubles per iteration (readPt reads dim ANNcoords in one go), so you try to read 1000*2 ANNcoords and run out of data before you reach the end. You should check the return value of readPt() in the loop and break if it is false:

nPts:=0;
while nPts < maxPts do
begin
  if not readPt(BinStream, dataPts[nPts]) then
    Break;
  nPts:=nPts+1;
end;

Of course, you could also write dim * maxPts items into the memory stream.

Rudy Velthuis
  • 28,387
  • 5
  • 46
  • 94
  • THanks a ton - I think I've almost got it. Can you please take a look at my recent edit at the bottom of my question? – Mike Furlender Aug 17 '11 at 09:03
  • Looks good to me. As a simple test, I would personally write a function writePt() and model it after readPt but for the opposite direction. Then I would create a 2D array of coordinates and save it with the aid of writePt() (in a loop). Then I would read those from the stream and write them to another array, and then compare. – Rudy Velthuis Aug 17 '11 at 09:34
  • Hm - well I tested it and I am successfully writing doubles to the memory stream. I just can't get this statement to work (if not readPt(BinStream, dataPts[nPts]) ...) – Mike Furlender Aug 17 '11 at 13:26
  • From what I understand, it is because dataPts is not an array and cannot be referenced like `dataPts[nPts].` The SIGSEGV error occurs specifically at the moment. If I attempt to access data from dataPts[anything] I get that error. – Mike Furlender Aug 17 '11 at 15:26
  • In C and C++, you can. In newer Delphis you can access it as an array too (using pointer math). In FreePascal, just declare it as `type TANNcoordArray = array[0..SomeBigNumber-1] of ANNcoord; PANNcoordArray = ^TANNcoordArray;`. Now you can GetMem memory for it and use your PANNcoordArray as an array. – Rudy Velthuis Aug 17 '11 at 18:50
  • Oh, instead of GetMem, you can use annAllocPts or whatever it is called. – Rudy Velthuis Aug 17 '11 at 18:51
  • Alright! Well, it doesn't straight out crash with that modification as long as I change `inStr.Read(p^, Size) = Size;` to `.Read(p,Size)` - not sure if that is right though... – Mike Furlender Aug 17 '11 at 22:12
  • The loop iterates the proper number of times, and dataPts gets filled up... but I cannot do ShowMessage(FloatToStr(dataPts2^[nPts])) - it crashes again. Looks like I still do not have real double values going into that array :-/ – Mike Furlender Aug 17 '11 at 22:17
  • Debug your array. The watches should show what you have in there. I do assume that the Lazarus debugger has watches? Or what is the debugger you use? – Rudy Velthuis Aug 17 '11 at 22:24
  • Oh man - starting to wonder if I will ever get this to work lol. <-data-evaluate-expression dataPts[nPts]> ^error,msg="Cannot access memory at address 0x20d2fe8" <-data-evaluate-expression dataPts> ~"type = PANNCOORDARRAY\n" ^done,value="0x20d2fe8" – Mike Furlender Aug 18 '11 at 02:33