WTSRegisterSessionNotification() API to receive session switch notification

本文讨论了在不同操作系统上实现登录状态检测的方法,包括使用WTSRegisterSessionNotification API、Winlogon通知包等技术,并探讨了这些方法在Windows XP快速用户切换特性下的适用性。
 
ou might want to look at the
WTSRegisterSessionNotification() API to receive session switch notification. (consult msdn)

Comment from PeterLarsen
Date: 02/25/2003 12:58AM PST
Author Comment

But WTSRegisterSessionNotification/WM_WTSSES SION_CHANG E only works with XP right !?!

Comment from smot
Date: 02/25/2003 11:44AM PST
Comment

yes, right.

Comment from PeterLarsen
Date: 02/26/2003 01:10AM PST
Author Comment

I dont get it.
Is it really necessary to use 3 different methods to detect system login - one for Win 95/98/NT4, one for Win2000 and one for XP ???

Comment from Fallen_Knight
Date: 02/26/2003 01:33AM PST
Comment

http://www.pcmag.com/article2/0,4149,93230,00.asp

this guy did it somehow, the source for the program he wrote is availble to download. not sure how much this will help but its worth a shot. (its in C++ but i followed your link in the c++ section to here)

Assisted Answer from Salte
Date: 02/26/2003 01:36AM PST
Grade: A
Assisted Answer

Peter,

Don't you just love Microsoft?

Afraid you have to use 3 methods to detect it.

You should probably pack that in a streamlined interface.

You could try to use the other 2 methods (Win2K and WinXP methods), detect that user has changed and instead of handling it immediately you send a WM_USERCHANGED message to yourself. That way you get the message also for Win2000 and WinXP and you can use common code to handle the situation.

If you need to do different things in those 3 types of systems you probably should do the handling in each of the functions anyway. In that case you might want to make that a separate DLL and create 3 DLLs, one for Win95/98 etc and one for Win2000 and one for WinXP and at the beginning of your program test what system you are running on and load the correct DLL and call the DLL to handle the system specific stuff.

Alf

Accepted Answer from jkr
Date: 02/26/2003 05:47AM PST
Grade: A
Accepted Answer

What about using Winlogon Notification Packages? You'd basically build a Dll that exports

//Event handler for the Winlogon Logon event
VOID WLEventLogon (PWLX_NOTIFICATION_INFO pInfo)
{
    //Print the name of the handler to debug output.
    //You can replace this with more useful functionality.
    OutputDebugString (TEXT("NOTIFY:  Entering WLEventLogon./r/n"));
}

//Event handler for the Winlogon Logoff event.
VOID WLEventLogoff (PWLX_NOTIFICATION_INFO pInfo)
{
    //Print the name of the handler to debug output.
    //You can replace this with more useful functionality.
    OutputDebugString (TEXT("NOTIFY:  Entering WLEventLogff./r/n"));
}

and register it under build that dll and register it under HKEY_LOCAL_MACHINE/Software/Microsoft/Wi ndows NT/CurrentVersion/Winlogon/Notify as

DllName = mynotify.dll
Logon = "WLEventLogon"
Logoff = "WLEventLogoff"

See also http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/security/winlogon_notification_package_reference.asp ("Winlogon Notification Package Reference")

Comment from Salte
Date: 02/26/2003 06:40AM PST
Comment

Think jkr's solution might work.

You might even have those functions send a WM_USERCHANGED event to yourself - i.e. to your server's message queue, that way you can have the same interface as in Win95 etc.

If WM_USERCHANGED isn't defined when building your service just #define  (or use an enum) and use a value that isn't used by windows or you already.

Probably not WM_USER since WM_USER area is used by windows own controls. It is ok if you are sure that none of those windows ever get the WM_USERCHANGED but if they do and WM_USERCHANGE is defined equal to WM_USER or something like that they are likely to misunderstand the message.

Alf

Comment from PeterLarsen
Date: 02/26/2003 12:13PM PST
Author Comment

Thank you for your comments.

