Introduction This article explains how to write a fully featured Win16 GUI application in C. It’s based on another article from this site, entitled “Building Win32 GUI Applications with MinGW”, and is basically a port of that application from Win32 to Win16 (if you came here by mistake looking for Win32 development in C, I’d recommend reading that article). You may be asking, “Why?”. It’s a fair question, because 16 bit Windows is long obsolete, and even if you are doing serious development for Win16 it’s most likely to be supporting a legacy application rather than developing new software. But you’re reading this article, so I can only assume that like me, you have a keen interest in 16 bit Windows, or you just want to understand how some of the crazy conventions in Win32 came from the need to be backwards compatible with Win16. To avoid any possible confusion, when we say “Win16” we are referring to the original 16 bit Windows API , where 32 bit addresses are made up of a segment (in real mode) or selector (in protected mode) combined with an offset, and all applications are cooperatively multitasked in a single address space. This API is present from Windows 1.0 to Windows 3.11, and for backwards compatibility reasons is also present from Windows NT 3.1 to current x86 versions of Windows. Windows 95 to Windows ME also support the Win16 API, partly for reasons of backwards compatibility, but also because much of the 32 bit user mode API code on these versions of Windows do little more than call the 16 bit API equivalent to carry out their tasks!

Application Features The following are the features which the Win16 application should demonstrate: Resizable main window, with an empty client area.

About dialog, with some basic text, an icon, and an “ok” button.

Version information resource, so that the version information and copyright information can be viewed using File Manager or Windows Explorer.

Main menu allowing exiting of the application, and the showing of the about dialog.

Keyboard accelerator “Alt + A” to show the about dialog.

System menu item allowing the about dialog to be shown. It will be targeted towards Windows 3.xx, and additionally runs on Windows 9x (Windows 95 to Windows ME) and NT based versions of Windows which have the WoW subsystem (Windows NT 3.1 to the x86 version of Windows 8.1 and beyond). It doesn’t support Windows 1.xx or Windows 2.xx as targeting these operating systems poses a number of difficulties, particularly the lack of freely available tools for building the applications. I have included a section in this article about targeting Windows 1, and the code download includes a Windows 1 version of the application.

