-1

I have a problem to translate the following C++ code to Delphi. This is the code:

char dbcc_name[1]

And this is what I think what it should be:

dbcc_name : array [0..0] of Char;

However, I know this field should return a name, and not just one character.

So, it maybe something like this:

dbcc_name: array of Char;

Now, this looks nice, but there's no way of predicting how long the name will be, and it will probably return something with a load of rubish and somewhere a #0 terminator in it, but that is -I think- not the proper way. Would it not be wise to use a pointer to this array? Like:

dbcc_name: PChar;

Thank you in advance.

ToKa
  • 80
  • 7
  • 1
    The context matters. Surely that variable is the final field of a struct. – David Heffernan Nov 10 '19 at 06:23
  • Specify on which compiler / platform you work. I saw differences between MacOS64 and MacOS32 Win32/64 in this point. Usually `PChar` works. – Schneider Infosystems Ltd Nov 11 '19 at 09:40
  • @SchneiderInfosystemsLtd No, `PChar` can never work for this scenario – David Heffernan Nov 11 '19 at 11:48
  • @DavidHeffernan: Of course, you are fully right for the variable declaration within the implementation part. PChar will be used typically in APIs for references to an array of chars. But while migrating from OSX32 to OSX64 I realized that this does not always work: https://stackoverflow.com/questions/57612931/import-ioregistryentrysearchcfproperty-from-macapi-iokit-in-delphi-for-osx64 – Schneider Infosystems Ltd Nov 12 '19 at 14:06
  • @SchneiderInfosystemsLtd Yes, I know what a `PChar` is, and how it is used for interop. However, we are not talking about pointers to character arrays. Here we have what is known in C and C++ as a variable length struct. The array is the final member of the struct, and its length is not known at compile time. We are not talking about pointers to the character array at all. – David Heffernan Nov 12 '19 at 15:28
  • @SchneiderInfosystemsLtd I'm using the Delphi XE7 32 and 64 bit compiler for both Win32 and Win64.The WinApi doesn't make a difference between both for the DEV_BROADCAST_DEVICEINTERFACE structure. However it does for Ansi or Unicode and of course record alignment. The structure members have to be filled and passed to the RegisterDeviceNotification function. – ToKa Nov 13 '19 at 19:54

2 Answers2

1

You were right the first time, only with the wrong data type. Use AnsiChar instead, which is char in C/C++:

dbcc_name: array[0..0] of AnsiChar;

In Delphi 2009+, Char is an alias for WideChar, which in C/C++ is wchar_t on Windows and char16_t on other platforms.

That being said, in C/C++, it makes sense for a 1-element array to exist in a struct when it represents variable-length data, and is the last field in the struct. In this case, the struct usually exists inside of a larger block of allocated memory. There is no array bounds checking in C/C++, the contents of an array can exceed the bounds of the array as long as it doesn't exceed the bounds of the memory that the array is allocated in. Referring to an array by name decays into a pointer to the first element. It is very common in C to exploit this to define a struct that has variable-length data embedded directly inside of it, that can be referred to by name, without having to allocate the data elsewhere in memory. This is especially useful in embedded systems with limited memory.

There are several structs in the Win32 API that use this approach for variable-length data. Raymond Chen discusses this in more detail on his blog:

Why do some structures end with an array of size 1?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you Remy. I think [this](https://schellingerhout.github.io/data%20transmission/datatransmission1/) article from Jasper Schellingerhout explains some more how to implement this in Delphi. Although i'm scratching my head about this solution. – ToKa Nov 10 '19 at 20:58
0

You will most likely use either array[0..0] of char or just char, with some caveats. The code below assumes you are using a Windows API and I make assumptions based on one specific Windows Message record that matches your description.

If you are using char dbcc_name[1] as defined in DEV_BROADCAST_DEVICEINTERFACE in C its a char in the DEV_BROADCAST_DEVICEINTERFACE_A structure, but a wchar_t in the DEV_BROADCAST_DEVICEINTERFACE_W structure. NOTE: char in C maps to AnsiChar in Delphi and wchar_t maps to char in Delphi.

With the W strucutre I declare this in Delphi as dbcc_name: char; to read, I simply use PChar(@ARecordPtr^.dbcc_name). Your C++ sample seemingly uses the A struct, a straight translation to Delphi would mean a using the A structure with AnsiChar and using PAnsiChar to read, just replace in the code above.

However, a new Delphi project will by default use the Unicode version (or W imports) of a Windows API so that is why my sample below is written for Unicode.

In my implementation I simply have it defined as char. Some developers like the array[0..0] of char syntax because it leaves a clue of variable length array at that position. It is a more accurate translation, but I find it adds little value.

Example:

PDEV_BROADCAST_DEVICEINTERFACE = ^DEV_BROADCAST_DEVICEINTERFACE;
DEV_BROADCAST_DEVICEINTERFACE = record
  dbcc_size: DWORD;
  dbcc_devicetype: DWORD; // = DBT_DEVTYP_DEVICEINTERFACE
  dbcc_reserved: DWORD;
  dbcc_classguid: TGUID;
  dbcc_name: Char; // <--- [HERE IT IS]. Use AnsiChar is using the A record instead of the W Record
end;


and to use it

procedure TFoo.WMDeviceChange(var AMessage: TMessage);
var
  LUsbDeviceName: string;
  LPDeviceBroadcastHeader: PDEV_BROADCAST_HDR;
  LPBroadcastDeviceIntf: PDEV_BROADCAST_DEVICEINTERFACE;
begin
  if (AMessage.wParam = DBT_DEVICEARRIVAL) then
  begin
    LPDeviceBroadcastHeader := PDEV_BROADCAST_HDR(AMessage.LParam);

    if LPDeviceBroadcastHeader^.dbch_devicetype = DBT_DEVTYP_DEVICEINTERFACE then
    begin 
      LPBroadcastDeviceIntf := PDEV_BROADCAST_DEVICEINTERFACE(LPDeviceBroadcastHeader);
      LUsbDeviceName := PChar(@LPBroadcastDeviceIntf^.dbcc_name); // <--- [HERE IT IS USED] Use PAnsiChar if using the A Record instead of the W Record
      ... 
    end;
  end;
end;

See more in my post on pointers and structures and for more explanation on the odd use of a single character array see the the "Records with Variable Length Arrays" section in my post on arrays and pointer math.

Jasper Schellingerhout
  • 1,070
  • 1
  • 6
  • 24