There are several ideas here and right now i'm working on a solution where i am using WM_USERCHANGED for Win95/98/ME and for WinNT/2000/XP i'm trying Winlogon Notification Packages.
I dont know whether Winlogin notification will work with XP Fast-User-Switching or not - i really hope i does.

Best regards
Peter

Assisted Answer from smot
Date: 02/26/2003 12:18PM PST
Grade: A
Assisted Answer

For XP, you can use this code I wrote some time ago:

{
  Typically, an application does not need to be notified when a session switch
  occurs. However, if the application needs to be aware when its desktop is
  current, it can register for session switch notifications. Applications that
  access the serial port or another shared resource on the computer should
  check for this. To register for a notification, use the following function:
}

  function WTSRegisterSessionNotification(
      hWnd: HWND ,    // Window handle
      dwFlags: DWORD  // Flags
      ): Bool; // Return value

{
  The registered HWND receives the message WM_WTSSESSION_CHANGE
  through its WindowProc function.

  In dwFlags you can specify:

    a) NOTIFY_FOR_THIS_SESSION. A window is notified only about the session
      change events that affect the session to which window belongs.

    b) NOTIFY_FOR_ALL_SESSIONS. A window is notified for all session change
      events.

  The action happening on the session can be found in wParam code, which may
  contain one of the following flags.

  WTS_CONSOLE_CONNECT:        A session was connected to the console session.
  WTS_CONSOLE_DISCONNECT:     A session was disconnected from the console session.
  WTS_REMOTE_CONNECT:         A session was connected to the remote session.
  WTS_REMOTE_DISCONNECT:      A session was disconnected from the remote session.
  WTS_SESSION_LOGON:          A user has logged on to the session.
  WTS_SESSION_LOGOFF:         A user has logged off the session.
  WTS_SESSION_LOCK:           A session has been locked.
  WTS_SESSION_UNLOCK:         A session has been unlocked.
  WTS_SESSION_REMOTE_CONTROL: A session has changed its remote controlled status.


  lParam contains the sessionId for the session affected.

  When your process no longer requires these notifications or is terminating,
  it should call the following to unregister its notification.

}
  function WTSUnRegisterSesssionNotification(
    hWnd: HWND // window handle.
    ): Boolean; // Result

{

  The HWND values passed to WTSRegisterSessionNotification are reference
  counted, so you must call WTSUnRegisterSessionNotification exactly the same
  number of times that you call WTSRegisterSessionNotification.

  Applications can use the WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT,
  WTS_REMOTE_CONNECT, WTS_REMOTE_DISCONNECT messages to track their state, as
  well as to release and acquire console specific resources.
}

unit Wtsapi;

interface

{ (c) By Thomas Stutz 10. April 02 }

uses
  Windows;

const
  // The WM_WTSSESSION_CHANGE message notifies applications of changes in session state.
  WM_WTSSESSION_CHANGE = $2B1;

  // wParam values:
  WTS_CONSOLE_CONNECT = 1;
  WTS_CONSOLE_DISCONNECT = 2;
  WTS_REMOTE_CONNECT = 3;
  WTS_REMOTE_DISCONNECT = 4;
  WTS_SESSION_LOGON = 5;
  WTS_SESSION_LOGOFF = 6;
  WTS_SESSION_LOCK = 7;
  WTS_SESSION_UNLOCK = 8;
  WTS_SESSION_REMOTE_CONTROL = 9;

  // Only session notifications involving the session attached to by the window
  // identified by the hWnd parameter value are to be received.
  NOTIFY_FOR_THIS_SESSION = 0;
  // All session notifications are to be received.
  NOTIFY_FOR_ALL_SESSIONS = 1;


function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
function UnRegisterSessionNotification(Wnd: HWND): Boolean;
function GetCurrentSessionID: Integer;

implementation

function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
  // The RegisterSessionNotification function registers the specified window
  // to receive session change notifications.
  // Parameters:
  // hWnd: Handle of the window to receive session change notifications.
  // dwFlags: Specifies which session notifications are to be received:
  // (NOTIFY_FOR_THIS_SESSION, NOTIFY_FOR_ALL_SESSIONS)