The Application’s WinMain Procedure The following is the application’s WinMain procedure. If you’re familiar with Win32 programming, you’ll notice that it is almost identical to a typical Win32 program—this is no coincidence, as Win32 was designed with maximum Win16 compatibility in mind. /* Global instance handle */ HINSTANCE g_hInstance = NULL; /* Our application entry point */ int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hWnd; HACCEL hAccelerators; MSG msg; /* Assign global HINSTANCE */ g_hInstance = hInstance; /* Register our main window class */ if (! hPrevInstance) { if (! RegisterMainWindowClass()) { MessageBox(NULL, "Error registering main window class.", "Error", MB_ICONHAND | MB_OK); return 0; } } /* Create our main window */ if (! (hWnd = CreateMainWindow())) { MessageBox(NULL, "Error creating main window.", "Error", MB_ICONHAND | MB_OK); return 0; } /* Load accelerators */ hAccelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); /* Show main window and force a paint */ ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); /* Main message loop */ while(GetMessage(&msg, NULL, 0, 0) > 0) { if (! TranslateAccelerator(msg.hwnd, hAccelerators, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } The differences between this and a typical Win32 application are: The “WinMain” function is declared “ PASCAL ”, which expands to the “ __pascal ” keyword. This ensures the function is declared as a near function with the Pascal calling convention. In Win32 we declare it as “ WINAPI ”, but on Win16 that expands to “ __far __pascal ” which is not what we want (it’s a far function rather than near). Getting this wrong will likely result in a crash either when trying to do something with the parameters to WinMain, or a crash when the WinMain function returns. Always be careful to correctly declare your functions!

”, which expands to the “ ” keyword. This ensures the function is declared as a near function with the Pascal calling convention. In Win32 we declare it as “ ”, but on Win16 that expands to “ ” which is not what we want (it’s a far function rather than near). Getting this wrong will likely result in a crash either when trying to do something with the parameters to WinMain, or a crash when the WinMain function returns. Always be careful to correctly declare your functions! We check the “ hPrevInstance ” parameter for a NULL value before registering the window class. Unlike Win32 where this parameter is always NULL, in Win16 it is a handle to an existing running instance of your application, or NULL if there is no currently running instance. We only register the Window class when there is no previous instance, because it will fail if the previous instance has already registered the class.

” parameter for a NULL value before registering the window class. Unlike Win32 where this parameter is always NULL, in Win16 it is a handle to an existing running instance of your application, or NULL if there is no currently running instance. We only register the Window class when there is no previous instance, because it will fail if the previous instance has already registered the class. We don’t use any “ TEXT() ” macros for the strings, as Win16 does not support Unicode. Although it’s not relevant in this particular code snippet, it’s important to remember that whilst in Win32 an “ HMODULE ” is the same as an “ HINSTANCE ”, this is not the case in Win16. When you are calling functions which take an instance handle as an argument, check that you’re actually passing an instance handle!

The Main Window The main window is created by registering a window class and then creating an instance of it. This is much the same as in Win32, except that we are restricted to using a “ WNDCLASS ” structure and calling “ RegisterClass() ” rather than “ WNDCLASSEX ” and “ RegisterClassEx() ”. This is because extended window classes did not exist prior to Windows 95. Likewise “ LoadImage() ” did not exist in Win16, so we load icons and cursors with “ LoadIcon() ” and “ LoadCursor() ”. /* Main window class and title */ static const char MainWndClass[] = "Win16 Example Application"; /* Register a class for our main window */ BOOL RegisterMainWindowClass() { WNDCLASS wc = {0}; wc.lpfnWndProc = &MainWndProc; wc.hInstance = g_hInstance; wc.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_APPICON)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); wc.lpszClassName = MainWndClass; return (RegisterClass(&wc)) ? TRUE : FALSE; } /* Create an instance of our main window */ HWND CreateMainWindow() { HWND hWnd; HMENU hSysMenu; hWnd = CreateWindowEx(0, MainWndClass, MainWndClass, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 320, 200, NULL, NULL, g_hInstance, NULL); if (hWnd) { /* Add "about" to the system menu */ hSysMenu = GetSystemMenu(hWnd, FALSE); InsertMenu(hSysMenu, 5, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); InsertMenu(hSysMenu, 6, MF_BYPOSITION, (UINT) ID_HELP_ABOUT, "About"); } return hWnd; } Unlike extended window classes, extended window styles did exist in Windows 3.xx and we have the option of using “ CreateWindowEx() ”. After we create the window, we add a separator and “about” item to the system menu. We cover this in detail in the “System Menu” section.

The Version Information Resource It’s possible to add information into the executable, containing information such as the version, author, copyright, and description. This is done with a version information resource, and means File Manager or Windows Explorer are able to display this when looking at the executable’s properties page: (if you look closely at the “last change” date you’ll notice that Program Manager is not fully Y2K compliant!) Adding a version information resource is simple a case of defining it in your resource script: /* Executable version information */ VS_VERSION_INFO VERSIONINFO DISCARDABLE FILEVERSION 1,0,0,0 PRODUCTVERSION 1,0,0,0 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE #else FILEFLAGS 0 #endif FILEOS VOS_DOS_WINDOWS16 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080904E4" BEGIN VALUE "CompanyName", "Transmission Zero\0" VALUE "FileDescription", "Win16 Example application\0" VALUE "FileVersion", "1.0.0.0\0" VALUE "InternalName", "Win16App\0" VALUE "LegalCopyright", "©2014 Transmission Zero\0" VALUE "OriginalFilename", "Win16App.exe\0" VALUE "ProductName", "Win16 Test application\0" VALUE "ProductVersion", "1.0.0.0\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x809, 1252 END END I won’t cover the format of the version resource in detail as it’s covered very well in the MSDN, but most of it is self-explanatory (I’d suggest reading the MSDN article anyway, particularly if you need to set the language to anything other than British English). Notice that the strings are double null terminated by using an embedded null character. I don’t know why this is required in Win16, but it’s the way it has always been done in the Microsoft SDK sample applications. Without the extra null character, Windows Explorer will chop off the last character of each string when you view the details!

