#Winprog FAQ

Table of Contents

Part 1: Introduction

By theForger

1.1 About

Visit the EFNet #Winprog Homepage for information on what exactly the big deal is. The short story is that were are a small online community of computer programmers who work primarily, but not exclusively, in the Microsoft Windows environment.

Well it's been ages since any significant work as been done on this FAQ, so it's about time it got updated. With it's new snazzy XML based source files thanks to maharg, it should be a lot easier to update.

Your humble editor,

Last Updated: $Id: index.xml 146 2004-08-11 07:02:11Z graham $.

1.2 Guide to #Winprog

This section is specifically about the EFNet channel #winprog, what you expect from it, and what is expected from you on it. Aside from GarMan's Rants (see below), there are a few fine points that you should be aware of.

It is, of course, in your best interest not to annoy or insult people. We don't have to be here, we could be doing other things. It's not perticularly rewarding to try and help someone and then have them swear in your face. It doesn't promote a helpfull attitude. What it comes down to is this, if you are civil and polite to us and others on the channel, we will feel much more inclined to help. If you are trying to be as polite as possible and still don't get an answer, don't announce we are all stupid and storm out. Instead come back later and try again, we have schedules to and we aren't always available. Also keep in mind that since you don't know the answer, it's entirely possible that whoever is currently active on the channel doesn't know it either.

1.2.1 GarMan's Rants

By GarMan

If you don't read this we reserve the right to yell at you.

  • DO NOT ask to ask!!
    Instead of asking to ask a question, just ask the question and save time. If you ask to ask there are several possible results:
    1. nobody will answer.
    2. somebody will tell you not to ask to ask but to just ask the question already.
    3. somebody will insult you.
    As you can see it simply in your best interest to ask the question outright.
  • DO NOT say "My code doesn't work."
    You can't expect us to know what's wrong and be able to help.
  • DO be as specific as possible.
    Tell us the specific error you got, or what went wrong. But don't paste more than one line of code unless you are asked to.
  • DO your own debugging FIRST.
    Don't expect us to do your debugging for you. You are expected to check for things like null pointers, memory allocation, return and error values etc... before you ask for help. This just goes without saying (or at least it should).
  • DO learn the basic before the advanced stuff
    Learn C before trying to learn the Win32 API, and learn C++ before trying to learn MFC. It is also the opinion of some that you should have a firm understanding of the Win32 API before attempting to learn MFC. Without it you will not be able to get the most out of windows.

1.3 More information

1.4 How can I help?

