0

The SHQueryUserNotificationState function specified at MSDN is not specified in my old shellapi.pas (32bit). This function is introduced in Vista. Searched on the net to find a Pascal implementation but doesn't find it.

In C++ (MSDN):

HRESULT SHQueryUserNotificationState(
  _Out_ QUERY_USER_NOTIFICATION_STATE *pquns
);

and:

typedef enum  { 
  QUNS_NOT_PRESENT              = 1,
  QUNS_BUSY                     = 2,
  QUNS_RUNNING_D3D_FULL_SCREEN  = 3,
  QUNS_PRESENTATION_MODE        = 4,
  QUNS_ACCEPTS_NOTIFICATIONS    = 5,
  QUNS_QUIET_TIME               = 6,
  QUNS_APP                      = 7
} QUERY_USER_NOTIFICATION_STATE;

What I already have (pascal 32bit):

function SHQueryUserNotificationState( p : Pointer ) : HRESULT; stdcall; external shell32 name 'SHQueryUserNotificationState';

Code to call it:

var
 i : LongInt;

begin
 if( SHQueryUserNotificationState( @i ) = S_OK ) then
 begin
  writeln( i );
 end;

end;

This is working perfectly (returns the expected values) but I want to know the type of the pointer. It seems to be pointer to a 32bit variable but want to know if this is correct (signed/unsigned) to avoid problems on other windows OS-es/systems (now run it on Windows 7 64bit), I want to know it to be sure. Can somebody tell me the correct implementation to this function?

Description of the function on MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762242%28v=vs.85%29.aspx

EDIT: Pointer to a DWORD?

////////////////// BELOW NOT A PART OF QUESTION ///////////////

EDIT2: (Example) code I made

Because it's required that the app can be run on OS-es lower than Vista (and is not always required), I made the function optional (dynamicly assign it). Based on the accepted answer, this is the final result:

........
type
  // See also: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762533%28v=vs.85%29.aspx
 TUserNotifcationState = (unsError,           // -. Cannot not obtain info, mostly error running on Windows lower than Vista
                          unsUnknown,         // -. Working, but unknown future feature value returned by WinAPI func.
                          unsNotPresent,      // 1. User not present, screensave active or fast user switch in progress
                          unsFullScreen,      // 2. Windows run an application in full screen mode
                          unsDirectX,         // 3. A full screen Direct3D/DirectX is running
                          unsPresentation,    // 4. Activated presentation and blocks notification and popup messages
                          unsNormal,          // 5. User runs Windows normally, accept messages and popup messages
                          unsPresentLocked,   // 6. Windows 7 and higher, present but logging in
                          unsWindowsStoreApp  // 7. Windows 8 and higher, user runs Windows Store app (metro app)
                         );
TUserNotificationStateFunc = function( var iResult : Integer ) : HRESULT; stdcall;

.............

const
  FShell32Handle : THandle = 0;


.............

function shellGetUserNoticationState() : TUserNotifcationState;
const
 fFunc   : TUserNotificationStateFunc = nil;
 bErr    : Boolean = FALSE;

var
 cResult : Integer;

begin
 Result:=unsError;
 if( bErr ) then
  Exit;

 if( NOT Assigned( fFunc )) then
 begin
  if( FShell32Handle < 0 ) then
   Exit;

  if( FShell32Handle = 0 ) then
   begin
    FShell32Handle:=LoadLibrary( shell32 );
    bErr:=( FShell32Handle <= 0 );
    if( bErr ) then
     Exit;
   end;

   try 
    fFunc:=getProcAddress( FShell32Handle, 'SHQueryUserNotificationState' );
   except
    bErr:=TRUE;
   end;

   bErr:=(( bErr ) or ( NOT Assigned( fFunc )));
 end;

 if( NOT bErr ) then
  begin
   cResult:=high( Cardinal ); // set to abnormal range
   try
    bErr:=( fFunc( cResult ) <> S_OK );
   except
    bErr:=TRUE;
   end;
 end; 

 if( bErr ) or ( cResult < 1 ) or ( cResult >= 50 {50 = future options, but not supported in this compile} ) then
  Exit;

 // Future options higher than latest Win8 additions not supported
 // in this compile. 
 if( cResult > 7 ) then
  begin
   Result:=unsUnknown;
   Exit;
  end;

 Result:=TUserNotifcationState( Byte( cResult )+1 );
end;

.............

