Remote Desktop View Lite

This system is designed to view a remote desktop's activity. It is composed of three Visual C++ .NET 2003 projects.

Quick Compress: A Compression COM Object

Quick Compress is a previous project that I have uploaded. I will briefly sum it up here. It is a ATL/COM component that performs Huffman compression and run length encoding. Both methods are written directly in assembler for optimized performance.

Huffman compression deals with minimum data redundancy by using a Huffman encoding tree.

Run length encoding deals with packing up like data into a packet containing the byte and the run length.

RemoteDesktopService and RemoteDesktop both call on it to compress and uncompress the data.

RemoteDesktopService first run length encodes the data; then it passes over the data multiple times with the Huffman encoding scheme to achieve the maximum minimum redundancy.

RemoteDesktop reverses the steps of RemoteDesktopService.

RemoteDesktopService: A Win32 Console Application that Creates an Installable Service

The service, when running, accepts TCP/IP connections and then streams the desktop out to the connection. If it weren't for MSDN and its sample code, I wouldn't have been able to have a rapid turnaround on the development of the service. Please refer to the article "Creating a Simple Win32 Service in C++" by Nigel Thompson. It is located in the section DLLs, Processes, and Threads Technical Articles. I used this program as my foundation for my service.

A Windows registration file fine tunes the service's behavior. It controls the socket connectivity parameters, the bit depth of the display, the port to allow connections, an optional password required to make a successful connection, the number of threads dedicated to updating the display, and the delay between capturing the display and sending. If no registration file is installed, the program uses intelligent defaults.

The socket connectivity parameters should not be adjusted unless you really know what you are doing. Here is a breakdown of their behavior:

MaxBlock: The number of times to block for "MinWait" microseconds on a socket for data. Default is "10"

MinWait: The number of microseconds to block for data on a socket. Blocking implies a slice of time where the socket is queried for its status. The status can indicate many states including lost connectivity and data present on the port for reading. These last two are uses I make of blocking in the code. Default is "100000"

MaxPacket: The maximum attempted send size of a data packet. In most cases, the real amount of data sent will be less, but the attempted send size will always be transmitted. The default setting is 65 Kbytes. Default is "65536"

The bit depth of the captured display. Default is "16". "BitCount" can only be the following values; changing it to another value is undefined behavior and usually causes a failure in the GDI system.

4: a 16-color gray scale palette

8: a 256-color gray scale palette (best economical setting)

16: 2^16 colors

24: 2^24 colors

32: 2^32 colors (true color)

The server port that listens for incoming connections and password.

ServerPort: Should be in the range of 1025 to 65535. The default is "8370", which is the day when Mr. Gravy entered this world.

Password: The plain text password that allows clients a connection. Default is "pass"

Delay: The amount of time in milliseconds to sleep after a screen has been sent to the client. Default is "250"

DisplayBands: The number of threads dedicated to monitoring the screen for changes and sending these onto the client. This parameter must be divisible by the number of Y pixels of the display. Default is "32"

If 'DisplayBands' are set to '4' and the height of the display is 1024, the breakdown on each thread is as follows. The more threads, the less data that each connection must send to the client.

Thread 1 monitors Y from 0-255

Thread 2 monitors Y from 256-511

Thread 3 monitors Y from 512-767

Thread 4 monitors Y from 768-1023

This service runs using the following design:

The main thread of execution listens on the port for new connections.

Upon a new connection, a packet of data is sent to the client that describes the following in the list below. The client uses this data to construct empty DIBs that will accept the servers DIB data. It also allows for maximized performance in the number of ports for the client to make successive connections. This reduces the load on any one connection for sending back DIB data to the client.

Initial port

Number of ports to connect with

The screen width, height, and bit depth

The main thread then listens for connections starting on the initial port and numbering the number of ports. This connection is then handled in a seperate thread. The thread is created suspended until all connections are made. This keeps the early connections from overloading the pipe with DIB data that will prevent connectivity timeouts. If any of the connections fail, all client connections are terminated.