If you want to help there are a number of things you can do: you can send in corrections for spelling or coding errors that we have missed in this file (although code errors are much more likely to get fixed than spelling or grammatical errors, I'm lazy); you can improve or clearify an article if you think it needs it; you can submit a question if you see it being asked frequently on the channel and, even better, submit the answer as well; and you can come on down to #winprog and spread your knowledge around.

If you struggle for days on solving some problem of yours, what better way to celebrate than to send in how you did it and prevent others from making the same mistake.

If you aren't experianced enough to help others quite yet there are some things you can do to help us help you:

  1. Install and use your online help. This is not an option.
  2. Learn how to use the debugger that came with your compiler, how to set breakpoints and how to inspect variables at run-time.

Part 2: General Questions

2.1 READ THIS: How to RTFM

One day a Novice came to the Master.
"Master," he said, "How is it that I may become a Writer of Programs?"
the Master looked solemly at the Novice.
"Have you in your possession a Compiler of Source Code?" the Master asked.
"No," replied the Novice.
the Master sent the Novice on a quest to the Store of Software.

Many hours later the Novice returned.
"Master," he said, "How is it that I may become a Writer of Programs?"
the Master looked solemly at the Novice.
"Have you in your possession a Compiler of Source Code?" the Master asked.
"Yes," replied the Novice.
the Master frowned at the Novice.
"You have a Compiler of Source.  What now can prevent you from becoming
   a Writer of Programs?"
the Novice fidgeted nervously and presented his Compiler of Source to
   the Master.
"How is this used?" asked the Novice.
"Have you in your possession a Manual of Operation?" the Master asked.
"No," replied the Novice.
the Master instructed the Novice as to where he could find the
   Manual of Operation.

Many days later the Novice returned.
"Master," he said, "How is it that I may become a Writer of Programs?"
the Master looked solemly at the Novice.
"Have you in your possession a Compiler of Source Code?" the Master asked.
"Yes," replied the Novice.
"Have you in your possession a Manual of Operation?" the Master asked.
"Yes," replied the Novice.
the Master frowned at the Novice.
"You have a Compiler of Source, and a Manual of Operation.  What now can
   prevent you from becomming a Writer of Programs?"

At this the Novice fidgeted nervously and presented his Manual of Operations to
   the Master.
"How is this used?" asked the Novice.
the Master closed his eyes, and heaved a great sigh.
the Master sent the Novice on a quest to the School of Elementary.

Many years later the Novice returned.
"Master," he said, "How is it that I may become a Writer of Programs?"
the Master looked solemly at the Novice.
"Have you in your possession a Compiler of Source Code, a Manual of Operation
and an Education of Elementary?" the Master asked.
"Yes," replied the Novice.
the Master frowned at the Novice.
"What then can prevent you from becomming a Writer of Programs?"

the Novice fidgeted nervously.  He looked around but could find nothing to
present to the Master.
the Master smiled at the Novice.
"I see what problem plagues you." said the Master.
"Oh great master, please tell me." asked the Novice.

the Master turned the Novice toward the door, and with a supportive hand on his
shoulder said, "Go young Novice, and Read The Fucking Manual."

And so the Novice became enlightened.
- Brook Miles

So, you've got a grade 8 education, a compiler and a manual. What now? Now you read it. Nobody expects you to read an API reference from front to back. You do however need to do some legwork of you own to find the information you need. When you come on a channel or news group and ask something like How Do I Open a File? You aren't making the greatest impression.

For example if you were to look for a function to open files....

  1. Look for help topics called "file" or "files"
  2. Look for help topics stating with the appropriate word "File"
  3. Look for help topics using combinations of related words, FileOpen, OpenFile, etc...
Once you've found the function "OpenFile" and you think to yourself, okay but how do I Create or Delete files? Well follow the same rule, and by golly there you find CreateFile and DeleteFile. Sometimes you can't guess exactly what the name is, but if you can find a related help topic they might provide pointers to the one you want. For instance there is no RenameFile, but there is a MoveFile.

How do I Send a Message to a window? SendMessage() of course

How do I Read from a File? ReadFile() of course.

How do I get a dialog item thats on my window? GetDlgItem()

How do I Get Foo? GetFoo()

How do I Set Bar? SetBar()

Are you noticing a pattern yet?

What message do I get when SOMETHING happens? Well chances are it'll be called WM_SOMETHING or similar. And as before, look up related topics and hopefully you can find links to the info you need.

After you've exhausted your creativity on making up function names to look for, then you can come and ask How do I do X? But please search sites like MSDN and Google Groups first. They provide much more information on demand then we on a channel possibly can, we have work to do as well and how much time we have to help you varies. Never ask about something before looking it up. even if you don't understand it completely, you will be able to ask about what you don't understand instead of some vague Help! query. We don't memorise this stuff so if you ask for help there's a good chance we will have to look it up ourselves for specifics, and if we find out that you could have answered your own question by simply reading the help file we will be less eager to rush to your aid the next time.

2.2 Questions you may NOT ask.

2.3 How do I use sockets/winsock

That's a pretty big question... check out the following links:

2.4 What about MFC?!

2.5 What about VB?!

You could try these channels:

  • #vb
  • #visualbasic
Or better yet,

2.6 Why can't I open/create file "c:\foo\bar.txt"?

By theForger

You've done everything right, double checked all the flags and parameters and it STILL doesn't work? Well probably you forgot to escape the \'s in your filename. (Don't feel bad, this is an incredably common mistake.)


2.7 How can I change a VC++ project from console to GUI after I've already created it?

The reason you would want to do this is that you accidently created a project in console mode when you ment to create it in GUI mode. This generally results in an unresolved external _main() error.

In Project Settings find the Preprocessor Definitions box and change "_CONSOLE" to "_WINDOWS". Then under the linker settings, find "/subsystem:console" and change it to "/subsystem:windows".

Part 3: Win32 API

3.1 System

