Reputation: 65
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
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