type
  TWTSRegisterSessionNotification = function(Wnd: HWND; dwFlags: DWORD): BOOL; stdcall;
var
  hWTSapi32dll: THandle;
  WTSRegisterSessionNotification: TWTSRegisterSessionNotification;
begin
  Result := False;
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
  if (hWTSAPI32DLL > 0) then
  begin
    try @WTSRegisterSessionNotification :=
        GetProcAddress(hWTSAPI32DLL, 'WTSRegisterSessionNotification');
      if Assigned(WTSRegisterSessionNotification) then
      begin
        Result:= WTSRegisterSessionNotification(Wnd, dwFlags);
      end;
    finally
      if hWTSAPI32DLL > 0 then
        FreeLibrary(hWTSAPI32DLL);
    end;
  end;
end;

function UnRegisterSessionNotification(Wnd: HWND): Boolean;
  // The RegisterSessionNotification function unregisters the specified window
  // Parameters:
  // hWnd: Handle to the window
type
  TWTSUnRegisterSessionNotification = function(Wnd: HWND): BOOL; stdcall;
var
  hWTSapi32dll: THandle;
  WTSUnRegisterSessionNotification: TWTSUnRegisterSessionNotification;
begin
  Result := False;
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
  if (hWTSAPI32DLL > 0) then
  begin
    try @WTSUnRegisterSessionNotification :=
        GetProcAddress(hWTSAPI32DLL, 'WTSUnRegisterSessionNotification');
      if Assigned(WTSUnRegisterSessionNotificatio n) then
      begin
        Result:= WTSUnRegisterSessionNotification(Wnd);
      end;
    finally
      if hWTSAPI32DLL > 0 then
        FreeLibrary(hWTSAPI32DLL);
    end;
  end;
end;

function GetCurrentSessionID: Integer;
 // Getting the session id from the current process
type
  TProcessIdToSessionId = function(dwProcessId: DWORD; pSessionId: DWORD): BOOL; stdcall;
var
  ProcessIdToSessionId: TProcessIdToSessionId;
  hWTSapi32dll: THandle;
  Lib : THandle;
  pSessionId : DWord;
begin
  Result := 0;
  Lib := GetModuleHandle('kernel32');
  if Lib <> 0 then
  begin
    ProcessIdToSessionId := GetProcAddress(Lib, '1ProcessIdToSessionId');
    if Assigned(ProcessIdToSessionId) then
    begin
      ProcessIdToSessionId(GetCurrentProcessId (), DWORD(@pSessionId));
      Result:= pSessionId;
    end;
  end;
end;

end.

// Example:

unit Unit1;

interface