3.1.1 How do I get the hInstance of my App?

By GarMan

Use the WIN32 API function GetModuleHandle() to get a HANDLE to your app's instance.

3.1.2 How do I get the filename of my App?

By Violus

Use the WIN32 API function GetModuleFileName() to get the full path and filename of your binary.

3.1.3 How do I split out parts of a file path

By GarMan

The Windows Shell includes numerous Path Handling Functions to help you.

3.2 Windows

3.2.1 How do I modify my window's style on the fly?

By mlin
and theForger

The function below allows you to modify window styles after the window has been created. It is equivalent to MFC's CWnd::ModifyStyle and CWnd::ModifyStyleEx.

It can be used for standard window styles (WS_*) by setting the last parameter to FALSE, and for the Extended windows styles, (WS_EX_*) by setting the last parameter to TRUE.

   Action: Modifies the basic styles of a window
   Parameters: hWnd - handle to window
               dwAdd - window styles to add
               dwRemove - window styles to remove
               bEx - TRUE for Extended style otherwise FALSE
   Return value: TRUE if successful, FALSE otherwise

   BOOL ModifyStyle(HWND hWnd, DWORD dwAdd, DWORD dwRemove, BOOL bEx)
      DWORD dwStyle,dwNewStyle;

      dwStyle = GetWindowLong(hWnd, (bEx ? GWL_EXSTYLE : GWL_STYLE));
      dwNewStyle = (dwStyle & (~dwRemove)) | dwAdd;
      SetWindowLong(hWnd, (bEx ? GWL_EXSTYLE : GWL_STYLE), dwNewStyle);

      SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE);

      return (GetLastError() == 0);

3.2.2 How do I know when the mouse has entered my window?

By theForger

The little pile of droppings on the sill is usually a dead give away. The crumbs on the counter are a sure sign as well.

When the mouse is moved over your window you receive WM_MOUSEMOVE messages, this isn't just for the first time the mouse enters your window, but for all following movements as well.

See below for how to keep track of the mouse entering and exiting your window.

3.2.3 How do I know when the mouse has left my window?

By theForger