The Main Menu The main menu allows exiting the application or showing the about dialog. It has been defined in a resource script rather than creating it programmatically, and is created in exactly the same way as a Win32 menu would be. /* Our main menu */ IDR_MAINMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About", ID_HELP_ABOUT END END As with Win32, a “ WM_COMMAND ” message is sent to your window procedure when a menu item is selected. There is however an important difference between Win16 and Win32 when it comes to the parameters for this message. This is because in Win16 the “ wParam ” is a WORD and “ lParam ” is a DWORD , whereas in Win32 they are both DWORD s. This means that on Win16 the “ lParam ” can contain a packed HANDLE plus another WORD variable, whereas on Win32 the “ lParam ” is only big enough for a single HANDLE . Instead the WORD variable is packed into the high WORD of “ wParam ”. It’s best to check the API documentation when it comes to windows messages as there are subtle differences between Win16 and Win32. If you are using message crackers defined in “ windowsx.h ” then you can avoid these problems and have code which compiles for Win16 and Win32 without any changes. /* Window procedure for our main window */ LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_COMMAND: { WORD id = wParam; switch (id) { case ID_HELP_ABOUT: { ShowAboutDialog(hWnd); break; } case ID_FILE_EXIT: { DestroyWindow(hWnd); break; } default: return DefWindowProc(hWnd, msg, wParam, lParam); } break; } /* Other message handlers omitted from code snippet */ default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; } In this case, we use the wParam to determine which menu item was selected, and take the appropriate action.

About Dialog The about dialog box is defined in the resource script, and done so in the same way as a Win32 dialog. The only difference between Win16 and Win32 is that Win32 adds some new properties and styles which aren’t supported in Win16, and we are using a “ DIALOG ” resource type rather than “ DIALOGEX ”. /* Our "about" dialog */ IDD_ABOUTDIALOG DIALOG DISCARDABLE 0, 0, 147, 67 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Sans Serif" BEGIN ICON IDI_APPICON,IDC_STATIC,7,7,20,20 LTEXT "Win16 Example application.",IDC_STATIC,34,7,91,8 LTEXT "©2014 Transmission Zero",IDC_STATIC,34,17,86,8 DEFPUSHBUTTON "OK",IDOK,90,46,50,14,WS_GROUP END The dialog procedure is constructed in the same way as a Win32 dialog. The return type of the procedure is “ BOOL ”, whereas in Win32 it would be declared as “ INT_PTR ” if the code is to be compatible with both Win32 and Win64. /* Dialog procedure for our "about" dialog */ BOOL CALLBACK AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: { WORD id = wParam; switch (id) { case IDOK: case IDCANCEL: { EndDialog(hwndDlg, id); return TRUE; } } break; } case WM_INITDIALOG: return TRUE; } return FALSE; } The dialog box can be shown using one of the dialog functions such as “ DialogBox() ”: /* Show our "about" dialog */ void ShowAboutDialog(HWND owner) { /* Create dialog callback thunk */ FARPROC aboutProc = MakeProcInstance(&AboutDialogProc, g_hInstance); DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_ABOUTDIALOG), owner, aboutProc); /* Free dialog callback thunk */ FreeProcInstance(aboutProc); } You might be wondering why the “ DialogBox() ” call has a variable of type “ FARPROC ” passed to it as the fourth argument. Whilst in Win32 you simply pass the address of your dialog procedure to the dialog creation function, in Win16 you must pass a thunk which has type “ FARPROC ”, returned by a call to the “ MakeProcInstance() ” function. In fact, this must be done for all callback functions except for window procedures. It doesn’t matter where in your application you call the “ MakeProcInstance() ” function, but you must remember that each call must be accompanied by a call to “ FreeProcInstance() ” to avoid leaking memory.

