Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Desktop Sharing with RDP in Vista: Add a Professional Interactive Help System to your Applications

0.00/5 (No votes)
2 Oct 2008 1  
Use of the RDP API in simple classes

1.jpg

2.jpg

Introduction

Windows Vista introduced a powerful Remote Desktop Management API which is relatively unknown. This article will present an easy way to use it, in order to provide professional level help in your Vista applications.

A generic implementaton of this technique is presented in this article and is featured in my "Turbo Remote" experimental application.

Requirements

  • Windows Vista or later (if you find any way to run it on XP, tell me!). Admin rights are NOT required.
  • Knowledge of COM basics and ActiveX controls.

API Reference

The API reference is provided from Microsoft here.

Contents

  • RDP.H / RDP.CPP code for server/client implementation
  • Helpers AX.CPP and AX.H, my AX control which describes how to use an ActiveX control)
  • Test projects to demonstrate the functionality
  • Solutions / VCPROJ files for all the stuff

Features

  • Easy API
  • Allows both direct and reverse connections
  • Easy manipulation of the IRDP* classes
  • Works in x86/x64

Classes

  • RAS:: BASERDP, base for SERVER and CLIENT
  • RAS :: SERVER, implements a server
  • RAS :: CLIENT, implements a client
  • RAS :: ATTENDEE, implements an attendee
  • RAS :: CHANNEL, implements a virtual channel
  • RAS :: S_INVITATION, implements an invitation

BaseRDP

BaseRDP is a class that has common code for both server and client, like channels, data transmission, events, etc. Some useful member functions are here:

 class BASERDP
 {
 ...
 public:
   void SetWindowNotification(HWND,UINT);
   // Sends the specified message to the specified window before and after an event
   // occurs. The wParam contains the dispid of the notification
   // and the lParam the DISPPARAMS* passed (if sent before the event is processed,
   // in which case you can return 1 to prevent default processing), or NULL 
   // (if sent after the event has been processed). 
   
   void SetDataNotification(void (*)(CHANNEL*,ATTENDEE*,char*,int,void*),void*);
   // Sets a callback function to be notified when data is received at a
   // specified channel, from a specified attendee.
   // It contains the data, the size, and a custom parameter.

   void AssignChannelToAttendees(CHANNEL* c,vector<ATTENDEE*>& AssignAtts);
   // Sets the list of attendees that will send/receive data from a channel
   
   virtual CHANNEL* CreateVirtualChannel(const wchar_t* n,bool Compressed,int Priority);
   // Creates a virtual channel with the specified name, 
   // compression flag and priority (CHANNEL_PRIORITY_* flags)
   virtual HRESULT SendData(CHANNEL* C,char* d,int sz,
       bool Unc = false,vector<ATTENDEE*>* List = 0);
   // Sends data through a channel to the attendee list. If List is 0, it
   // is sent to all attendees configured previously with AssignChannelToAttendees
   virtual void OnReceiveData(CHANNEL* C,ATTENDEE* A,char* d,int sz);
   // This function calls the callback registered with SetDataNotification()

   vector<CHANNEL>& GetChannels();
   // Returns the channel list
   vector<ATTENDEE>& GetAttendees();
   // Returns the attendee list
 };

Server Implementation