initialization
finalization
 if( FShell32Handle > 0 ) then
   FreeLibrary( FShell32Handle );
end.

Because the Windows API function is called dynamicly, the app will always run and do not break when the API function not exists in the DLL. When running the code on a OS lower than Vista, the function returns always unsError. When running the code on a OS higher than Windows 8 or a newer future version and a new enum value in the OS is added, it returns unsUnknown.

Codebeat
  • 6,501
  • 6
  • 57
  • 99

1 Answers1

4

The declaration of this API in Delphi XE4 states that the parameter is a pointer to an Integer (actually, a var parameter of type Integer, which amounts to the same thing).

The declaration does not use Integer directly, but declares a type alias named for the enum involved:

type
  QUERY_USER_NOTIFICATION_STATE = Integer; 

function SHQueryUserNotificationState(var pquns: QUERY_USER_NOTIFICATION_STATE): HResult; stdcall;

The actual size of an enum (in C/C++) is as far as I know 'implementation dependent' and the relevant implementation in this case is Windows API. As far as I know, this means Integer, which is consistent with the Delphi XE4 declaration. Whether this means all enum's in Windows APIs are guaranteed to be of size Integer or not.... I think so.

In any event, in this specific case the Delphi XE4 API header is declared on the basis that the parameter is a reference to an Integer (32-bit signed integer).

Your use of a LongInt is exactly equivalent to using Integer. However, I would eliminate the use of an untyped pointer and use a correctly typed out or var parameter, as per the Delphi XE4 declaration.

RE: DWORD ?

Th e DWORD type would be equivalent to a Cardinal (unsigned 32-bit integer).

Since this is formally an out parameter (indicating that the API does not use any value passed to it thru this parameter) the important thing from the API's point of view is that you provide a pointer to a variable of the right size for it to write to (which is why an untyped pointer is a bad idea). In this case, 32-bits. How you interpret what the API places in those 32-bits is then down to you.

In this case since the enum has no signed values and all values involved are (comfortably) within the positive range of an Integer you could use either Integer or Cardinal without any discernible difference from the perspective of your application code.

However, for what I hope are obvious reasons, I would stick to the formal type. :)

