user3045525
user3045525

Reputation: 65

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();
  }
}

Upvotes: 0

Views: 1103

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597875

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;

Upvotes: 3

Related Questions