Usually you do not receive any message when the mouse leaves your window. This is the way I've done it. It consists of Capturing the mouse when it enters so that we can check for when it leaves. It has been pointed out to me that this is NOT a good way to do it, and that one should use TrackMouseEvent instead. However no examples have been forthcoming, and since my headers/libs do not contain this function I cannot provide one.

   // This is a global variable.
   // If you want you can store it somewhere else as long as you can
   // access it from your window procedure.
   bool bMouseOver = false;

   // Following are the relevent changes to your main window procedure
   LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
      POINT point;
      RECT rect;

         //... handle your normal messages here ...

         case WM_MOUSEMOVE:
            GetWindowRect(hwnd, &rect);

            if(PtInRect(&rect, point)){
               if(hwnd != GetCapture()){
                  bMouseOver = true;
                  // ... This is Where you do stuff you want done when the mouse
                  // ENTERS the window ...
               // Calling ReleaseCapture in Win95 also causes WM_CAPTURECHANGED
               // to be sent.  Be sure to account for that.
                  bMouseOver = false;
                  // ... This is where you do stuff you want done when the
                  // mouse LEAVES the window ...
         case WM_CAPTURECHANGED:
            // This message means we are losing the capture for some reason
            // Either because we intentionally lost it or another window
            // stole it
               bMouseOver = false;
               // ... This is ALSO where you do stuff you want done when the
               // mouse LEAVES the window ...
            return DefWindowProc(hwnd, Message, wParam, lParam);
      return 0;

As you may have noticed there are two occasions where the mouse will leave our control, one being the user moves the mouse off, and the other if the capture is taken from us. You probably would want to write a function to do your processing so you could simply call it from either case.

3.2.4 How do I size my client area?

By theForger

The windows API calls to size windows size the frame and not the client area. This is a way I use to size based on the desired client area. It seems a little bit like a hack, but it works and I've seen a couple other people use similar things so...

   RECT rectClient, rectWindow;
   UINT windowWidth, windowHeight, clientWidth, clientHeight;

   GetWindowRect(hwnd, &rectWindow);
   GetClientRect(hwnd, &rectClient);

      On the following two lines you replace the 320, 240 with
      the actualy dimensions you want for your windows client area.
      This might be something you calculate on the fly based on
      some other factors
   clientWidth = 320;
   clientHeight = 240;

   windowWidth = ((rectWindow.right - rectWindow.left) - rectClient.right)
      + clientWidth;

   windowHeight = ((rectWindow.bottom - - rectClient.bottom)
      + clientHeight;

   SetWindowPos(hwnd, NULL, 0, 0, windowWidth, windowHeight,

Now the size returned by GetClientRect() will be the size you want, and GetWindowRect() will return a slightly larger size.

3.2.5 The truth about WM_CLOSE and WM_DESTROY

By theForger

Which comes first the Close of the Destroy? Either, the two messages are independant of each other. WM_CLOSE is sent when the user tries to close the window by clicking on the [x] button in the title bar, using Close in the window menu or hitting Alt-F4. It just so happens that the default handler for WM_CLOSE detroys the window. By handling WM_CLOSE you can make anything you want happen, ie. prompt the user before calling DestroyWindow().

DestroyWindow() sends a WM_DESTROY, but not a WM_CLOSE. WM_DESTROY is sent as the window is being destroyed, when you get this message it's a good place to clean up everything (and terminate your program with PostQuitMessage() if it's the main window). WM_DESTROY is sent to parent windows first and then children.

3.3 Controls

3.3.1 Why don't the items in my drop-down ComboBox show up?

By theForger

If you know you've successfully added items into your combobox and can select one by using the arrow keys to scroll through them but when you click on the little arrow you see nothing but a little line...

The reason is that when you specify the height of the control, you are specifying it's height when OPEN not when closed. If you made the control in the resource editor then simply use it to increase the height of the control. If you have created the control with code, the you can use SetWindowPos() to increase the height, or specify a greater height when creating it in the first place.

3.3.2 How do I get the HWND for a control on my dialog?

By GarMan

The function GetDlgItem() returns the HWND for the control. In addition some common tasks that you would like done on controls can be done directly, for example with GetDlgItemInt()/SetDlgItemInt() and GetDlgItemText()/SetDlgItemText().

3.3.3 How do I set the text of a static control? (applies to most controls)

By GarMan

To set the text of a static/button/edit control you can use either SetWindowText() or SetDlgItemText().

3.3.4 How do I enable or disable/greyout a button/control?

By GarMan

To enable or disable a control or a button, use the EnableWindow() function.

3.3.5 How do I insert line-breaks into a multiline edit control?

By GarMan

Chances are you tryed "\n" and found that it shows up as a little black box. Windows uses the "\r\n" combination to specify line breaks, that's CR-LF, or 0D 0A in hex. "Foo\r\nBlah" will place "Blah" on the line below "Foo".

3.3.6 Why do my controls look bad when I use CreateWindow to create them instead of a dialog?

By maharg

When a window is created, it is given the default System font. Dialog Boxes use the default GUI font. To make a window use the appropriate font (that does not look so ugly) one must set the windows font to the GUI font. To do this, one uses WM_SETFONT to send the default GUI font which we get from GetStockObject() The following code is a sample function that create's a window and assigns it the GUI font.

HWND CreateWindowGuiFontEx( DWORD dwStyleEx, LPTSTR szClass, LPTSTR szTitle, 
    DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hParent, 
    UINT nId,HINSTANCE hInstance, void *pParam)
    HWND hRet = CreateWindowEx(dwStyleEx, szClass, szTitle,
        dwStyle, X, Y, nWidth, nHeight, hParent, (HMENU)nId,
        hInstance, pParam);
    if(hRet != NULL)
        SendMessage(hRet, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), FALSE);
    return hRet;

3.3.7 Why does my dialog box close whenever I start typing new label text in a treeview with TVS_EDITLABELS set?

By timecop

The problem is that the edit control which is created to edit the treeview label has an ID of 1, which is also the same value as IDOK, commonly used for the 'OK' button. When text in that edit control changes, it sends notifications such as EN_CHANGE to the treeview, which then forwards these notifications to the dialog window. The dialog procedure thinks 'OK' has been pressed, and closes the dialog.

Solution? Use a different ID for the 'OK' button in your dialog, such as IDC_OK. (This also applies to ListView with label edit as well).

For more information you can check out this knowledge base article

3.3.8 How do I change the background/text color of an EDIT control?

By theForger

This can easily be accomplished by handling (NOT sending) the messages WM_CTLCOLOREDIT and WM_CTLCOLORSTATIC. Why the STATIC one? Well if the edit control is disabled then it sends WM_CTLCOLORSTATIC instead, so you need to be aware of which you are going to use or both.

To change the background colour you return the handle to a brush, which windows will use to paint the background of the control. To change the text color, you can use SetTextColor() on the HDC which is passed to you in the wParam parameter. You may also want to use SetBkMode() to set the text drawing mode to TRANSPARENT, otherwise your background color won't show through if you are changing it.

For example, to make the background black, you would do:

          case WM_CTLCOLOREDIT:
            return (LRESULT)GetStockObject(BLACK_BRUSH);

If your edit control is disabled, substitute WM_CTLCOLORSTATIC for WM_CTLCOLOREDIT.

But if your background is black, you probably don't want your text to be black. So we'll make it white:

                case WM_CTLCOLOREDIT:
                SetBkMode((HDC)wParam, TRANSPARENT);
                SetTextColor((HDC)wParam, RGB(255, 255, 255));
                return (LRESULT)GetStockObject(BLACK_BRUSH);

If you only want to change the color for a single control, you can match the ID of the control you want to change to the ID of the hwnd you get in lParam:

              case WM_CTLCOLOREDIT:
              if(IDC_OF_THE_CONTROL == GetDlgCtrlID((HWND)lParam)){
              SetBkMode((HDC)wParam, TRANSPARENT);
              SetTextColor((HDC)wParam, RGB(255, 255, 255));
              return (LRESULT)GetStockObject(BLACK_BRUSH);

If you haven't guessed by now, you can also use this to change the colors of STATIC controls, and a few others. (look up the WM_CTLCOLOR* messages in MSDN).

Part 4: Win32 API Wrappers

There are various diffreant API Wrappers for the Win32 API. Probably the most common is MFC, but WTL is popular as are other options. #winprog doesn't advocate any specific one, but we do try to answer questions related to them if we can.

4.1 WTL

According to the README from WTL 7.1:

Windows Template Library, or WTL, is a set of classes that extend ATL to support more complex user interfaces for either applications or various UI components, while maintaining the big advantage of ATL - small and fast code. WTL classes were designed to be the best and the easiest way to implement rich Win32 based UI for ATL based applications, servers, components, and controls.

WTL provides support for implementing many user interface elements, from frame and popup windows, to MDI, standard and common controls, common dialogs, property sheets and pages, GDI objects, UI updating, scrollable windows, splitter windows, command bars, etc. The WTL classes are mostly templated and use minimal instance data and inline functions. They were not designed as a framework, so they do not force a particular application model, and can accommodate any. The classes do not use hooks or thread local storage, so they have no restrictions that those techniques impose. They also have no inter-dependencies and can be freely mixed with straight SDK code. In summary, WTL delivers very small and efficient code, very close in size and speed to SDK programs, while presenting a more logical, object oriented model to a programmer.

4.1.1 Where can I get WTL?

You can download it from Microsoft.

4.1.2 What other good WTL resources can you recommend?

There is a lot of WTL stuff on the web, here is a small list of some of the more useful sites we have found.

Part 5: Tips and Tricks


By mlin

Ah yes, all you do is, before you include your header files,


And magically, your program compiles much faster and uses less memory. How?

All this does is excludes some rarely-used stuff from being processed by the compiler. Specifically, the following header files are not included when WIN32_LEAN_AND_MEAN is defined:

  • cderr.h
  • dde.h
  • ddeml.h
  • dlgs.h
  • lzexpand.h
  • mmsystem.h
  • nb30.h
  • rpc.h
  • shellapi.h
  • winperf.h
  • winsock.h, mswsock.h, or winsock2.h
  • wincrypt.h
  • commdlg.h
  • winspool.h
  • ole.h or ole2.h
This information is found in windows.h lines 132-165

Note that if you want to have the lean and mean advantages, but you need to use something that is excluded in it, such as the Shell API (maybe for system tray) or winsock, it's usually safe to simply #include the appropriate header file in your program after defining WIN32_LEAN_AND_MEAN.

NOTE: I know the experts out there will complain that there is no reason why WIN32_LEAN_AND_MEAN would reduce memory usage. However, I have found in my tests that it actually does reduce it slightly. If anyone has the free time to do further research, I would update this page accordingly.

5.2 windowsx.h

By mlin

Windowsx.h contains nearly a thousand macros that are extremely cool, but almost no one uses. I have not used them in the other articles on this site because so many people are infamiliar with them, and it would look wierd.

Anyway, Windowsx.h contains a bunch of #defines (note: because they are mere #defines, they do not increase the file size or memory usage of your program). These defines basically fall into one of three categories: Macro APIs, message crackers, and control message APIs. None of these provide any new functionality, but they are present for your benefit (and the benefit is great).

5.2.1 Macro APIs

Macro APIs are macros that basically do repetetive code for you. For instance, take subclassing. Normally, you would use SetWindowLong to set the window's GWL_WNDPROC, right?

   #define     SubclassWindow(hwnd, lpfn)       \
              ((WNDPROC)SetWindowLong((hwnd), GWL_WNDPROC, (LPARAM)(WNDPROC)(lpfn)))

This is Windowsx.h's solution. Now, all you have to do is:


Again, there are no technical advantages, but it's more readable and it's less typing.

There are a number of these that automate common tasks. Take a look at Windowsx.h lines 18-126.

5.2.2 Message Crackers

Message Crackers are probably the real gem about Windowsx.h. When you write a WindowProc, what do you do? You make one massive switch statement that tends to become total spaghetti code for anything but the smallest of programs. Well, no more!

Let's look at a typical handler of WM_LBUTTONDOWN:

         ...// lots of cases
      case WM_LBUTTONDOWN:
         POINT pntClick = { LOWORD(lParam), HIWORD(lParam) };

         ...// lots of code here
         ...// lots of cases and a default

As you can see, with a lot of other messages in there, this could get very nasty very fast. But wait! Message crackers to the rescue!

         HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Cls_OnLButtonDown);
         ...// lots of short, 1 line HANDLE_MSGs

            return DefWindowProc(...);

   void Cls_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
         ...// lots of code here

         ...// Other message handlers

Not only is your WndProc much more manageable - but the message wParam and lParam are "cracked" into their corresponding values automatically! Using message crackers, handling messages is almost as easy to do as MFC (someone needs to make a MessageCrackerWizard add-in like ClassWizard).

Windowsx.h defines message crackers for almost every message you can think of - lines 128-941. Because of the extra function, using message crackers may increase your file size & memory usage slightly - I think it's more than worth it, though.

5.2.3 Control Message APIs

Control message APIs are kind of like message un-crackers. They take easy to understand parameters and translates them into an appropriate SendMessage.

Take, for instance, the oft encountered dillema of deleting a string from a list box.

Before Windowsx.h:

SendMessage(hwndBox,LB_DELETESTRING,(WPARAM)nIndex, 0);

After Windowsx.h:


Windowsx.h defines control message APIs for all the standard controls (I'm sure anyone who writes some for the common controls would make a lot of people very happy). Lines 943-1153.

Part 6: Errors

6.1 What does "Undefined external symbol _main" mean?

You have specified in your project settings that you wish to create a console program instead of a GUI program, and the linker is looking for the main() function.

Chances are you really wanted to create a GUI program, so you will need to either change this setting, or start a new project and specify the correct type (GUI).

If you actually wanted to write a console progam then write your main and all will be well.

6.2 RegisterClass succeeds but CreateWindow fails, what gives?

By theForger

I've seen this a couple of times, and the reason is that in the Window Procedure associated with the class you are not calling DefWindowProc for the messages you aren't handling. And thus the window never handles important messages regarding it's creation, which fails. Also if that doesn't work make sure you return 0 or NULL from WM_CREATE.


Generated by the Sandworm Article Generator XSLT Last Modified: $Id: index.xsl 146 2004-08-11 07:02:11Z graham $.

code NORTH Software Owl Island Help Desk Software and Live Chat Software

Copyright © 2009 EFNet #winprog. All rights reserved.