2

I'm working with Windows API and have to recreate a structure inside a Delphi record. I think I have it down, but this one was a little confusing and I need to make sure I did this right.

Here's the original C++ structure:

typedef struct RETRIEVAL_POINTERS_BUFFER {
  DWORD         ExtentCount;
  LARGE_INTEGER StartingVcn;
  struct {
    LARGE_INTEGER NextVcn;
    LARGE_INTEGER Lcn;
  } Extents[1];
} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;

Notice that there's an array struct within this struct. This is where I got lost. If I'm not mistaken, the Delphi version should look like this:

  TExtent = record
    NextVcn: Integer;
    Lcn: Integer;
  end;

  TExtents = array of TExtent;

  PRETRIEVAL_POINTERS_BUFFER = ^TRETRIEVAL_POINTERS_BUFFER;
  TRETRIEVAL_POINTERS_BUFFER = record
    ExtentCount: DWORD;
    StartingVcn: Integer;
    Extents: TExtents;
  end;

When I use this structure in the Windows API, it does appear to work. But, because of this structure array inside of the structure, I'm a little hesitant that I did this correctly. Does this look right?

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • 4
    Almost everything is already translated [`by JEDI`](http://www.koders.com/delphi/fid87C50F65617654141B73DFD6DAF36BC0B953BF75.aspx). – TLama Oct 06 '12 at 20:33
  • @TLama, I think that could be my answer right there actually, so long as "almost" isn't a factor. – Jerry Dodge Oct 06 '12 at 20:43
  • 3
    I meant almost everything from Windows API. What you need [`is of course complete`](http://www.koders.com/delphi/fid87C50F65617654141B73DFD6DAF36BC0B953BF75.aspx#L3913) there. – TLama Oct 06 '12 at 20:53

3 Answers3

6

The Extents field is a variable length array inlined in a struct. The actual struct will have ExtentCount elements. You cannot use a Delphi dynamic array here. In fact you can never use a Delphi dynamic array in interop.

So, declare it as array [0..0] just as the C code does. In order to access it you'll need to disable range checking. An actual instance of this record will have valid data in indices 0..ExtentCount-1.

For your integral types, map DWORD in C to DWORD in Delphi. And LARGE_INTEGER in C to LARGE_INTEGER in Delphi. Neither of those are the same as Delphi Integer. The former is unsigned, and the latter is 64 bits wide.

PRetrievalPointersBuffer = ^TRetrievalPointersBuffer;
TRetrievalPointersBuffer = record
  ExtentCount: DWORD;
  StartingVcn: LARGE_INTEGER;
  Extents: array [0..0] of record
    NextVcn: LARGE_INTEGER;
    Lcn: LARGE_INTEGER;
  end;
end;

The LARGE_INTEGER type is rather awkward to work with. You may prefer to declare those fields as Int64 instead.


This type of struct is invariably heap allocated. The heap allocation code has to work out the size needed to fit ElementCount items in the variable length array. If you are allocating the buffer then you'll need the inner record in a separately defined type so that you can conveniently name it to pass to SizeOf. If the API allocates then you are fine as above.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • +1, especially for your edit. I almost always give inner records a separately defined type. It makes a lot of things very easy. With the introduction of record methods, I'd add a method on the `TRetrievalPointersBuffer` type that calculates the total size for a given `ExtentCount`. – Jeroen Wiert Pluimers Oct 07 '12 at 17:09
  • 1
    @jeroen In this was I'd define the inner type inside the outer record. – David Heffernan Oct 07 '12 at 17:20
  • When you know the high bound of the array you can have it in the stack by declaring the array accordingly. Not for this specific case, but suppose you have `WCHAR Name[1]` and you know that it is at most 255 characters, then you can define the field as `Name: WCHAR[0..254]`. – Sertac Akyuz Oct 07 '12 at 23:29
  • I'll try to restate for -1... One might know the maximum possible number of elements, or one might not care the actual information but only want to know the number of elements, or one might care the information only if the element count is smaller than a defined total. There's nothing wrong with declaring this kind of struct having a definite number of array elements greater than 1, and hence have it in the stack. This is not a problem regarding the api because api accepting this kind of struct is not interested if the passed size fits the api header, but the size of the actual buffer used. – Sertac Akyuz Oct 08 '12 at 21:28
  • @Sertac In this case, you probably could use stack allocation and keep calling again and again when faced with ERROR_MORE_DATA. I'd heap allocate though. – David Heffernan Oct 08 '12 at 22:31
  • @David - Indeed, it would be too much hassle just for the sake of it... I was in disagreement with the compulsory tone with the answer previously. I still am not sure about invariableness. See for instance [this question](http://stackoverflow.com/questions/12756751/keyinformation-parameter-of-ntenumeratekey) for declaration of `KEY_NAME_INFORMATION`, honestly I don't see anything wrong with it. But I guess it's a matter of preference. – Sertac Akyuz Oct 08 '12 at 22:46
2

Defining TExtents as array of TExtent is a mistake. That's declaring it as a dynamic array, a managed reference type. What you need is a bounded array, like array [x..y] of TExtent.

This C declaration is rather strange, though, in that it's declared as an array with only one element. If you want to copy it exactly, you should declare it as array [0..0] of TExtent.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
2

StartingVcn, NextVcn and Lcn are defined as LARGE_INTEGER which is defined as follow in winnt.h:

typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER;

Depending on the version of Delphi you are using, the structure you defined might not work. LARGE_INTEGER should be declared as follow:

LARGE_INTEGER = record
  case Integer of
    0: ( LowPart: DWORD;
      HighPart: Longint; );
    1: ( QuadPart: LONGLONG);
end;

LONGLONG is actually just an Int64. You can access this structure using LowPart and Highpart or QuadPart.

Hope this helps.

Eric Fortier
  • 760
  • 1
  • 7
  • 16