The server is the application that shares the session. An application, some windows, or an entire desktop can be shared. The steps are:

  • Configure parameters and start the server.
  • Set the notification to receive events if you want to handle them manually (if you don't set a notification, there are default handlers).
  • Create an invitation ticket and (optionally) a password.
  • Send this ticket and password to the recipient in your own way.
// Create the server
RAS:: SERVER s;
// Set or retrieve connection parameters - Type (AF_INET or AF_INET6),
//        Port, Driver type (dynamic or static), Color (8/16/24 bit)
s.SetConnectionParameters(AF_INET,3389,true,24);
// Start the Server
s.Open();
// Set the shared region if desired
RECT rc = {0};
rc.right = GetSystemMetrics(SM_CXFULLSCREEN);
rc.bottom = GetSystemMetrics(SM_CYFULLSCREEN);
s->SetDesktopRegion(rc);
// Create Invitation, specifying the max number  of viewers (1)
s.Invite(0,L”password”,L"group",1);

If you wish to connect to the client instead of accepting connections (that might be needed if your PC is behind an IPv4 NAT, for example), you can use SERVER::ReverseOpen. The ticket you must pass to that function must have been created by the client and transported to you through an external interface.

The S_INVITATION* from SERVER::Invite() can be used to expire the invitation with S_INVITATION::Revoke().

Once you have viewers connected, you can use the ATTENDEE class.

 class ATTENDEE 
 {
  ...
 public:
  void SetControl(int x);
  // Sets the control for an attendee (CTRL_LEVEL_NONE,CTRL_LEVEL_VIEW,
      CTRL_LEVEL_INTERACTIVE);
  wstring& GetNane();
  // Gets the attendee's friendly name
  int GetID();
  // Gets the attendee's ID, assigned by the API.
  HRESULT Kill();
  // Disconnects the attendee
  int GetProtocol();
  wstring GetLocalIP(); 
  wstring GetPeerIP(); 
  long GetLocalPort(); 
  long GetPeerPort(); 
  // Returns TCP/IP information for the attendee };

Client Implementation

The client application is a viewer that connects to the sharer. The steps are:

  • Get the ticket from an external interface.
  • Create the client, either by passing 0 to the constructor, in which case. it creates it with CoCreateInstance, or by passing an IUnknown* to it if you ever manage to create it with OleCreate.
  • If you plan to accept connections to the client, call CLIENT:: SetReverseConnectionParameters, taking the invitation ticket (generated by the server). You must give the new connection string to the server by an external method.
  • If you plan to connect directly, call Connect() using the ticket and password.
// Create the client
RAS::CLIENT* c = new RAS::CLIENT(a);
// Use your own ActiveX container here; I use AX
TCHAR Ticket[1000] = {0};
TCHAR Pwd[1000] = {0};
if (Reverse)
{
	wstring s = ReversalString;
	wstring y = c->SetReverseConnectionParameters(s.c_str(),
	            _T("hello"),_T(""));
	SendToServerSomeHow(y.c_str());
}
else
{
	wstring wTicket = GetTicketSomeHow();
	wstring Password = GetPasswordSomeHow();
	c->Connect(Ticket.c_str(),L"A Client",Pwd.c_str());
}

Virtual Channels

The CHANNEL class implements a virtual channel (a way to share binary data between clients). You must call BASERDP::CreateVirtualChannel with the same name from both the server and clients, so as to establish a communication channel between them. The channel is distinguished by its name.

BASERDP::CreateVirtualChannel must be called before any connection has occurred. It is not possible to create virtual channels after connections have been established.

In order to set which attendees will receive data from a channel, you must call SERVER::AssignChannelToAttendees.

After that, you can call BASERDP::SendData() with the selected channel, the binary data and its size.

The implementation ensures that the data is sent and received in a proper way, and also if the data is delayed, it keeps a queue to resend them, either to all or to specified attendees.

The data comes to BASERDP::OnReceiveData(), which calls your callback.

Sharable Applications and Region

You can limit the shared region of the desktop by calling SERVER :: SetDesktopRegion(const RECT& rc).

You can limit the number of applications shared by calling:

SERVER :: ShareOnlyTheseApplications(vector<int>& pids,bool X)

The vector is an array of the process id of the applications you want to be shared, and X is true.

If X is false, all applications are shared no matter what pids contain.

You can get the list of sharable applications, their names and their status by calling:

SERVER :: GetShareableApplications
	(vector<int>& pids,vector<wstring>& names,vector<int>& ST);

Interesting Stuff

  • The Viewer must be created with CoCreateInstance() and not OleCreate().
  • The COM state must be in a single threading mode (CoInitializeEx(0, COINIT_APARTMENTTHREADED) in both the server and the client implementations). This would cost me some hours of debugging.
  • Virtual channels must be created before any connection is established.

To Do

  • Implementation of more server and client events

History

  • 29-9-2008 - Implementation of data callbacks, more member functions.
  • 26-9-2008 - Implementation of Virtual Channels, some bug fixes and Implementation of Sharable Applications
  • 24-9-2008 - First post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here