Caveat Developor: Delphi API translations are not always 100% reliable, but unless and until proven otherwise they are at least a good starting point and I have no reason to think it is wrong in this case.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Thanks for the excellent answer! Yeah, I know the pointer is a bad idea, that's the reason I wanted to know the type. But just to play around I have used this. Glad we are not in the early days of Windows when a bad assignment automatic results in a blue screen ;-). Finally I made a cardinal of it, see also EDIT2 with example code added to the question. Thanks again for the quick reply, cheers! – Codebeat Aug 24 '15 at 04:23
  • Changed it to Integer because of possible future changes, like your example code. Thanks. – Codebeat Aug 24 '15 at 04:47
  • C enums on Windows are based on `int` which is a 4 byte type. So, yes, `Integer` is the correct type, as it matches C `int`. As for `Longint` it is semantically wrong. `Longint` matches C `long int`. On Windows that's also 4 bytes wide. On 64 bit *nix targets `Longint` is 8 bytes wide. Clearly not relevant to the Win32 question. But worth being clear on the difference between `Integer` and `Longint`. – David Heffernan Aug 24 '15 at 06:54
  • ;-) What do you think about my EDIT2. Because I really don't understand why people downvote. What's wrong with the question anyway? – Codebeat Aug 24 '15 at 12:17
  • @Erwinus - despite your comment, unsError will have the value 0, not -1, since a Delphi enum always starts with ordinal value 0 (unless specific values are assigned). You can however explicitly assign values to enum members to get the values you want and you have to if you do not want the compiler to determine these values. Tip: If you stop explicitly assigning values the compiler takes over by incrementing by one for all subsequent enum members until your next explicit assignment in that enum. – Deltics Aug 24 '15 at 19:50
  • @Erwinus - examples of explicit enum values in this question/answer (along with a word of cuation re limitations of this technique w.r.t RTTI): http://stackoverflow.com/questions/1420562/why-do-i-get-type-has-no-typeinfo-error-with-an-enum-type – Deltics Aug 24 '15 at 19:51
  • I know that it starts with zero, that's why the TUserNotifcationState( Byte( cResult )+1 ); ;-) cast , see also 'return'/result statement. -1 and 0 is in the comment description is to show that the returned enum value of the windows API doesn't use this. So if the windows API returns 1 it will be 1+1 = 2 = unsNotPresent. As stated in the comment description this is correct. When the Windows API function returns a value lower than 1 it is not correct. – Codebeat Aug 24 '15 at 20:09
  • Yes, exactly - that cast is ugly and non-obvious and only necessary because of the enum mapping and behaviour (not to mention extremely misleading documentation... Ord(unsError) = 0 not -1 as 'documented'). All of which is unnecessary imho. Yield the UNS value in a var Integer param with constants (not an enum) for the API enum values known at compile time - consumer code can then support other values (no reason it shouldn't). The function return should be simply TRUE/FALSE. But, it's your code and you have to live with it. You asked what we thought. Now you know. :) – Deltics Aug 24 '15 at 23:57
  • Thanks for the comment. Maybe I have to change the documentation. There are three different states so you can't do it with a boolean. I need an unknow result anyway because of possible additions (two additions after Vista for example), but unknown is not an error, it is simply not implemented. That's why there is a range check between 1 and 50, all other values will be invalid/abnormal. To avoid confusion and conflicts with a later version of Delphi I decided to do it this way, return a pascal type instead of ints, like Delphi mostly do. ....... – Codebeat Aug 25 '15 at 12:38
  • ...... Because the WINAPI func does not have an error constant, I needed to define an error state (and an unknow state). To not disturb the order of the enum values like as the WINAPI func results, I defined two identifiers (that doesn't change for sure in future) before all known states and not after it. New additions can be added after the latest identifier in range so it doesn't disturb the ord values. That's why this way is chosen, with respect to the future. The way you want it to do, with two results, is not Delphi like and you need always a variable to get the final/actual result. – Codebeat Aug 25 '15 at 12:38
  • This makes no sense and is flatly wrong on one count: The WinAPI **does** have an error result - that's what the HRESULT is !! Add to that, the fact that your implementation introduces an *additional* potential error scenario which is that the API does not even exist. As for the "Delphi way" of returning results, this is simply nonesense. As well as function results, Delphi supports both **var** and **out** params so these *are* clearly part of the "Delphi way". RTL/VCL code often combines **both**. I refer you to the RTL function: **TryStrToInt()**, as an example. – Deltics Aug 25 '15 at 19:52
  • Introducing a 0 (zero) value enum for Unknown is also incredibly shortsighted - it guarantess that your API implementation will be useless if/when the underlying API starts returning enum values not known by your wrapper, since it pointlessly denies these values to the code using your implementation. Just because the API uses a C/C++ "enum", this does **not** mean that a Delphi enum is the most appropriate type to use in it's place. – Deltics Aug 25 '15 at 19:55
  • Al right, maybe I had to say "the VCL way", the property way. Have changed also documentation a bit. I don't understand your focus on the 0 and -1, skip it, I have tried to make it clear already. I don't think, when seeing additions after Vista, that MS starts to add new values as zero or below zero. Also the enum story, look at the code, there is a range check BEFORE any cast. I know that the HRESULT got an extended errorstate, i'm not interested in an errorstate, there is an error and that's not right, that's the only thing I want to know. .............. >> – Codebeat Aug 25 '15 at 22:25
  • >> ............... The WINAPI is not consequent with this, not my fault. What will happen to the var when I forgot to check the HRESULT, I dunno, strange things may happen, why is there no QUNS_ERROR instead, very simple to add because zero seems to be unused. You wrote: "the fact that your implementation introduces an additional potential error scenario which is that the API does not even exist." I'm really don't understand either, when it not exists it doesn't work, take a look at the example. You will get an unsError. At that stage I know "doesn't work, don't use it", that's fine. – Codebeat Aug 25 '15 at 22:25
  • Choosing to ignore a result is not the same as "the function doesn't return a result". **Bottom line:** Why ask for comments on your code if you aren't going to listen to sound advice ? Good luck. :shrug: – Deltics Aug 25 '15 at 22:36
  • There is a difference between advice and you want to get your right. Really appreciate the discussion but when you start to ignore why I'm doing this the way I'm doing it and there is also a code example you can take a look at, I don't get the point to get stuck at the enum thingy, I already explained it & there is a range check. Also I never said "the function doesn't return a result", that's your interpretation. There is a WAPI result and that's S_OK or another value, take a look at example code. There is no 1 way to do it right, it's just a matter of taste but that doesn't have to be wrong. – Codebeat Aug 25 '15 at 23:09