uses
  Windows, Messages, {...},  Wtsapi;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
  { Private declarations }
    FRegisteredSessionNotification : Boolean;
    procedure AppMessage(var Msg: TMSG; var HAndled: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.AppMessage(var Msg: TMSG; var Handled: Boolean);
var
  strReason: string;
begin
  Handled := False;
  // Check for WM_WTSSESSION_CHANGE message
  if Msg.Message = WM_WTSSESSION_CHANGE then
  begin
     case Msg.wParam of
       WTS_CONSOLE_CONNECT:
           strReason := 'WTS_CONSOLE_CONNECT';
       WTS_CONSOLE_DISCONNECT:
           strReason := 'WTS_CONSOLE_DISCONNECT';
       WTS_REMOTE_CONNECT:
           strReason := 'WTS_REMOTE_CONNECT';
       WTS_REMOTE_DISCONNECT:
           strReason := 'WTS_REMOTE_DISCONNECT';
       WTS_SESSION_LOGON:
           strReason := 'WTS_SESSION_LOGON';
       WTS_SESSION_LOGOFF:
           strReason := 'WTS_SESSION_LOGOFF';
       WTS_SESSION_LOCK:
           strReason := 'WTS_SESSION_LOCK';
       WTS_SESSION_UNLOCK:
           strReason := 'WTS_SESSION_UNLOCK';
       WTS_SESSION_REMOTE_CONTROL:
           begin
             strReason := 'WTS_SESSION_REMOTE_CONTROL';
             // GetSystemMetrics(SM_REMOTECONTROL);
           end;
      else
        strReason := 'WTS_Unknown';
     end;
   // Write strReason to a Memo
   Memo1.Lines.Add(strReason + ' ' + IntToStr(msg.Lparam));
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // register the window to receive session change notifications.
  FRegisteredSessionNotification := RegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
  Application.OnMessage := AppMessage;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // unregister session change notifications.
 if FRegisteredSessionNotification then
   UnRegisterSessionNotification(Handle);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 // retrieve current session ID
 ShowMessage(Inttostr(GetCurrentSessionID ));
end;


Comment from PeterLarsen
Date: 02/26/2003 12:50PM PST
Author Comment

Thanks smot,

I dont know yet, whether i have to use WTSRegisterSessionNotification/WM_WTSSES SION_CHANG E or not.

From what i hear, i must - because i have to recreate handles to the taskbar (and desktop) when fast-switching - and fast-switching dont act like a normal login/logout.

Peter

Comment from PeterLarsen
Date: 02/28/2003 12:13PM PST
Author Comment

Hi all,

If i'm using Winlogon Notification Packages, the package must notify my running NT-service on login/logout.
How can it do that ??

Something like this : Sendmessage(aHandle, MessageIdentifier, xxxx, xxxx); ??
Regards
Peter

Comment from jkr
Date: 02/28/2003 12:20PM PST
Comment

>>Something like this : Sendmessage(aHandle, MessageIdentifier, xxxx, xxxx); ??

'SendMessage()' will only work for GUI apps - and since most services lack a GUI... :o)

I'd use either a simple event or a service control request using 'ControlService()' with a user-defined control code.

Comment from dave_p_r_b
Date: 03/21/2003 02:04AM PST
Comment

Hi,
I may have misunderstood, but have you looked into GINA? Its a huge topic, but search on msdn. Be prepared for a few remote network registry adjustments though!

D.

Comment from Salte
Date: 03/21/2003 03:48AM PST
Comment

SendMessage()' will only work for GUI apps - and since most services lack a GUI... :o)

This is actually wrong.

SendMessage() works for any process that has a message queue attached to it.

A process gets a message queue if it ever does a PeekMessage() or GetMessage call. First time this happens windows will create a message queue for the process.

If a server does GetMessage() you can do SendMessage() to send messages to it.

Of course, one problem is that SendMessage() takes a windows handle as argument and that means you must have a 'window'. This is actually quite silly and is the reason why full screen DirectX games have to create (an invisible) window at startup for the sole purpose so that they can receive windows messages.

A server can of course do the same thing. But most services do not and as such you can't use SendMessage() to send messages to a random server. But if you write your own server you can easily do so. Just have the server create an invisible window and do GetMessage() or PeekMessage() on that window at some early stage and there you got your message queue.

However, the regular way to interact with a service is to use ControlService() which is essentially exactly the same as SendMessage() wouldn't surprise me if it under the hood used exactly the same mechanism as SendMesage() and uses exactly the same message queue mechanism as SendMessage does. There is one important difference though, it won't use a window handle to identify the message queue but will instead use a handle returned by OpenService() to identify the message queue. Apart from that there's no reason why they should have a completely different type of message queue for servers compared to windows GUI programs.

In fact I believe it is a bad idea that they did it this way, since the difference between a service and a GUI program is - on the whole - very small, they're both event driven or request/response driven (some event or request comes and they need to respond to it), so I believe it is bad OS design to have two different worlds for something that is - on the whole - very much the same but then nobody ever claimed that Windows was good OS design?

Alf

Comment from PeterLarsen
Date: 03/22/2003 02:49AM PST
Author Comment

Are we talking about MSGINA.DLL - and what can it do ??

