WW WW WW PPPPPPPP JJ WW WW WW PP PP JJ WW WWWW WW PP PP JJ WW WW WW WW PPPPPPPP JJ WW WW WW WW PP JJ JJ WWWW WWWW PP JJ JJ WW WW PP JJJJJ ---------------------------------------------------------------- The Windows Programmer's Journal Volume 01 Copyright 1992 by Peter J. Davis Number 01 and Mike Wallace Jan 93 ---------------------------------------------------------------- A monthly forum for novice-advanced programmers to share ideas and concepts about programming in the Windows (tm) environment. You can get in touch with the editor via Internet or Bitnet at: HJ647C at GWUVM.BITNET or HJ647C at GWUVM.GWU.EDU CompuServe: 71141,2071 or you can send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Dr. Fairfax, Va. 22032 The two GWUVM IDs are Pete's and CompuServe is Mike's. We can also be reached by phone at: (703) 503-3165. Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups, Windows for Pen Computing, Win32, and Win32S are registered trademarks of Microsoft Corporation. Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for Windows are registered trademarks of Borland International. WordPerfect is a registered trademark of WordPerfect Corporation. The Windows Programmer's Journal takes no responsibility for the content of the text within this document. All text is the property and responsibility of the individual authors. The Windows Programmer's Journal is solely a vehicle for allowing articles to be collected and distributed in a common and easy to share form. No part of the Windows Programmer's Journal may be re-published or duplicated in part or whole, except in the complete and unmodified form of the Windows Programmer's Journal, without the express written permission of each individual author. The Windows Programmer's Journal may not be sold for profit without the express written permission of the Editor, Peter J. Davis, and only then after he has obtained permission from the individual authors. Table of Contents Subject Page Author(s) ----------------------------------------------------------------- WPJ.INI ...................................... 3 Pete Davis Off Topic .................................... 6 Pete & Mike Beginner's Corner (C) ........................ 8 Pete Davis & Mike Wallace A Drag and Drop Trashcan (TPW) ............... 16 Andreas Furrer Using DDE to Communicate With Program Manager. 18 Pete Davis Implementing a Linked List in the Global Heap. 22 Mike Wallace Book Review .................................. 26 Pete Davis Last Page .................................... 28 Mike Wallace Getting in Touch with Us ..................... 29 Pete & Mike Windows Programmer's Journal Staff: Publishers ......................... Pete Davis and Mike Wallace Editor-in-Chief .................... Pete Davis Managing Editor .................... Mike Wallace Contributing Writer ................ Andreas Furrer WPJ.INI First of all, I'd like to introduce myself. My name is Pete Davis and I was the editor of the Pascal NewsLetter, a journal similar to this one which lasted 1 year for 6 issues. I unfortunately had an accident which left me unable to continue the newsletter for some time. By the time I was back in shape and ready to go, someone had picked up the newsletter and re-started it. It was nice to see that there was enough interest that it could continue without me. In the past year I have developed an interest in Windows programming using C, so I guess at this point, I'm more suited to Windows programming anyway. Well, it sure is nice to have a "magazine" again. I guess I have the same problem now that I had with the Pascal NewsLetter: What is it? A magazine? A Journal? A NewsLetter? Well, I never quite got it figured out. The Pascal NewsLetter was originally intended to be just that, a newsletter, but in its prime it was over 30 pages single-spaced, so it wasn't exactly a newsletter. What, you may ask, is the purpose of this thing anyway? Well, I love to program. I know a lot of other people out there, techno-geeks like myself, love it also. As with Pascal, I'm finding there aren't a whole lot of magazines which cover Windows programming. That's fine, it just leaves more readers for us, I suppose. Not to mention, this is free. There aren't any free ones for Windows programming (or if there are, I haven't heard of them). I like to teach people how to program and I can't think of an easier way to get such a large audience and have it all be free. In this magazine, you won't just be hearing Mike and I ramble on, but you'll be hearing from a lot of people. Not professors, not industry experts, but people like you and me, regular programmers who like what they do and want to share their ideas and experiences with us. No one gets paid and no one has to pay. That's the idea. If the initial response we got, when we first started asking people about doing this, is any indication, there are going to be a LOT of readers. That means we should also have a lot of writers. You'll find a file in this .ZIP file with the name SUBMIT.TXT. It is simple instructions on how to submit an article. There aren't any solid rules and it's easier than you'd probably expect, so please consider sending in an article. Right now, things are real open to regular columnists. I had several with the Pascal NewsLetter and their columns were very well received. Since we don't have a regular columnist for a beginners column yet, Mike and I are going to start it up ourselves, but I hope someone will try to pitch in and give us a hand with it. When I did the Pascal NewsLetter, I had a regular writer for a beginners column and it was the most popular column in the magazine. There's a reason for that. There are a lot of people trying to get started and just can't seem to get the hang of the basics. Programming Windows is a lot different than programming in DOS and it's not as easy to pick up, but with a little persistence, anyone can do it. Other columns that we'd like to see are ones that address things like C++ for Windows and Turbo Pascal for Windows. We'd like to hear from you if you want to tackle any of these. It's not a lot of work and only requires a few hours each month. Most of us can make at least that much time. The Pascal NewsLetter came out irregularly, but we'd really like to do this on a monthly basis if possible. If we don't get enough submissions, we might have to make it every two months. We should be able to let you know by the second or third issue. As far as things we'd like to see in the newsletter: * Software and book reviews, maybe even reviews of Windows related hardware. I'd like to see reviews every month. * As I mentioned before, I want a beginners column for plain C for Windows as well as one for Turbo Pascal for Windows and C++ for Windows. * As far as one-time articles, perhaps one on programming DLLs and special considerations that need to be made for writing them. * Articles on DDE and OLE programming. * Articles on network programming in Windows for Workgroups and Windows NT. * Printing text and graphics in Windows * Memory management * Using different memory models and how to handle multiple code or data segments. * Programming TCP/IP and Sockets under Windows NT * Maybe reviews of Windows NT while it's still in the pre-release stage to let people who don't have it know what it's like and how Microsoft is progressing with it. That should be enough to start with. Please take the time and try to contribute. You don't have to be an expert. In the Pascal NewsLetter, the beginner's column was written by a guy who was just learning Pascal. He did a fantastic job, and as I said earlier, it was the most popular column. I had an article submitted to me by someone who didn't speak English very well, so he wrote it in his native tongue, German, and I had it translated. I had a 16 year-old high school student who submitted a terrific article, so anyone can do it. You just need to take the time. The writing in this magazine is pretty informal. People seemed to like that about the Pascal NewsLetter. They said it was easier to relate to than regular magazines, so I'm going to stick with that format. As for the format, I'm going to try to keep it like I did with the Pascal NewsLetter, in that it's just a plain text file with Form Feed characters at page breaks. If there's a big push to go with something like the Windows Write format, that can be done too. Personally, I prefer using WordPerfect (just what I'm used to) and it makes it pretty easy for handling formatting and getting articles from different people in different formats. In fact, as far as anything about the magazine, if you have suggestions, we'd love to hear them. If you think we should change the way we're doing something, then you might not be the only one. If we get enough suggestions for things, or if it's just something we should obviously be doing then we'll make the change. Well, that's enough of my rambling on for now. Hope you guys (and gals) like the magazine. I'm open to suggestions and I love to get feedback, good and bad, about the magazine, so feel free to get in touch with me or Mike at the address on the cover page. Off Topic By Pete and Mike This is exactly what the title says, off topic. Mike and I have two computers that we want to sell, so we figured we may as well throw those in here. Both machines are identical. They are: IBM PS/2 Model 70-486 (8570-B61) Both machines have 2MB RAM and 60 MB hard drives. They come with 8512 Color VGA monitors and the IBM Enhanced Keyboards. The machines are less than 4 months old and barely used. The cost is $1,500.00 and includes shipping anywhere in the continental United States. Shipping outside of the United States is covered by the buyer. These machines are really in excellent condition. We've checked them out thoroughly and there aren't any problems with them. We'll throw in a guarantee that if there are problems with them sometime within the first month, you can return the computer for a full refund. Of course this doesn't include any damage applied after the purchase. Well, that's our little pitch for our computers. We don't normally sell computers, so hopefully this will be the only ad we throw in here for computers. Our next off-topic thing is something Mike and I discussed and both of us agreed that it wasn't something we wanted to do but felt that for the magazine's best interest, we would. We are asking for donations. Neither of us is looking to make a fortune in contributions (And I'd be willing to put money down that we don't). The idea behind it is this. We want to be able to support Windows programming with different compilers, Windows NT, Windows for Workgroups, etc. The donations would be used specifically to get the software so we could either work with these environments for our own articles or use them to test programs and articles sent in by others. Our address is at the end of the article. Send in what you believe this magazine is worth. We want to put together the best Windows magazine out there, but we're not rich, so we'll do what we can with what we have. If you do want to send a donation, see the last page for our address. Make the check payable to Pete Davis. Beginner's Corner By Pete Davis and Mike Wallace Well, since this is the first issue and we don't have a writer for the beginner's column yet, Mike and I are going to split it between the two of us. I'm a strong believer in the beginner's column because once you make the first few steps, you get hooked. Unfortunately, few books really address the beginner so someone has to help them get started. We must assume that the reader is familiar with the C language. Covering both a beginners column for C and for Windows would be far too great a task. There are several good books out for C programming and there's also the C News, which, like this magazine is a file which is available on many BBSes and the Internet. It's a great place to start learning C and I highly recommend it. Also, although formally, all C routines are called functions, we use the words procedure, function, and routine fairly interchangeably. A function tends to imply that some result is returned to the calling routine so I tend to use it in that manner. In almost every beginners book on programming, the first program written is the infamous "HELLO WORLD" program. We're going to do that here, but in a sort of round-about way. We're not just going to show you the one simple program, but we're going to go to great lengths in explaining the different parts of a Windows program first. For example, I'm going to specifically cover how Windows works and then the .DEF file which is used to tell the compiler how your code and data is supposed to be configured when it's linked. Then Mike and I are going to go in-depth into the .RC or Resource source file. The .RC file has the definitions of all the resources of your program. It has your dialog boxes, menus, icons, bitmaps, etc. We won't actually get to the "HELLO WORLD" program itself until the second issue. To a beginner, it may seem a bit overwhelming that we're going to have to cover so much just to do a "HELLO WORLD" program, but the truth is, we don't HAVE to cover all of it. When you actually see the program, you'll see how simple it really is. We'd just like to give you an overview of the parts of a Windows program first and then show you a specific example. We'd also like this article to be a sort of reference on how to create more than just the simple resources and definitions (.RC and .DEF files) for a "HELLO WORLD" program. How Windows Works Too some of you who are beyond the very basics, some of this you may already know, but it may be a good idea to read anyway. Some of this is a simplified explanation, but the point is to get the concept easy to understand. Later we can be a little more specific. Windows is what is called a non-preemptive multi-tasking environment. A preemptive multi-tasking environment is one where the operating system allocates a certain amount of time to each task. It then runs one task until it has used its share of time and then runs another task until its time has run out and so on until it gets back to the first task. Windows, on the other hand, will run a procedure from your program until the procedure is completed and then it will run a procedure from another program until it is completed. What this means is that a Windows program can take over the CPU entirely, if it wants, but that defeats the purpose of programming for a multi-tasking environment. (There is one exception, and that is DOS sessions running under Windows, which are handled in a preemptive manner, but this is not our topic and won't be discussed in this column.) In a Windows program, the initialization of the program first 'registers' itself with Windows. It does this by telling Windows a few parameters that it needs to know about the program and to set-up the main message-loop, then telling Windows where the main window procedure is. A message-loop procedure is one that accepts messages from Windows that are sent to your program. Messages in Windows are a way of telling your program what the user is doing, or a way for your program to tell Windows or some other program what to do. Windows messages usually come in the form of WM_something, where the WM_ stands for Windows Message. For example, when you first create your Main Window, one of the first messages passed to your procedure is WM_CREATE. When you receive the WM_CREATE message, you know Windows is about to create your window and if you want to initialize certain variables to be handled by that procedure, you want to do it now. Or, if you receive the WM_CLOSE message, you know that Windows is about to close your window and end your program. You may want to, at this point, ask the user if he wants to save his files or ask if he really wants to quit. Messages are the heart of the Windows multi-tasking environment and when we create our "HELLO WORLD" program, you'll see a little more clearly how this works. In your programs, you will create windows and dialog boxes. When you do that, you need to tell Windows the name and location of a procedure which is going to handle the messages used by the window or dialog box. The procedure then gets all messages pertaining to that specific window or dialog box. Each of these procedures is a lot like the main window procedure to Windows. This is unlike a regular DOS C program in which the program always controls which procedure is called next and is responsible for making its own calls. In a Windows program, you tell Windows where your procedures are and it will run them in response to certain commands and user actions. This may seem a little unclear, but all I can say is stick around until the end of the second part of the article and it may make more sense in terms of a real program. This is, essentially, how Windows works. If you have questions, please feel free to contact us in one of the ways mentioned on the first page and we will try to be more clear in the areas readers seem confused about in the second part of this article. The .DEF File (Also known as the Module Definition File) The .DEF file is what the compiler uses to find out how to handle your program as a whole and how to handle individual routines in your program. We're going to go fairly in-depth into the different things you can do in a .DEF file, but in the second part of the article, you'll see a very simple application .DEF file and see exactly what parts are basic. I will try to distinguish between the things that you always must have and then parts that are optional or rarely used. I will show each parameter, a summary of its purpose and options, followed by how it would appear in the .DEF file. The .DEF file is simply an ASCII text file that you type in with your program editor. The first parameter is the NAME. In general, this should be the same as the name of the executable. Also, in the cases of Dynamic Link Libraries (DLLs), this is replaced by the LIBRARY parameter. NAME myprog (for a regular Windows program) or LIBRARY mydll (for a DLL) The next parameter is the DESCRIPTION. This is inserted into the header of your executable, but serves no real purpose other than perhaps allowing you to keep track of version and/or copyright information. DESCRIPTION 'My first Windows Program' The EXETYPE parameter tells the compiler which environment the program will be run under. For Windows, it is simply: EXETYPE WINDOWS The next parameter is the WINSTUB program. The WINSTUB is simply the name of a program that is run in the event that someone tried to execute a Windows program under DOS. This may be familiar to you. The most common WINSTUB is the one that says, 'This program requires Microsoft Windows.' In theory this program can be almost anything you want. A programmer could, for example, have both a DOS and Windows version of his/her program with the DOS version simply being a WINSTUB in the Windows version. In a later article, I'll provide source code for a WINSTUB that will allow a user to run your Window program from DOS. This is done by running Windows with the name of the program as the parameter (i.e., the user types 'TESTPROG' and the stub from TESTPROG executes the command: 'WIN TESTPROG'). The possibilities are enormous and it would be nice at some point to see some more creative uses of the WINSTUB besides 'This program requires Microsoft Windows.' Anyway, the standard WINSTUB is a program called WINSTUB.EXE which comes with most Windows compilers. The syntax is as follows: WINSTUB 'WINSTUB.EXE' The next section covers the code and data segments. Windows has the Small, Medium, Compact and Large memory models. The memory model determines whether or not there are multiple code and/or data segments. See the following table: Model # Data Segments # Code Segments ---------------------------------------------------------- Small Single Single Compact Multiple Single Medium Single Multiple Large Multiple Multiple For now, we'll stick mostly with Small or Medium models, as they're the easiest to use. We will go into the different models in later articles to discuss the advantages and disadvantages of each and how to program for them. Each code and data segment can have different options telling Windows how to handle them. Most segments, you want to give the option MOVEABLE which means that the Windows memory manager can move the segment around in memory to make room for other things if needed. There may be occasions when you would want a segment set to FIXED. For example, if you were writing a program that was working with DOS on a fairly low level, it is useful to have things set for FIXED so DOS will always know where they are. This is, however, in the advanced arena, so we'll be avoiding it. The PRELOAD option tells Windows to automatically load the segment into memory before the program is actually executed. The LOADONCALL option tells Windows only to load the segment when it is needed. This helps keep memory available for other processes. In general, you want your main CODE and DATA segments set for PRELOAD. The DISCARDABLE option tells the Windows memory manager that it can remove that segment from memory all together when it needs more memory. The memory manager will then re-load the segment the next time it is needed. This is fine for most CODE segments where the contents of the code does not change. In DATA segments, the contents of the data will change as the program is run and because of this, you do not want it to be discardable. The discardable segments are not saved before they are discarded, so any changes made to them are lost. Making a DATA segment discardable would give unknown results. The SINGLE option tells windows tells Windows that although there may be multiple copies of the program running at once, that this segment may be shared. This is useful for a CODE segment where several copies of the same program can use the same copy of the code, thus saving memory. The DATA segment, however, would be different from one running program to the next, so the SINGLE option wouldn't really be appropriate. (Because of the way Windows handles DLLs, they data segments MUST be SINGLE. This requires special handling on the part of the programmer to handle multiple programs using the same DLL. We'll discuss this another day.) The MULTIPLE option tells Windows to create a separate copy of the segment for each copy (instance) of the program that is being executed. This is useful for DATA segments. If your CODE segment is single and your DATA segment is multiple, you can share the CODE segment and save memory, but each copy (instance) of the program will have its own data. The following is an example for a Small model program. It has one code segment and one data segment. CODE PRELOAD MOVEABLE DATA PRELOAD MOVEABLE MULTIPLE The next parameters are Heapsize and Stacksize. Heapsize refers to how much space is available for local dynamic memory allocation. If you are familiar with dynamic memory allocation in regular C programming, then this is nothing really new to you. The only real difference is in Windows you have Global and Local heaps. For now, let's just keep the Heapsize at about 4096. This is a nice safe size for the types of programs we're going to be doing for a while. The stacksize tells Windows how much space to make available for the stack. The stack is where Windows keeps a lot of it's variable declarations. It also keeps information when you call procedures and functions here. As with Heapsize, 4096 should do fine for the time being. The syntax is as follows: HEAPSIZE 4096 STACKSIZE 4096 The next section is the EXPORTS and IMPORTS sections. The exports are procedures that will be called by something other than your own program. For example, your main window procedure that receives all the messages regarding your main window is an exported procedure. Any procedure that accepts messages, for that matter, are Exported. These procedures receive their messages from Windows and are called by Windows. Each procedure exported should be followed by an @n where n is an integer number. The number is an unique index number that Windows uses to help it keep your program organized. It's not required, but it does help speed things up for Windows and I suggest you use it all the time. Imports are functions that you want to use from external sources, like a DLL, or some other source. To use those, you simply need to list them. If they come from a DLL, you need to do the name in the form of: DLLNAME.FUNCTIONNAME. EXPORTS MainWndProc @1 ADlgProc @2 IMPORTS SHELL.DRAGQUERYFILE Well, that about covers the .DEF file. As we start to use it more regularly, you'll see how the different sections affect the program. So, we'll end this section with a sample .DEF file. NAME MYPROG DESCRIPTION "My first Windows program" EXETYPE WINDOWS STUB 'WINSTUB.EXE' CODE PRELOAD MOVEABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 4096 STACKSIZE 4096 EXPORTS MainWndProc @1 The .RC File The .RC file is used to define the resources for a Windows application. Resources include icons, cursors, menus, dialog boxes, bitmaps, fonts, string tables, accelerators, custom resources and version information; in other words, the gist of a Windows program. The .RC file can contain either definitions for one or more of each of these resource types or tell Windows where to find the definition(s). I'll start with the dialog box format this issue. A dialog box is a type of window allowing the user to, for example, select an item from a list box, pick a printer, select a drive for a file directory, etc. In other words, whenever you want to display information to the user or get input, use a dialog box. The "About" box is a dialog box. The difference between a dialog box and a window is that a window is normally used for drawing graphics and a dialog box displays text ("dialog"). You define a dialog box with the DIALOG statement, which has the following format: name DIALOG [load option] [memory option] col, row, width, height [option statements] BEGIN [dialog box items] END Description of parameters: name - unique (to your program) name for the dialog box load option - determines when the dialog box is loaded into memory; if specified, must be either PRELOAD or LOADONCALL. PRELOAD tells Windows to load the dialog box when the application starts. This makes the box appear quicker, but takes up memory since it must stay in memory throughout execution of your program, even if your program never loads it (based on user input, for example). The default, LOADONCALL, tells Windows to load it when necessary. memory option - combination of FIXED, MOVEABLE and DISCARDABLE. This should normally be "MOVEABLE DISCARDABLE" (notice there's no comma). See the article "Implementing a Linked List in the Global Heap" elsewhere in this issue for a discussion of these attributes. row,col,width,height - position and size of the dialog box; col (column) and row specify the location of the upper left corner of the dialog box relative to the upper left corner of the window which called it. All dimensions are in dialog base units. option stmts - describe the dialog box; can include the STYLE, CAPTION, MENU, CLASS and FONT. These are described below. STYLE - Format "STYLE style". Valid values for style are a subset of the styles for windows. Any window style starting with WS_ or DS_ can be used (except for WS_MINIMIZEBOX and WS_MAXIMIZEBOX), and combined with "|". These are listed below: DS_LOCALEDIT - Forces memory used by dialog boxes into the application's data segment. DS_MODALFRAME - Creates a dialog box with a modal frame. DS_NOIDLEMSG - No WM_ENTERIDLE messages are sent from the dialog box if created with this style. WM_ENTERIDLE messages are normally used to alert the application that the dialog box is displayed but no user activity has happened yet. DS_SYSMODAL - System modal dialog box. This means no other window can gain the input focus until this dialog box is closed. WS_BORDER - Border on the dialog box. WS_CAPTION - Caption on the dialog box; can't be used with WS_DLGFRAME WS_CHILD - Create a child dialog box; can't be used with WS_POPUP. WS_CHILDWINDOW - Same as WS_CHILD. WS_CLIPCHILDREN - When creating a parent dialog box, specifies that child dialog boxes will be clipped at the boundary of the parent. WS_CLIPSIBLINGS - Used with WS_CHILD; keeps "sibling" dialog boxes from overlapping. WS_DISABLED - the dialog box is initially disabled (cannot receive the input focus). WS_DLGFRAME - Double border. WS_GROUP - The control can be reached via the direction keys (arrows). WS_HSCROLL - Horizontal scroll bar. WS_ICONIC - Initially iconic; used with WS_OVERLAPPED. WS_MAXIMIZE - Initially maximized. WS_MAXIMIZEBOX - Maximize box in the upper right corner. WS_MINIMIZE - same as WS_ICONIC. WS_MINIMIZEBOX - Minimize box in the upper right corner. WS_OVERLAPPED - Caption and border. WS_OVERLAPPEDWINDOW - Combination of WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU and WS_THICKFRAME (standard parent window style). WS_POPUP - Popup dialog box (can't be used with WS_CHILD). WS_POPUPWINDOW - Combination of WS_POPUP, WS_BORDER and WS_SYSMENU (standard popup window style) WS_SYSMENU - System menu. WS_TABSTOP - Specifies at which control the tab key stops. WS_THICKFRAME - Thick frame (used to size the dialog box). WS_VISIBLE - Initially visible. WS_VSCROLL - Vertical scroll bar. Example : STYLE DLGFRAME | WS_VISIBLE CAPTION - Format "CAPTION text". Gives a caption of "text" to dialog boxes defined with the WS_CAPTION style. MENU - Format "MENU menuname". Assigns menu "menuname" to the dialog box. The menu is normally defined in the .RC file. CLASS - Format "CLASS classname". Causes a class other than the class of the parent window to be used for the dialog box. FONT - Format "FONT pointsize, typeface". Determines the font size used for the dialog box, which in turn determines the sizing of every control and the dialog box itself. Example : FONT 10, "Helv" Defining Dialog Box Items: Defining the objects in a dialog box (e.g., a check box) occurs between the "BEGIN" and "END" statements. A lot of these control statements have as part of their parameter list "col, row, width, height". These give the size and location of the object, and must be integers. Some objects have a "text" field, which can be any string enclosed in quotes (e.g., "Cancel"). Most of the time, the optional "style" parameter can be either WS_TABSTOP or WS_GROUP or the two combined using "|". The "id" parameter is a unique identifier assigned by the programmer, with the exception of static text controls, which are usually given an ID value of - 1 since they are never selected. The control statements are described below: CHECKBOX - Format "CHECKBOX text, id, col, row, width, height [, style]". Defines a check box control. COMBOBOX - Format "COMBOBOX id, col, row, width, height [,style]". Defines a combo box control. This is a combination of an edit control and a drop- down list box. The optional style parameter can include any combination of WS_TABSTOP, WS_GROUP, WS_DISABLED and WS_VSCROLL. CONTROL - Format "CONTROL text, id, class, style, col, row, width, height". Specifies all forms of child window controls within a dialog box. The "class" parameter can be either "button", "combobox", "edit", "listbox", "scrollbar" or "static". The "style" parameter can be any of the allowed values for CreateWindow(). CTEXT - Format "CTEXT text, id, col, row, width, height [, style]". Defines a centered static text control. DEFPUSHBUTTON - Format "DEFPUSHBUTTON text, id, col, row, width, height [, style]". Defines the default pushbutton for a dialog box. EDITTEXT - Format "EDITTEXT id, col, row, width, height [, style]". Defines an editable text control in a dialog box. The style parameter can be any combination of WS_TABSTOP, WS_GROUP, WS_VSCROLL, WS_HSCROLL, WS_BORDER and WS_DISABLED. Text is aligned based on ES_LEFT, ES_CENTER or ES_RIGHT. GROUPBOX - Format "GROUPBOX text, id, col, row, width, height [, style]". Draws a rectangle with a title at the top left around a group of other controls. ICON - Format "ICON text, id, col, row [, style]". Places an icon in the dialog box. The "text" parameter is the name of the icon as defined elsewhere in the .RC file with an ICON statement. The only allowed style is SS_ICON. LISTBOX - Format "LISTBOX id, col, row, width, height [, style]". Places a list box in the dialog box. An example of a list box is, if you run a Windows application and select "File Open..." from the menu bar, the box that appears with a list of the filenames available for opening. Possible values for "style" are any styles allowed in CreateWindow(). LTEXT - Format "LTEXT text, id, col, row, width, height [,style]". Defines a left-justified static text control. PUSHBUTTON - Format "PUSHBUTTON text, id, col, row, width, height [, style]". Defines a pushbutton for a dialog box. The allowed styles are WS_TABSTOP, WS_DISABLED and WS_GROUP. RADIOBUTTON - Format "RADIOBUTTON text, id, col, row, width, height [, style]". Defines a radio button in a dialog box. RTEXT - Format "RTEXT text, id, col, row, width, height [,style]". Defines a right-justified static text control. SCROLLBAR - Format "SCROLLBAR id, col, row, width, height [, style]". Defines a scroll bar within a dialog box. Here is an example of the DIALOG statement that could be used for showing a list of printers and letting the user select one. It includes a list box to hold the printer names and three buttons: OK, RESET and CANCEL. IDL_PRINT DIALOG LOADONCALL MOVEABLE DISCARDABLE 78, 40, 124, 58 CAPTION "Select a Printer" STYLE WS_OVERLAPPED | WS_DLGFRAME | WS_CAPTION | WS_POPUP BEGIN LISTBOX IDL_LIST, 32, 6, 60, 27, LBS_STANDARD |WS_HSCROLL |WS_BORDER DEFPUSHBUTTON "OK", IDL_OK, 4, 40, 37, 14, WS_GROUP PUSHBUTTON "Reset", IDL_RESET, 44, 40, 37, 14, WS_GROUP PUSHBUTTON "Cancel", IDL_CANCEL, 84, 40, 37, 14, WS_GROUP END That's all for now about dialog boxes. We'll continue next month with more resource types. If you don't like the format and have an idea for a better one, drop us a note. Programming Drag&Drop with Windows 3.1 by Andreas Furrer This article describes writing applications that uses Drag&Drop. At the end of this article is an example of an application that makes the use of Drag&Drop. The program is written in Turbo Pascal for Windows (1.0), but many of the things you'll learn in this article will port to other languages. A nice new feature of Windows 3.1 is Drag&Drop (D&D). With D&D you can use the File Manager to select files and drop them onto an application to perform some action on the files (e.g., print them). In Windows 3.1 there are a lot of applications that make use of D&D. For example, if you want to read a text file you can simply pick it up in File Manager and drop it onto the window or the icon for Notepad or you can print files by dropping them onto your Print Manager. You can also use D&D for your own applications. D&D is supported by the new SHELL.DLL. There are four procedures and one new message. The declarations in Pascal are: const wm_DropFiles = $0233; procedure DragAcceptFiles(Wnd: HWnd; Accept: Bool); external 'SHELL' index 9; function DragQueryFile(Drop: THandle; FileIndex: Word; FileName: PChar;MaxChars: Word): Word; external 'SHELL' index 11; function DragQueryPoint(Drop: THandle; var Pt: TPoint): Bool; external 'SHELL' index 13; procedure DragFinish(Drop: THandle); external 'SHELL' index 12; To use D&D with your application, you first have to register at least one Window of your application to accept dropped files. This will be done by: DragAcceptFiles(HWindow,true); Now this HWindow will receive wm_DropFiles message if some files are dropped onto it. If you want to undo the registration just pass false as the second parameter to the function above. This should always be done when your window is destroyed. If someone drags some files onto your window you will receive a wm_DropFiles message. The word parameter (wparam) of this message is a handle to a global data structure. This structure contains information about the dropped files and you have to use this handle for all Drag&Drop procedures. LParam is not used with this message. Now you can get information about the dragged files. You have to use the following two functions: DragQueryFile: function DragQueryFile(Drop: THandle; FileIndex: Word; FileName: PChar;MaxChars: Word): Word; external 'SHELL' index 11; With this function you will get the filename of one of the dragged files. You have to pass the handle of the D&D structure (received by wm_DropFiles), the index of the file (0 is the first), a buffer for the filename and the size of the buffer to this function. The function will copy the filename into the buffer and return the number of copied chars. To get the number of dropped files you can use $ffff as index. DragQueryPoint: function DragQueryPoint(Drop: THandle; var Pt: TPoint): Bool; external 'SHELL' index 13; With this function you can get the position of the cursor at the moment when the files were dropped. You have to pass the handle for the D&D structure (received by wm_DropFiles), and a variable of type TPoint to this function. The return value is true, if the files were dropped in the client area of the window, otherwise (for the non-client area) it is false. At the end of your D&D procedure you have to tell Windows to release the memory of the D&D data structure. This will be done with the DragFinish function. Again you have to pass the handle of the D&D structure to this function. A simple application for demonstrating D&D is a Trashcan. The accompanying code will implement a Trashcan which will delete all files and directories you dropped into its icon. See the TRASH.PAS file. After starting Trashcan, you can easily delete files and directories. Just select the files or directories in File Manager and drag them onto the icon of trashcan. If you have selected a hidden, read-only or a system file, you will be asked if you really want to delete it. To build Trashcan, you first have to open TRASH.RC with the resource workshop and save it as TRASH.RES in the .RES format. Now you can compile TRASH.PAS and you will get TRASH.EXE. A Custom Install Program: Part I Using DDE to Communicate with Program Manager By Pete Davis This is going to be a four part article on writing an Install program for Windows applications. I haven't completed the program yet so I don't even know exactly how it's going to turn out. My main problem at this point is that I really want to have a file compression algorithm for the files to be installed. The main problem is my laziness. I'm not really in the mood to write a unzip program, but since I'm having trouble coming up with alternatives, that may be my only possibility. This program is going to be loosely based on one I wrote in the past. I can't use any of that code because it is now the property of someone else, so I have to start from scratch. There are essentially three main sections which I will discuss separately. There's the part that runs last (which I'll be discussing first) which involves telling Program Manager to create our program group(s) and application icon(s). The second part reads and interprets a SETUP.INF file to get parameters of the installation, such as what files are to be installed, what disk they're on, whether or not they're executables, what is the default directory, etc. The third part is the previously mentioned decompression routines. (If anyone has a decompression algorithm they'd like to donate, that would be terrific.) The last article will tie all these parts together and make them all work together. One thing of extra interest will be that the decompression routines will be in a DLL. When I originally wrote my first install program, the UNZIP algorithm I used required a lot of memory and it was either make it a DLL or give it it's own data segment. I still haven't spent enough time with multiple data segment programs, so I stuck with the easy (or so I thought) method, the DLL. The first part I want to discuss is using DDE to communicate with Program Manager. This was the most challenging part for me the last time I wrote an Install program, so it seems to me that it would be the part that would be most interesting for a lot of people. My first few attempt at creating program groups were pathetic cludges. My main aim at the time was just to create the program group files myself and make the appropriate modifications to the PROGMAN.INI file. This turned out to be a little more difficult than I expected and I soon abandoned it. The next thought was to just create my own program group files and install them as part of the installation process. This was a workable solution but would not allow for installing the code in anything but the default directory. About the time I was mulling over the good and bad points of the last option, I came across a little snippet about using DDE to communicate with Program Manager. After a lot of reading and looking at source code from different sources I finally got a handle one it. This, incidentally, was my first attempt with DDE so it was doubly challenging. My first attempt at the DDE was itself a bit of a cludge. Instead of trying to follow all of the normal DDE protocols, I just tried to force my commands on Program Manager without the proper acknowledgements. (Ok, so I'm a little lazy.) This was only partially successful and I soon broke down and went the whole nine yards. From my experience with it, I have only this to say: Follow the protocol. Don't try to force DDE down some applications neck, 'cause it's gonna puke. Since this article doesn't discuss DDE in general, I'm not going to go in depth about variations in DDE. That will be discussed in a later article by me or someone else (hopefully!). The way DDE with Program Manager works is something like this. You put together a string of commands and place them in a globally allocated space. A typical Program Manager command is: [CreateGroup(My Group)] You then send a WM_DDE_EXECUTE command to Program Manager and pass it the handle of your globally allocated string. That's about all there is, in theory. In practice, there's a lot that needs to be done. The segment of DDE code provided with this issue is called PMDDE.C. This contains routines which will be called by the main program, so it isn't a runable module in itself. It can be included with your own programs, however. Additionally, since I haven't finished the entire program yet, there's no way to be sure that it all works. Well, that's just a little splash of real life. If it doesn't work when I'm done, then I'll just have to fix it and provide any fixes required. I debated whether to do this article now or wait until I had the whole program together, but I decided that even if there are mistakes, the algorithm itself is still valid, and the final product will work. The best way to handle a DDE conversation is to create a window that's sole purpose is to handle your DDE conversation. In our case, and in most, the window that handles the DDE conversation is not the main window and it is usually not a window in the sense that it's visible. It is simply the procedure which handles the DDE messages that we want. In our case we'll call this procedure the DDEWndProc. The creation of this window is handled in the main program, but I'll give you an idea of what the code looks like. /* This code is executed when the main windows procedure receives the WM_CREATE message wc is a WNDCLASS variable. hDDEProc is a static HWND */ wc.style = 0; wc.lpfnWndProc = DDEWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "DDEWndProc"; RegisterClass(&wc); hDDEProc = CreateWindow("DDEWndProc", NULL, WS_CHILD, 0, 0, 0, 0 hWnd, NULL, hInstance, NULL); if (!hDDEProc) MessageBox(hWnd, "DDE Window won't come up.", "ERROR", MB_OK); That's all there is to creating this blank little window. The next thing on our list is to handle the actual DDE conversation. Under the WM_CREATE of our DDEWndProc procedure, we need to add two global atoms. These are the Application and Topic names that our conversation is supposed to deal with. When you start a DDE conversation and you want to contact a DDE server application (Program Manager in this case) so you need to use the Application and Topic. Essentially what you do is send a WM_DDE_INITIATE message with the application and topic as a parameter. (These are actually one parameter by passing the MAKELONG(Application, Topic) as a long in the message parameter. You also need to pass the handle for your DDEWndProc in this message so that Program Manager will know how to get back in touch with us. Notice that the SendMessage has a -1 for the first parameter. This tells Windows to essentially broadcast the message to all programs that are running. The DDEWndProc will handle several other messages. One of them is the WM_DDE_ACK message. This message is used to acknowledge responses from the DDE server (Program manager.) This part is not quite so intuitive as I learned. First of all, this part has to be broken up. If you're acknowledging the WM_DDE_INITIATE (which we sent first thing. Don't tell me you already forgot what I wrote in the last paragraph!) Ok, so here we are sitting around with nothing to do and BOOM, we get an acknowledgement from our WM_DDE_INITIATE. Well, it's about time. So the first thing we want to do is make sure we grab the handle of Program Manager. This is passed as the wParam of the WM_DDE_ACK message sent by Program Manager in reply to our WM_DDE_INITIATE message. With all of the other messages we send to Program Manager, we will now know exactly who to send them to and not have to broadcast all of our messages. At this point I'd like to mention one thing about DDE. There are two ways to send Windows messages. You can use the SendMessage or the PostMessage procedures. Both functions do the same thing except with SendMessage, the message is sent and execution stops until the receiver of the message has finished processing the message. With PostMessage, control returns to the calling program immediately. With DDE you use the SendMessage only for the WM_DDE_INITIATE message. For all other messages you use the PostMessage procedure. In our DDEWndProc we need to handle the WM_DDE_ACK message. This is sent by Program Manager in response to all of our messages. We're actually going to break that process into two parts, though. The first WM_DDE_ACK that we get is in response to our WM_DDE_INITIATE. When we get the acknowledgement we then have to make sure that Program Manager will handle our conversation. We also need to grab Program Manager's handle. Additionally, the lParam of each WM_DDE_ACK is probably going to have some data that it points to that we need to delete. In the WM_DDE_INITIATE response the handles for two atoms are sent back to us. In this case, they happen to be the same two atoms we sent, so we just delete them. In response to our WM_DDE_EXECUTE messages, we get a Global Memory handle that is also the same one we sent to Program Manager (you'll see this later) so we have to free it. That basically handles all the rough stuff. To send commands to Program Manager we need to use the GlobalAlloc command with the GMEM_DDESHARE option so that we can share the data with Program Manager. The data is actually just text strings that we pass to Program Manager to execute as commands. These are commands like CreateGroup, AddItem, etc.. The commands we'll use are: * CreateGroup(Group Name[, Group Path]) Where Group Name is the name of the Program Group you want to add to Program Manager. Group Path is optional and specifies the path of the group file. In our case, we'll let program manager decide where to put that. * AddItem(Program [, Program Name, IconPath, IconNum, X, Y, DefDir, HotKey, Minimize] Just adds an Program item to the group you've created. The Program is that Path and the Filename of the executable. Program Name is the name you want to give it under Program Manager. Those are the only two we're going to cover for now. The other messages we're going to handle is the WM_CREATE_PMGRP and WM_ADD_ITEM. These are custom messages that we're going to define in our .h file. The first is used to create the program group under Program Manager and the second is going to be create any items we're putting in the program group. The lParam of these messages is going to be a handle for Atoms that are going to have the names of the program group and the file names. We'll then create the entire string that we want like: "[CreateGroup(New Group)][ShowGroup(New Group)]" and pass this off to Program Manager with a WM_DDE_EXECUTE message. Notice that all commands are in brackets. Don't know why but just use 'em. That about covers the PMDDE.C. I might modify it before the fourth article in this series, but if I do I'll pass it along. Implementing a Linked List in the Global Heap By Mike Wallace The ability to implement a linked list is almost a requirement if a platform wants to get taken seriously. If you don't know what a linked list is, let me explain. Using an array to store data internally is fine if you have a good idea of how many items you'll need to store, which will determine the size of the array (which must be stated explicitly in your code prior to compiling). However, if you don't have a reasonable upper bound, you have two solutions: either make the array as large as possible and hope it always works, or allocate memory dynamically. The first solution isn't good enough. It usually means a lot of wasted memory, and in Windows, that's memory you can't afford to lose. The second solution is a bit more difficult to implement, but is the only way to go. By allocating memory dynamically (that is, at run-time), you only allocate exactly enough memory needed to hold your data. This is the method that produces a linked list, so called because the data is stored in a "list" of memory blocks (nodes), where each block holds an item of data plus another field giving the memory location of the next node in the "list", thus "linking" the data. I recently had to implement a linked list for a Windows program but could not find any good examples of how to do this, so I had to reason the problem out. It turned out to not be as difficult as I expected, but there are some things you need to keep in mind when implementing a linked list. Microsoft advises that when allocating a memory block, make it moveable and discardable. Why? Windows is more efficient if it can move your memory block around while the program is running in order to make the most of the available memory. However, a consequence of this is that you cannot use a pointer when linking two consecutive nodes in the list because Windows can (and will) move each node around in memory as it sees fit. So, I had to use a handle since that would guarantee the address stored in it (for the next node) would always be valid. The tricky part was that Windows requires a memory block to be locked when accessing a node by handle because that ensures the node won't be moved until you unlock the block. At the end of this article I've included the source code for a routine which allocates ten nodes in the global heap, stores in each node a number between 1 and 10 (the numbers are stored consecutively), then traverses the list and prints (to a file) the value stored in each node, and finally, retraverses the list and frees each node one at a time. Included with the Windows Programmer's Journal package is all the code and files needed to run the program. All of the files have a filename of "linklist", except the batch file which is run to compile the program, which is called "makelist.bat". The beginning of the function contains declarations for the variables needed to implement the linked list. The _listmarker structure is the structure of each node in the list. The LISTMARKER variable has to be a FAR pointer to the structure since the function uses the global heap. The LISTMARKER variables (tailPos, tempPos and headPos) are used to traverse the list. Finally, "fp" is a file pointer to the output file. After the function opens the output file for writing, it does a GlobalAlloc to allocate a block on the global heap for the first node. If "NULL" is returned, it means there was not enough room for the node of a size of FileMarker, so the function displays a message to that effect and returns to the calling routine. Otherwise, the allocated block is locked (so the block doesn't move while its fields are getting assigned), gets initialized and then gets unlocked. The "hMem" handle points to the first node for the duration of the function. This algorithm is repeated for each successive node, using tailPos to point to the current end of the list. The function then traverses the linked list, printing out the value of the "value" field to the output file. This is done by locking each node, performing a "fprintf" on the "value" field and then unlocking the node. The output file is then closed. Finally, the function traverses the list, freeing each node one at a time. This is done by locking each node, saving its handle to the next node, unlocking the current node and then freeing it. This algorithm is repeated until the list has been traversed. That is the entire algorithm. This is the first time I have written a linked list in Windows, so there may be more efficient methods of attaining the same ends. If you can find any, please send in your suggestions and we'll print them. void FAR PASCAL MakeList (HWND hWnd) { /* local variables */ short i; static HANDLE hMem, hMem2, hMem3; /* structure of each node in linked list */ typedef struct _listmarker { short value; HANDLE next; } ListMarker; /* declare a far pointer to the above structure */ typedef ListMarker FAR *LISTMARKER; LISTMARKER tailPos, tempPos, headPos; /* pointer to output file */ FILE *fp; /* Open output file for writing */ fp= fopen("data.out", "w"); /* Build initial linked list of the numbers 1 to 10 */ if((hMem= GlobalAlloc(GMEM_DISCARDABLE | GMEM_MOVEABLE, sizeof(ListMarker))) == NULL) { /* Not enough memory, so beep, show a message and return */ MessageBeep(0); MessageBox(hWnd, "Out of allocation memory!", "ERROR", MB_OK); return; } /* Lock the first node, save a value, set next to NULL and unlock */ tailPos= headPos= (LISTMARKER) GlobalLock(hMem); headPos->value= 1; headPos->next= NULL; GlobalUnlock(hMem); /* Allocate a node for each of the numbers between 2 and 10 and link */ for (i=2; i < 11; i++) { /* setup index lookup lists */ if((tailPos->next= GlobalAlloc(GMEM_DISCARDABLE | GMEM_MOVEABLE, sizeof(ListMarker))) == NULL) { MessageBeep(0); MessageBox(hWnd, "Out of allocation memory!", "ERROR", MB_OK); return; } /* If - End */ /* Lock the next node, save the value, and set its next to NULL */ hMem2= tailPos->next; tempPos= (LISTMARKER) GlobalLock(hMem2); tailPos= tempPos; tailPos->value= i; tailPos->next= NULL; GlobalUnlock(hMem2); } /* While - End */ /* Lock the 1st node and write out its "value" field */ tailPos= headPos= (LISTMARKER) GlobalLock(hMem); fprintf(fp, "%d\n", tailPos->value); /* Save the handle to the next node */ hMem2= tailPos->next; /* Unlock the 1st node */ GlobalUnlock(hMem); /* Go through list and print out "value" until no more nodes */ while (hMem2 != NULL) { /* Lock the next node and save to tailPos */ tempPos= (LISTMARKER) GlobalLock(hMem2); tailPos= tempPos; fprintf(fp, "%d\n", tailPos->value); /* Get the handle to the next node and then unlock the current one */ hMem2= tailPos->next; GlobalUnlock(hMem2); } /* While - End */ /* Close the output file */ fclose(fp); /* free nodes in the list */ tempPos= (LISTMARKER) GlobalLock(hMem); hMem2= tempPos->next; tempPos= (LISTMARKER) GlobalLock(tempPos->next); GlobalUnlock(hMem); GlobalFree(hMem); while(tempPos->next != NULL) { hMem3= tempPos->next; tempPos= (LISTMARKER) GlobalLock(tempPos->next); GlobalUnlock(hMem2); GlobalFree(hMem2); hMem2=hMem3; } GlobalUnlock(hMem2); GlobalFree(hMem2); return; } /* MakeList */ Book Reviews By Pete Davis Undocumented Windows: A Programmer's Guide to Reserved Microsoft Windows API Functions by Andrew Schulman, David Maxey, and Matt Peitrek Addison-Wesley Publishing Company ISBN 0-201-60834-0 Well, away with the formality and on to the book review. If you're comfortable with programming Windows, then I have only two words to you regarding this book: BUY IT! This is the one of the best references, not only for finding functions that Microsoft 'forgot' to tell us about, but it also gives an incredible amount of insight into how the internals of Windows operates. Not to take away from Maxey and Peitrek, but Schulman's fingerprints are all over this book. This book is very well organized. Each chapter is divided in a way that's easy to understand and covers specific topics. It's hard to know where to start with this book, it has it all. It has chapters on KERNEL, USER, GDI, and SYSTEM calls. It has a chapter on undocumented Windows messages. There are great chapters on how to disassemble and examine Windows executables. This is particularly useful for the true Windows hacker. The book also comes with a load of utilities for examining Windows executables. I have actually found several of these utilities useful simply for debugging my own code. Some of the utilities will tell you what undocumented calls a program is making. This is a neat little way to find out who's got an inside track at Microsoft. Of course, with the release of Undocumented Windows, many of these calls are going to be used more frequently. Microsoft's humor in naming some of these calls (e.g. BozosLiveHere and TabTheTextOutForWimps) are accented with the authors' humor. When discussing how a certain utility sometimes "decides that a function is undocumented when in fact it's documented. (It's sort of like one's coworker who every week thinks he's found a bug in the compiler)." The book is a joy to read just for the humor alone. Although there are a lot of functions listed (well over a hundred, I'd guesstimate), the true value of this book lies in its discussion of Windows and how Windows operates. There is more in this book on the internal operations of Windows than any ten books I've seen. He also discusses at length the politics at Microsoft regarding the undocumented functions. Well, I've talked about all the reasons that I like the book, but let's discuss it's usefulness. Is it useful? Well, for its in-depth discussion of Windows internals, yes. The functions themselves are scattered in a lot of different areas of programming. Some would be useful to some people while others would be useful to others. For example, someone working on a Windows debugger would be very interested in task queues and the like whereas a programmer of a painting package might be more interested in the GDI function calls. There are tons of functions and the documentation of each one is more complete than the documentation Microsoft provides for most of it's 'documented' functions. Schulman and Co. have done a fantastic job on this book and I don't think I could give it a higher recommendation. I look forward to his Undocumented NT (maybe? perhaps?). Last Page By Mike Wallace Well, it's happened. The first issue of Window's Programmer's Journal (official motto: Apply directly to infected areas) is done, and I feel good about it. Why? Because it's the first magazine I've seen that is fully devoted to Windows programming and it's more than just product reviews. Pete and I were hungry for something like WPJ but couldn't find anything that was really helpful, so we thought, "Let's do one ourselves." It's been fun so far, and hopefully we'll get lots of articles from ya'll so we can fill each issue with solutions to your Windows programming problems. If you think you have an idea for an article, send it in! If you just want to submit an outline for an article to see if we'd be interested before you write a full article, do it! The magazine can't last long without help from you people, and if you've done something cool in Windows, we want to hear about it! If you have any questions about Windows, ask! We're going to start a letters column as soon as we have enough letters to fill one, and if we can't answer your questions, we'll submit them to the rest of our readers to get an answer. We're not just doing this as part of our work-release program, we want to learn everything we can about programming in Windows, and I haven't seen any magazines that are as devoted to it as I'd like, and we're not looking for articles written by people with Ph.D.s - we'll take an article from anybody. Future issues will depend on it. Speaking of future issues, we have a few ideas about what you'll see soon. Pete's going to write an article about how to print under Windows 3.0 & 3.1, we'll continue our beginner's column, start a column on how to program C++ (written by me - since I don't know anything about C++, I seemed overly qualified to write a beginner's column on it), some product reviews of books and software, and just yesterday I bought a Windows NT workstation and Pete has the pre-release of NT, so we're going to tell you all about that and try to write some programs for it. Coming Soon! The WPJ BBS! By Jan. 1st (if we're lucky, and by the 5th if we're not quite so lucky), you can send your articles and letters directly to us. We also plan on putting the SIMTEL20 CD-ROM on it so you can download anything you want. It's a great CD full of stuff you never knew you needed but can't live without once you get it. We'll also be getting the CICA CD-ROM of Windows shareware and public domain software soon. See the "How to get in contact with us" section elsewhere in this issue for more info. If you're wondering about the point of this column, this is where I get to rant and rave about anything I'd like. If you don't want to read it, don't. You won't miss anything of earth-shattering importance. I'll try to make it interesting, but there's no telling what will happen here - you'll have to wait and see. If there's anything on your mind you want to talk about it, send me a note and if it sounds like it would be of interest to other readers, I'll write what I can about it. This is your forum as much as mine. One of my minors in school (Va. Tech, by the way - I graduated in '89 with a BS in Computer Science) was philosophy (the other was math - I knew you were wondering about that!), so just because I don't know anything about a subject won't stop me from writing about it. If there's anything you want to get off your chest, tell me about it. I'm always looking for a good discussion about anything. That's the first issue. Hope you liked it; hope we're around for awhile. This could be a lot of fun, and we want you to join us. Until next month, may you look like the late Totie Fields! Getting in touch with us: Right now there are only four ways to get in touch with us. If you have access to the Internet or BITNET, you can get in touch with us at: HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (both Pete) CompuServe: 71141,2071 (Mike) WPJ BBS (703) 503-3021 You can also send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Drive Fairfax, VA 22032 U.S.A. Also, around January 1st, we'll be setting up a BBS specifically to support the WPJ, but we'll also be offering the SIMTEL20 CD-ROM for downloads. The SIMTEL20 system is one of the largest sources of public domain and shareware software. We'll also be adding more CDs later as we can afford it. Anyway, the Windows Programmer's Journal BBS can be reached at: (703) 503-3021. You can get in touch with either Mike or Pete at this number. The BBS is going to be relatively new, so don't be surprised if it's a little buggy in the beginning. Right now it's only 2400 baud as my 9600 baud modem died on me, but in the first month or two you can expect it to go up to 14,400. You can also expect to see a Fido-net node address by the second issue.