All new connections are then handled in a seperate thread of execution.

Each thread is a "desktop" entity composed of GDI components.

When the threads connection to the client (the accepted socket) detects a lost connection, it ends.

In this way, multiple clients can simultaneously view the machine. A new client will connect with the server and the server will negotiate different ports for it to connect.

RemoteDesktop: A Win32 MFC SDI Application that Displays the Remote Desktop in the SDI Document and Allows Scrolling Around the Document

Choosing this type of project for this application did not come litely. When I was a brand-new Windows 95 programmer using C and the Win32 API solely, I would have balked at such a monstrosity. Back then, I wanted to write boilerplate code because learning Windows programming requires getting your hands dirty. Well, many years have passed since then and my zeal is about sharing the important part of my code with you.

This is a pretty simple SDI application. It derives from CScrollView because of the wonder that class provides for scrolling around the document. So much for handling WM_HSCROLL and WM_VSCROLL. Oh well, in the name of progress, I suppose this is all right.

To connect to the server, use the 'New Connection' toolbar button. The new connection dialog is displayed.

Enter the server name or IP.

Enter the server's negotiation port.

Enter the password (sent plain text).

In case of an error in connection, no real message is displayed. However, if you are in debug mode, you will see an error message in the debug output.

To disconnect from the server, use 'Close Connection'.

The RemoteDesktop uses the Registry to save some common settings. The Registry is not required because intelligent defaults are used. All Registry keys are stored in HKEY_CURRENT_USER\Software\GravyLabs\RemoteDesktop.

The socket connectivity parameters should not be adjusted unless you really know what you are doing. Here is a breakdown of their behavior:

MaxBlock: The number of times to block for "MinWait" microseconds on a socket for data. Default is "10"

MinWait: The number of microseconds to block for data on a socket. Blocking implies a slice of time where the socket is queried for its status. The status can indicate many states, including lost connectivity and data present on the port for reading. These last two are uses I make of blocking in the code. Default is "100000"

MaxPacket: The maximum attempted send size of a data packet. In most cases, the real amount of data sent will be less, but the attempted send size will always be transmitted. The default setting is 65 Kbytes. Default is "65536"

Host: The server name or IP address to make a connection. The default is "localhost"

Data Flow: Data Flows from the Client to the Server and Back

Client initiates a connection to the server on the server ip or name. DNS or host entries are required for name resolution.

Client sends the password, or 1 byte null character for no password.

Server receives the connection and spawns one or more threads to handle the connection.

Server sends the number of connections, bit depth, width, and height of its desktop.

Client receives the number of connections, bit depth, width, and height.

Server listens for the client's connections on the negotiated ports.

Client connects to the server on all negotiated ports.

Server, for each thread, captures a portion of the desktop and sends onto the client. The server compresses the data using the Quick Compression library.

Client, for each thread, receives the portion of desktop and acknowledges receipt. The client uncompresses the data using the Quick Compression library.

Repeat Steps 7 and 8 until the client closes the connection.

Server ends the thread that handles the clients connection.

Data Compression: How Data Compression Is Integrated into the Data Flow

If the server simply sent the desktop every time it changed, that wouldn't be very optimal at all. A few techniques have been added to get the size down.

The server, for each thread, starts with two empty DIB buffers:

Buffer 1 is the current desktop.

Buffer 2 is the last desktop.

The current portion of the desktop, for each thread, is captured and XOR'ed with the last desktop portion. This step masks out all the common bits of each desktop.

The XOR'ed portion is run length encoded to take advantage of high encodeability of the masked out bits.

The resultant portion is then Huffman compressed to achieve minimum redundancy of the data. Please refer to my other article for a breakdown of the compression and encoding techniques.

The portion of the desktop is sent to the client.

The client, for each connection thread, receives the portion.

The client then reverses the data compression.

The client XOR's with the last desktop.

The client, for each thread, updates a master list of device contexts for each portion of the display.

The client, in the main thread, interates the list of device contexts containing the portions of the desktop received from each connection, and bit blits them to the screen. This recreates the full desktop.