salte :
Thanks for your comment.
Actually, i do have a question related to what you are writing about. Not that i dont know how to solve it, but i dont understand why it works . I will get back very soon.

Comment from PeterLarsen
Date: 03/27/2003 01:31AM PST
Author Comment

Hi Salte,
You are talking about handles and the need of a window in order to obtain a handle.

In this NT-Service i have, i use the message "TaskBarCreated" - so i know when the taskbar is ready.
But it's not possible to receive messages within the Service - just like you said.

To get the message, i interrupt (override) a function. And this is what i dont understand :
       OldWinProc := TFNWndProc(SetWindowLong(Forms.Applicati on.Handle, GWL_WNDPROC, Longint(@NewWndProc)));

How can this be a valid handle : Forms.Application.Handle ??
I dont have any forms yet and "Forms" is only in the uses clause.

Regards
Peter

Comment from Salte
Date: 03/27/2003 01:57AM PST
Comment

well, for one thing this looks like Delphi code and not C++. In any case, let me see if I can decode it

In C++ the equivalen code would be something like:

OldWinProc = TFNWndProc(SetWindowLong(Forms::Applicat ion->Handl e, GWL_WNDPROC, static_cast<long>(NewWndProc)));

Well, the crucial point here is the 'Handle' and what value it return. Check your startup code where the application object is initialized. There's one such object per application so you should have only one such object and there should be a static pointer pointing to it.

This object is created during VCL startup. I am not sure if it sets a handle value there or what, if you - as you say - never create a window then I would believe the Handle returned from this should be some form of a NULL handle to no window.

Not sure how that works to get messages sent to such a handle though.

It is also possible that it is the application startup code that creates a window if you don't make one yourself. Handle is most likely a property and so there is a read function attached to it. If that function checks for a NULL handle and creates a window if it is NULL and then return the handle of the created window then the service will create a (presumably invisible) window that you can use to receive messages.

Alf

Comment from PeterLarsen
Date: 03/27/2003 12:26PM PST
Author Comment

Hi salte,
Thanks for your comment - it didn't answer my question, but sometimes it helps to hear what others have to say about it - and this is what i found :

Delphi create a instance of TAppilation (Application) on program start. This is done automatically.
In the constructor of TApplication a window is created : "FHandle := CreateWindow(WindowClass.lpszClassName, PChar(FTitle), WS_POPUP or WS_CAPTION or WS_CLIPSIBLINGS or WS_SYSMENU or WS_MINIMIZEBOX, GetSystemMetrics(SM_CXSCREEN) div 2, GetSystemMetrics(SM_CYSCREEN) div 2, 0, 0, 0, 0, HInstance, nil);"
but only if the program isn't a DLL or a Console.

So you was right about this  :
>>It is also possible that it is the application startup code that creates a window if you don't make one yourself. Handle is most likely a property and so there is a read function attached to it. If that function checks for a NULL handle and creates a window if it is NULL and then return the handle of the created window then the service will create a (presumably invisible) window that you can use to receive messages.

TApplication is located in unit Forms. Not the entire unit is created, only a instance of TApplication (and probably other stuff).
Thats why i could type Forms.Application.Handle. Application.handle is exactly the same.

>>Not sure how that works to get messages sent to such a handle though.
All messages are sent to the hidden window through a function called WndProc "procedure TApplication.WndProc(var Message: TMessage);"
By calling SetWindowLong the address to WndProc (in the hidden window) is changed so it point to my function (called NewWndProc) in the Service.

I guess you are an experienced programmer in C++ and since this is Delphi, you'll probably dont understand much about TApplication and similar. How does it feel not to understand anything and still be able to help ??

Best regards
Peter

Comment from Salte
Date: 03/28/2003 02:34AM PST
Comment

Well, I do know about TApplication - it is used in C++ builder also and I must confess I have some but not detailed knowledge of object pascal or delphi. A couple of years ago I had a version of C++ builder installed on my computer with the source of the VCL library included and that source was all in pascal and was rather interesting reading

Must admit that it is many years ago now though and I it is not my strongest field.

Alf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值