How to write Delphi apps in a Terminal Server environment, a primer.
(Infos derived from carefully harvesting the net)
The Terminal Server environment (TS) it's quite common nowadays; it's often used to
WAN-enable older apps, something like retro-fitting an app.
It's also useful when no decent speed lines are available.
TS is an extension of the Windows NT 4.0 product line and is a multi user kernel in
which the 100% processing is done on the central server and only the desktop is
transferred to the terminal client (also called "Super thin client" by Microsoft).
It's not a technology invented by Microsoft (;-), but they did integrate it in the
OS.
There are three part of a terminal server system:
1. A multi user kernel, running on a strong (in terms of resources) central server
2. A protocol for the transfer of mouse and key board event from terminal client to
the server and desktop from terminal server to the client.
3. A super thin client
Windows TS provides users access to 16-32 bit Windows based application from any of
the following types of desktops:
1. Low cost hardware (Windows based terminals)
2. Any existing Windows desktop operating system such as Windows 95 or Microsoft
Windows NT Workstation (running the 32 bit Terminal server client as a window
within the local desktop environment).
3. Old 16 bit Windows-based desktops running the Windows 3.11 operating system
(running the 16 bit Terminal Server client as a window within the local desktop
environment).
4. X-based terminals, Apple Macintosh, MS-DOS, Networked computers, and UNIX based
desktops (albeit via third party add-on products).
A thin client is a network-dependent terminal capable of displaying remote
applications that run entirely on attached server. A thin client can be a PC, NC
(Network Computer), or terminal.
The key point to thin client computing is that applications run on the server- not
on the client.
An NC or PC that runs all or part of the application is not a thin client.
When writing for the Terminal Server environment (TS), we must pay more than usual
attention to some details.
If you develop a Windows application, it can be run on Terminal Server as long as
it takes care of data/files/registry informations by design, so that multiple user
settings or data don't clash with each other.
I.e., if you have an application that on starting creates a file on c:\, then this
creates a problem in Terminal Server, when multiple users run the application.
You can solve this by placing the common/log files into "c:\Documents and
Settings\\"
Likewise, if your application is using an .ini file to store user information,
consider instead using the HKEY_CURRENT_USER registry section, because all the
users will be using the same directory and will overwrite the same .ini file in the
same directory.
When using registry entries you have to do that in HKEY_CURRENT_USER, not in
HKEY_LOCAL_MACHINE; make sure your application doesn't write to the
HKEY_LOCAL_MACHINE registry section, since it's often only the Administrator who
can write to it.
Remember also that mutexes are system-wide, so if you use one (e.g. for detecting
if multiple copies of your applications are simultaneously run) it will make
impossible to run more copies of your app at the same time.
There's more information on Microsoft's website at (warning: beware of word-wrap):
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/termserv/termserv/t
erminal_services_start_page.asp
I also found the following MS infos useful:
----------------------------------------------------------------
GetCurrentProcessId
The GetCurrentProcessId function retrieves the process identifier of the calling
process.
DWORD GetCurrentProcessId(void);
Parameters
This function has no parameters.
Return Values
The return value is the process identifier of the calling process.
Remarks
Until the process terminates, the process identifier uniquely identifies
the process throughout the system.
Requirements
Client: Included in Windows XP, Windows 2000 Professional, Windows NT
Workstation, Windows Me, Windows 98, and Windows 95.
Server: Included in Windows Server 2003, Windows 2000 Server, and Windows NT Server.
Header: Declared in Winbase.h; include Windows.h.
Library: Use Kernel32.lib.
ProcessIdToSessionId
The ProcessIdToSessionId function retrieves the Terminal Services session
associated with a specified process.
1 BOOL ProcessIdToSessionId(
2 DWORD dwProcessId,
3 DWORD* pSessionId
4 );
Parameters
dwProcessId
[in] Specifies a process identifier. Use the GetCurrentProcessId function
to retrieve the process identifier for the current process.
pSessionId
[out] Pointer to a variable that receives the identifier of the Terminal
Services session under which the specified process is running. A value of
zero identifies the terminal server console session.
Return Values
If the function succeeds, the return value is a nonzero value.
If the function fails, the return value is zero. To get extended error
information, call GetLastError.
Remarks
If the calling process is not running in a Terminal Services environment,
the value returned in pSessionId is zero.
Requirements
Client: Included in Windows XP and Windows 2000 Professional.
Server: Included in Windows Server 2003 and Windows 2000 Server.
Redistributable: Requires Terminal Server 4.0 SP4 on Windows NT 4.0 SP4.
Header: Declared in Winbase.h; include Windows.h.
Library: Use Kernel32.lib.
--------------------------------------------------------
The following funcs (also found somewhere on the Net, credit where due), are also
useful to be able to detect if an application is running alone or in a Terminal
Server environment:
5
6 --------------------------------------------------------
7 unit WTSapi;
8
9 interface
10 { Public Domain, by Thomas Stutz 10 April 02 }
11
12 uses
13 Windows;
14
15 const
16 // The WM_WTSSESSION_CHANGE message notifies applications of changes in
17 // session state.
18 WM_WTSSESSION_CHANGE = $2B1;
19
20 // wParam values:
21 WTS_CONSOLE_CONNECT = 1;
22 WTS_CONSOLE_DISCONNECT = 2;
23 WTS_REMOTE_CONNECT = 3;
24 WTS_REMOTE_DISCONNECT = 4;
25 WTS_SESSION_LOGON = 5;
26 WTS_SESSION_LOGOFF = 6;
27 WTS_SESSION_LOCK = 7;
28 WTS_SESSION_UNLOCK = 8;
29 WTS_SESSION_REMOTE_CONTROL = 9;
30
31 // Only session notifications involving the session attached to by the window
32 // identified by the hWnd parameter value are to be received.
33 NOTIFY_FOR_THIS_SESSION = 0;
34 // All session notifications are to be received.
35 NOTIFY_FOR_ALL_SESSIONS = 1;
36
37 function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
38 function UnRegisterSessionNotification(Wnd: HWND): Boolean;
39 function GetCurrentSessionID: Integer;
40
41 implementation
42
43 function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
44 // The RegisterSessionNotification function registers the specified window
45 // to receive session change notifications.
46 // Parameters:
47 // hWnd: Handle of the window to receive session change notifications.
48 // dwFlags: Specifies which session notifications are to be received:
49 // (NOTIFY_FOR_THIS_SESSION, NOTIFY_FOR_ALL_SESSIONS)
50 type
51 TWTSRegisterSessionNotification = function(Wnd: HWND; dwFlags: DWORD): BOOL;
52 stdcall;
53 var
54 hWTSapi32dll: THandle;
55 WTSRegisterSessionNotification: TWTSRegisterSessionNotification;
56 begin
57 Result := False;
58 hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
59 if (hWTSAPI32DLL > 0) then
60 begin
61 try
62 @WTSRegisterSessionNotification :=
63 GetProcAddress(hWTSAPI32DLL, 'WTSRegisterSessionNotification');
64 if Assigned(WTSRegisterSessionNotification) then
65 Result:= WTSRegisterSessionNotification(Wnd, dwFlags);
66 finally
67 if hWTSAPI32DLL > 0 then
68 FreeLibrary(hWTSAPI32DLL);
69 end;
70 end;
71 end;
72
73 function UnRegisterSessionNotification(Wnd: HWND): Boolean;
74 // The RegisterSessionNotification function unregisters the specified window
75 // Parameters:
76 // hWnd: Handle to the window
77 type
78 TWTSUnRegisterSessionNotification = function(Wnd: HWND): BOOL; stdcall;
79 var
80 hWTSapi32dll: THandle;
81 WTSUnRegisterSessionNotification: TWTSUnRegisterSessionNotification;
82 begin
83 Result := False;
84 hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
85 if (hWTSAPI32DLL > 0) then
86 begin
87 try
88 @WTSUnRegisterSessionNotification :=
89 GetProcAddress(hWTSAPI32DLL, 'WTSUnRegisterSessionNotification');
90 if Assigned(WTSUnRegisterSessionNotification) then
91 Result := WTSUnRegisterSessionNotification(Wnd);
92 finally
93 if hWTSAPI32DLL > 0 then
94 FreeLibrary(hWTSAPI32DLL);
95 end;
96 end;
97 end;
98
99 function GetCurrentSessionID: Integer;
100 // Getting the session id from the current process
101 type
102 TProcessIdToSessionId = function(dwProcessId: DWORD; pSessionId: DWORD): BOOL;
103 stdcall;
104 var
105 ProcessIdToSessionId: TProcessIdToSessionId;
106 hWTSapi32dll: THandle;
107 Lib : THandle;
108 pSessionId : DWord;
109 begin
110 Result := 0;
111 Lib := GetModuleHandle('kernel32');
112 if Lib <> 0 then
113 begin
114 ProcessIdToSessionId := GetProcAddress(Lib, 'ProcessIdToSessionId');
115 if Assigned(ProcessIdToSessionId) then
116 begin
117 ProcessIdToSessionId(GetCurrentProcessId(), DWORD(@pSessionId));
118 Result:= pSessionId;
119 end;
120 end;
121 end;
122
123 end.
--------------------------------------------------------
So if you simply want to check if running on TS you can write:
124 // check if under Terminal Server
125 if (GetCurrentSessionID > 0) then
126 ShowMessage('TS detected !');
Hope all this can be useful to someone.
Comments, corrections and amplifications welcome as always at
alexza@mail.nauta.it.
|