The key to keeping the data is the initial 2nd buffer, or the "last desktop." The code uses a DIB that is filled with the color black in both client and server applications.

For fun, you can fool around with raster codes of these original buffers. You may get some strange kaliedoscopic effects. Just put on your favorite classic rock and trance with it.

TCP/IP Connectivity: The Socket Class

There is a custom, home-grown socket class that handles all the communication from the client and server. Who doesn't like writing their own socket classes? Its a rite of passage, as far as I am concerned. It is a robust enough class that is decently lightweight and does the trick. It has solid error detection and graceful behaviors when connectivity is lost or can't be established. It simply focuses on the Berkeley subset of socket functions available on the Win32 API. I am going to leave it to the reader to break down this class.

Debug Versus Release Modes

Debug mode contains informative output for most of the system. Using the debug viewer from SysInternals is a good way to see what is going on. Other ways are to run the program in the debugger and pay attention to the 'output' tab. In the service, you will have to attach to the process to get the same thing. Services can't be run like other programs, but they can be debugged when you attach to them. Be prepared for Visual Studio to crash here from time to time. It sometimes has problems enumerating the processes. I've sent 20 error reports to Microsoft when I was deep in the development of this. Lucky for me, the program created its own report and asked me if I wanted to send it on. I enjoyed spamming Microsoft with these reports.

Building and Running the System

Extract the RDVL.zip file to a folder location that suits you best. The zip contains all source code and projects.

Load up RDVL.sln into your Visual C++ .NET 2003 Studio.

Rebuild the entire solution. After building the solution for the first time, the service will have to be installed and manually started before it will serve up desktops.

The services control panel applet of the administrative tools can be used to do this.

The 'net start' DOS command also can be used to do this.

The ATL/COM object builds and registers itself to the machine. If you redistribute the application, make sure to put the QuickCompress.dll in the application path.

You won't have to register it on an enduser machine.

The code does a smart register and unregister of this component when the client or service starts up. As long as the DLL is in the application path you will be okay.

Run the client and connect to 'localhost'; this resolves to 127.0.0.1. If you see your desktop appearing in the client window, all is working as it should.

Compression Ratios

The following chart shows some typical compression ratios using a 1280 x 960 resolution display for 1 display band. This is the most inefficient setting. A better better setting uses 16 display bands. This reduces each amount of data by a factor of 16. In debug mode, the actual amount of data that is sent for each desktop is sent to the debugger output.

Compression Rate

First Frame

Second Frame

32 bpp

About 4 MB, compression brings it down to 1 MB

With no visible change, will drop down to about 8 KB

8 bpp

About 100 KB after compression

Wth no visible change, will drop down to about 1 KB

What Is Next?

(You wondered why I called it the lite version 2.) Good question.

Integrate the controlling component into the application.

Mirror Driver

Ahh, the real deal here. This is where I am ultimately headed. I have the Windows DDK that describes how to build a graphics mirror driver.

A mirror driver simply echoes all graphical operations to the mirror display.

Integrating with a mirror driver, the key behind VNC, is what really delivers performance. Balancing this type of graphical refresh with my method ought to really deliver a punch and make this program a real contender to VNC.

Top White Papers and Webcasts

Live Event Date: March 19, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT
The 2015 Enterprise Mobile Application Survey asked 250 mobility professionals what their biggest mobile challenges are, how many employees they are equipping with mobile apps, and their methods for driving value with mobility.
Join Dan Woods, Editor and CTO of CITO Research, and Alan Murray, SVP of Products at Apperian, as they break down the results of this survey and discuss how enterprises are using mobile application management and private …

On-demand Event
Event Date: February 12, 2015
The evolution of systems engineering with the SysML modeling language has resulted in improved requirements specification, better architectural definition, and better hand-off to downstream engineering. Agile methods have proven successful in the software domain, but how can these methods be applied to systems engineering? Check out this webcast and join Bruce Powel Douglass, author of Real-Time Agility, as he discusses how agile methods have had a tremendous …