11

When I compile the following code in Delphi XE2 for the target platform 64-bit Windows ...

function HKeyToString(_HKey: HKey): string;
begin
  case _HKey of
    HKEY_CLASSES_ROOT: result := 'HKEY_CLASSES_ROOT'; // do not translate
    HKEY_CURRENT_USER: result := 'HKEY_CURRENT_USER'; // do not translate
    HKEY_LOCAL_MACHINE: result := 'HKEY_LOCAL_MACHINE'; // do not translate
    HKEY_USERS: result := 'HKEY_USERS'; // do not translate
    HKEY_PERFORMANCE_DATA: result := 'HKEY_PERFORMANCE_DATA'; // do not translate
    HKEY_CURRENT_CONFIG: result := 'HKEY_CURRENT_CONFIG'; // do not translate
    HKEY_DYN_DATA: result := 'HKEY_DYN_DATA'; // do not translate
  else
    Result := Format(_('unknown Registry Root Key %x'), [_HKey]);
  end;
end;

... I get warnings for each of the HKEY_-Constants: "W1012 Constant expression violates subrange bounds"

I checked the declarations in Winapi.Windows (with Ctrl+Leftclick on the identifiers):

type
  HKEY = type UINT_PTR;
{...}
const
  HKEY_CLASSES_ROOT     = HKEY(Integer($80000000));

These look fine to me. Why does the compiler still think there is a problem?

dummzeuch
  • 10,975
  • 4
  • 51
  • 158
  • Sounds plausible, so the solution would be to use if statements instead? – dummzeuch Feb 01 '13 at 13:28
  • 4
    Extract from doc [`#Case_Statements`](http://docwiki.embarcadero.com/RADStudio/XE3/en/Declarations_and_Statements#Case_Statements) : ".. where selectorExpression is any expression of an ordinal type smaller than 32 bits (string types and ordinals larger than 32 bits are invalid)." – LU RD Feb 01 '13 at 13:31
  • 1
    @LURD I won't take the XE3 documentation for accurate, when it deals with 64 bit (and even CrossPlatform). It is pretty deprecated for new targets, and has not been refreshed (you have still "Linux" references, and Win32 specific descriptions - see [e.g. this page](http://docwiki.embarcadero.com/RADStudio/XE3/en/Program_Control)). But in this case, sounds like a real limitation - even if it is not mandatory, from the generated asm point of view, since in 64 bit, you can use x64 registers on the backend to check the `case`. So I guess compiler frontend was not updated for the 64 bit context. – Arnaud Bouchez Feb 01 '13 at 13:37
  • Cast the constants in the case statement to integer. But that would require conditional compiling for 32, 64 bits. – Sertac Akyuz Feb 01 '13 at 15:50
  • @SertacAkyuz How would that help? The constants don't fit into an integer when targeting 64 bit. – David Heffernan Feb 01 '13 at 16:18
  • @David - The differentiating part of the constants are in the low 32bits and I find it highly unlikely the API to change them such that they would be different for 32bits vs. 64bits. The case will be fine since the comparison will be performed in 32bits. – Sertac Akyuz Feb 01 '13 at 16:21
  • @SertacAkyuz True enough for the pre-defined keys, but what if `_HKey` is not one of the pre-defined keys? In that case it could be identified incorrectly. – David Heffernan Feb 01 '13 at 16:25
  • @David - Well, one should be careful when defining HKEYs then.. :) – Sertac Akyuz Feb 01 '13 at 16:29
  • @SertacAkyuz You don't define them. The operating system hands them to you. For example in `RegOpenKeyEx`. – David Heffernan Feb 01 '13 at 16:32
  • @David - I deleted my previous comment, figuring it was somehow cryptic. You can't use a *variable* key in a case statement, it has to be evaluated at compile time. – Sertac Akyuz Feb 01 '13 at 20:22

2 Answers2

12

On the 64 bit compiler the actual value of HKEY_CLASSES_ROOT is:

FFFFFFFF80000000

That's because the cast to Integer makes 80000000 into a negative number. And then the conversion to unsigned leads to FFFFFFFF80000000. Note that this value is correct. The declaration in the windows header file is:

#define HKEY_CLASSES_ROOT (( HKEY ) (ULONG_PTR)((LONG)0x80000000) )

and when you include the header file and inspect the value of HKEY_CLASSES_ROOT in a C++ program, it is the exact same value as for the Delphi declaration.

And then we can solve the puzzle from the Delphi documentation which states that the selectors in a case statement can only be:

any expression of an ordinal type smaller than 32 bits

You have no choice but to replace your case statement with an if statement.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I'm sorry, but *you have the choice* to use `case`, due to the limited possible values of `HKEY_*` constants. See my answer and comments above. – Arnaud Bouchez Feb 01 '13 at 17:36
  • If you want to pretend that HKEYs are 32 bits wide and run the risk of false positives with non-predefined keys then you what Arnaud says. But that's an awful way to code. Relying on implementation details and truncating 64 bit values to 32 bits. I'm appalled at the merest thought of it. – David Heffernan Feb 01 '13 at 17:41
3

HKEY=UINT_PTR is an unsigned 64 bit integer in your case, and case ... of statement seems not to handle it.

The XE2/XE3 compiler front-end still assumes it targets a 32bit platform, even if there is no technical reason for the compiler back-end not able to handle 64 bit case statements (with the classic sub register,constant; jz @... asm code generation pattern).

You can try to typecast everything to integer:

const
  HKEY_CLASSES_ROOT32 = Integer($80000000);

...

function HKeyToString(_HKey: integer): string;
begin
  case _HKey of
    HKEY_CLASSES_ROOT32: result := 'HKEY_CLASSES_ROOT'; // do not translate
 ...

or just ignore the upmost 32 bits of the _HKey value (this is the same):

function HKeyToString(_HKey: HKey): string;
begin
  case _HKey and $ffffffff of
    HKEY_CLASSES_ROOT and $ffffffff: result := 'HKEY_CLASSES_ROOT'; // do not translate
 ...

It will work as expected under Windows: due to the limited number of HKEY_* constants, I think you can just ignore the upmost 32 bits of the _HKey value, and therefore use the buggy case .. of... statement. And it will work of course for both Win32 and Win64.

I suspect even ... and $f will be enough - see all HKEY_* constants.

Last (and certainly best solution) is to use good old nested if... else if... statements:

function HKeyToString(_HKey: HKey): string;
begin
  if_HKey=HKEY_CLASSES_ROOT then
    result := 'HKEY_CLASSES_ROOT' else // do not translate
  if_HKey=HKEY_CURRENT_USER then
    result := 'HKEY_CURRENT_USER' else // do not translate
 ....

I guess the last one is preferred, and not slower, with modern pipelines CPUs.

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • The value of `HKEY_CLASSES_ROOT32` is `0000000080000000` and that is wrong. It must be `FFFFFFFF80000000`. – David Heffernan Feb 01 '13 at 13:45
  • @DavidHeffernan Of course, but since `_HKey` parameter has been redefined as `integer` in the `function HKeyToString(_HKey: integer): string;`, if will match the `HKEY_CLASSES_ROOT32` value, just ignoring the upper DWord of the original `HKey` value. It will work as expected under Windows: due to the limited number of `HKEY_*` constants, I think you can just ignore the upmost 32 bits of the `_HKey` value, and therefore use the buggy `case _HKey of...` statement. – Arnaud Bouchez Feb 01 '13 at 17:26
  • That's like ignoring the top 32 bits of a pointer. Wait until RegOpenKeyEx gives you an HKEY with high bits set. – David Heffernan Feb 01 '13 at 17:32