Keyboard Accelerators By pressing “ Alt + A ”, the “about” dialog for the application will be launched. This is done using a keyboard accelerator which has been defined in the resource script: /* Our accelerators */ IDR_ACCELERATOR ACCELERATORS DISCARDABLE BEGIN "A", ID_HELP_ABOUT, VIRTKEY, ALT, NOINVERT END Again, this is completely identical to how keyboard accelerators are defined in Win32. We use the same ID for our accelerator as we use for our “help → about” menu item, which means we don’t have to write any additional message handling code. To get the accelerators working, you need only load the accelerators and translate the accelerator messages in your message loop: /* Load accelerators */ hAccelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); /* Some non-accelerator intermediate code ommited */ /* Main message loop */ while(GetMessage(&msg, NULL, 0, 0) > 0) { if (! TranslateAccelerator(msg.hwnd, hAccelerators, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

System Menu If you click on a minimized icon in Program Manager, you’ll see the system menu. In our application we have added an item to this menu to show the “about” dialog. This isn’t the best use of the system menu, but the intention is to demonstrate how it is done. The system menu additions are done entirely in code, the same way as it is done in Win32. hWnd = CreateWindowEx(0, MainWndClass, MainWndClass, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 320, 200, NULL, NULL, g_hInstance, NULL); if (hWnd) { /* Add "about" to the system menu */ hSysMenu = GetSystemMenu(hWnd, FALSE); InsertMenu(hSysMenu, 5, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); InsertMenu(hSysMenu, 6, MF_BYPOSITION, (UINT) ID_HELP_ABOUT, "About"); } The following is the message handler for the “WM_SYSCOMMAND” message, which is sent by the system menu. /* Window procedure for our main window */ LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { /* Other message handlers omitted from code snippet */ /* Item from system menu has been invoked */ case WM_SYSCOMMAND: { WORD id = wParam; switch (id) { case ID_HELP_ABOUT: { ShowAboutDialog(hWnd); break; } default: return DefWindowProc(hWnd, msg, wParam, lParam); } break; } /* Other message handlers omitted from code snippet */ default: return DefWindowProc(hWnd, msg, wParam, lParam); } return 0; }

The Module Definition File The final component of our Win16 application is the module definition file. You may have used a module definition file when creating a Win32 DLL , but for Win32 executables it’s not common to use one. It is used to pass information to the linker when building the final executable, for example stack size, heap size, and exported functions. The following is our module definition file: NAME Win16App DESCRIPTION 'Win16 Test Application' STUB 'WINSTUB.EXE' CODE MOVEABLE PRELOAD DISCARDABLE DATA MOVEABLE PRELOAD MULTIPLE HEAPSIZE 1024 STACKSIZE 4096 EXPORTS MainWndProc AboutDialogProc You can either double quote or single quote the strings, but single quotes result in a def file which works with both older and newer linkers. Double quotes only work in newer linkers. The “DESCRIPTION” should be self-explanatory. The “STUB” is the MS-DOS stub for the application, which will be executed when the application is run from DOS. It can do anything you want it to do as long as it’s a valid MS-DOS executable, but here we are using the default “winstub.exe”, which displays the message “ This application requires Microsoft Windows ”. The “CODE” and “DATA” statements define the attributes on the code and data segments in the executable. In this case, they are both moveable and preloaded, meaning the operating system loads them when the application starts and can move them about in memory as it sees fit. The code segment is marked as “DISCARDABLE”, meaning the operating system can remove it from memory, and later load it back from the executable image on disk. This would not work for the data segment, since its contents will change during the execution of the program. We mark the data segment as “MULTIPLE”, meaning that each instance of the application gets its own copy of the data, rather than being shared as would be the case in a DLL. The “HEAPSIZE” and “STACKSIZE” determine the size of the heap and stack at runtime—remember that in Win16, the size of the stack, heap, and all static and global variables must fit within a single 64 KB segment. Finally, the “EXPORTS” section marks our dialog procedure and window procedure as being exported—it is essential to export every callback function which will be called by Windows. Also note that any functions exported from the executable cannot be called from within the application, as the exported functions have a specific set of machine instructions in their prolog which allow them to be called from outside the application, and this fails when you call the functions from within the application.

Putting It All Together Now we have all of the components of our application, we can compile the code and link it all into an executable. Assuming all of the code is contained in “myfile.c”, the resource script is named “resource.rc”, and module definition file is named “myapp.def”, to create the application “myapp.exe” with Microsoft’s Visual C++ compiler you would do the following: cl16.exe /nologo /c /D NDEBUG /D WINVER=0x0300 /G3swf /Os /W3 /Zp /FPi87 myfile.c rc16.exe /nologo /r resource.rc link16.exe /nologo /align:16 myfile.obj,myapp.exe,,libw.lib slibcew.lib,myapp.def rc16.exe /nologo /30 resource.res myapp.exe The 16 bit visual C++ compilers can be downloaded for free as part of the Windows Server 2003 SP1 DDK (you can get it using the Server 2003 DDK ISO direct download link). You will find it has executables such as “link16.exe” and “link.exe”, which are identical except for their names. In this example I have used the “16” versions to avoid any possible confusion with the more common 32 bit tools—these also have for example a “link.exe”, which is not the same as the 16 bit “link.exe”. If you have an MSDN subscription, you can download Visual C++ 1.52 and use the tools which come with it. If you prefer, you can use a different C compiler as long as it comes with the necessary headers and libraries for Win16 development. One example is the Open Watcom compiler / IDE, which is also a free download. The download link in this article contains a hand written Makefile for Visual C++, a Visual C++ Makefile project, and project files for Open Watcom. The application should run fine on any 32 bit version of Windows, so it’s not necessary to perform initial testing in Windows 3.x. Bear in mind that a Win16 app runs in a shared address space, so if there is a bug in your application then it can potentially crash other Win16 processes. This probably isn’t an issue unless you are running legacy applications, but if this is an issue and you’re running Windows NT, you can create a shortcut to the application, check the “Run In Separate Memory Space” option, and run the application from that shortcut. By doing this, the application gets its own NTVDM and WoW process, so the worst thing it can do is crash itself. Bear in mind that by running a 16 bit application in its own address space, it cannot share memory with any other applications. The application running in Windows 2000, showing the system menu.

Windows 1.xx and 2.xx I mentioned earlier in the article that the application does not run under Windows 1 or 2 because these operating systems pose some additional difficulties. It’s certainly not impossible, but here are a few hurdles you’ll need to overcome: You’ll need a very old C compiler because newer C compilers produce CRT startup code which won’t run on older versions of Windows. I believe Microsoft C 4 and Microsoft C 5 are the only supported compilers (not to be confused with Visual C++ 4 or 5).

startup code which won’t run on older versions of Windows. I believe Microsoft C 4 and Microsoft C 5 are the only supported compilers (not to be confused with Visual C++ 4 or 5). These very old C compilers don’t support modern ANSI or ISO C standards, so you may have to rewrite significant portions of your code to use K&R style C! There are no const variables, no zero initialisation of structs, and functions must be declared and defined using the awkward K&R syntax.

or C standards, so you may have to rewrite significant portions of your code to use style C! There are no variables, no zero initialisation of structs, and functions must be declared and defined using the awkward K&R syntax. Even if your C syntax is acceptable to the compiler, the code still won’t compile if you are using newer API functions which didn’t exist when Windows 1 or 2 were written. This probably goes without saying though, and isn’t likely to be a problem unless you are trying to backport an application.

Many of the typedefs in the Windows 3 SDK didn’t exist in the Windows 1 SDK. For example “ WPARAM ” and “ HINSTANCE ” aren’t defined, so you use more generic types such as “ WORD ” and “ HANDLE ” instead. Again, this is only likely to be a problem if you are backporting an application.

” and “ ” aren’t defined, so you use more generic types such as “ ” and “ ” instead. Again, this is only likely to be a problem if you are backporting an application. The windows.h header file does not have any inclusion guards, so you have to be very careful to avoid including it twice otherwise you’ll get compiler errors.

header file does not have any inclusion guards, so you have to be very careful to avoid including it twice otherwise you’ll get compiler errors. Many window styles and window messages which can be used in Windows 3 didn’t exist in Windows 1, for example Windows 1 didn’t even support overlapped windows! You can work around this by defining constants for the window messages and styles yourself. Whilst defining a certain window style constant won’t magically make Windows 1 support a style which was introduced in Windows 3, the application will display as expected in Windows 3 and should degrade gracefully in Windows 1.

Windows 1 doesn’t support menu quick keys (the underlined letters in menus where you can press the corresponding key to open the menu item). Instead it displays the ampersands from your menu resource.

You’ll need a very old version of the Windows SDK which is not easy to get hold of. Newer SDKs won’t work as the resource compiler will produce incompatible resources. This is because the format of some resources such as menus and icons changed between Windows 2.11 and Windows 3.0.

Applications written for Windows 1 and 2 will not run under Windows 9x. You will get an error message such as “Windows cannot run this program. Contact your vendor for a later version.”. Oddly enough, they do run on all NT versions of Windows which have the WoW subsystem, for example Windows NT 3.1 through to at least the x86 version of Windows 8.1. The only compatibility issue I have noted is that icons are not displayed correctly, and either show up completely garbled or you’ll see a stock Windows icon in its place. I would speculate the reason why Windows NT supports Windows 1 and 2 applications whereas Windows 9x does not, is because the Windows NT WoW subsystem is based on the Windows 3.1 kernel, and Windows 3.1 supports all Win16 applications.

Windows 3.0 and later will pop up a warning message such as “Obtain an updated version of the application that is compatible with Windows version 3.0 and later.”. This is only a minor problem as it does not prevent a well written application from running correctly. It’s possible to suppress this warning by running a tool called mark.exe which marks the executable as being able to run in protected mode. Whilst these are mostly fairly minor things, it all adds up when you are writing an application. In fact I would go so far as to say that the difference between a Windows 3 application and a Win32 application are tiny compared to the difference between a Windows 1 application and a Windows 3 application! For that reason I focussed on Windows 3.xx applications in this article, but you will find a Windows 1 version of the application in the code download. Finally, something which I find very impressive is that the Windows 1 version of the application runs unmodified in both Windows 1.01 and the preview version of Windows 10 (the latest version of Windows at the time of writing). How many other operating systems can run applications written for a 30 year old operating system? The Windows 1 application running on Windows 1. The Windows 1 application running on the Windows 10 preview.

The Full Story on MakeProcInstance and EXPORT I mentioned that any function called from Windows, i.e. the window procedure and dialog procedure, must be exported from the executable using the module definition file. I also mentioned that pointers to all callback functions except window procedures must not be passed to Windows API functions, and instead you pass a thunk created by a call to “ MakeProcInstance() ”. This is because of the slightly strange architecture of Win16, which poses some challenges loading the correct value into the data segment register inside a far function which has been called from outside your application. By exporting a function, a certain set of machine instructions are placed in the prolog of the function which allow the correct value to be loaded into the data segment register. Note that I used the word “allow”, because on its own, exporting a function does not load the correct value into the register—this is what MakeProcInstance is for. When you call MakeProcInstance, Windows creates a thunk which sits between Windows and your function. It does little more than placing the correct value of the data segment (which can only be known at runtime) into a register, and then jumping to your function. Your exported function then just moves this value from that register into the segment register, and your function will reference the correct data. Although the above does what needs to be done, it’s a lot more complicated than it needs to be. It’s possible to avoid exports and MakeProcInstance entirely, at the same time allowing your callback functions to be called from within your own code without causing a crash. For a full and technical description on this, have a look at the FixDS readme from Michael Geary.

Problems? This article has been put together after studying Win16 articles on the internet. Whilst I have done my best to ensure that all information is correct, due to the fact that Win16 documentation is hard to come by, it’s possible that there could be some mistakes. If you have any difficulties getting your Win16 application up and running, or you spot any mistakes in this article, please get in contact and I’ll try to help.

Related Pages All other computing articles on this site