This site contains older material on Eiffel. For the main Eiffel page, see http://www.eiffel.com.

WEL: the Windows Eiffel Library

Eiffel Power (TM) from ISE

The Windows Eiffel eXtension Library, a free extension to WEL, is now available for downloading.

This note introduces some of the concepts of the new Windows Eiffel Library (WEL), which has been designed to make Windows programming easier, more reliable, more convenient, and more powerful. WEL is the Windows-specific layer of the new version of EiffelVision, the portable Eiffel graphics library.

Note on this document

This working document was the first publication on WEL back in 1994. It is kept available to illustrate ISE's thinking and directions of development, as well as some of the benefits of the Eiffel way of doing things.

Since then, WEL has been greatly improved and expanded -- in line with the general principles described below -- and a full-fledged WEL tutorial is now available on-line. Refer to the tutorial for any specific information about the current WEL and its capabilities.

The document and the software it describes are copyright ISE, 1995-1997. If you find the content interesting, please put a link to its URL http://eiffel.com/doc/manuals/technology/wel/ in your home page or other appropriate location.

The example discussed below was chosen as a testbed for WEL. The C and C++/MFC versions were written later on, for purpose of comparison; they represent the result of our best efforts to implement the example in these approaches, and should not be construed as implying any inherent property of any existing commercial product.

You can download some WEL examples (executables) right here.

Introduction: more than an encapsulation

The most obvious definition of WEL is that it is an encapsulation of Windows primitives, making it possible for users of Graphical Eiffel for Windows to have direct access to the Windows graphical API.

But this is only part of the truth. What you will see through the discussion below is that for someone whose primary interest is Windows programming (rather than Eiffel per se), WEL provides considerable benefits of its own: the ability to work in a much simpler, move convenient and safer way than if you were using the Windows primitives directly, for example from C.

This advantage comes from the abstraction facilities of Eiffel, which make it possible to encapsulate many low-level details so that developers can concentrate on the functionality, not implementation requirements.

So even for specific and OS-dependent tasks such as using a particular graphical API, the benefits of the Eiffel method and of reusable Eiffel components will show up quickly.

Rather than providing an extensive description of WEL, this note will illustrate the above points through a commented example, which will contrast the C, MFC (C++) and Eiffel ways of achieving the same needs.

A small example application

The example application writes the x and y position in the window where the user has clicked. Also, the user must be able to clear the window by pushing a button. Here is the display:

Window Screenshot

This application needs to catch two kinds of action:

    The mouse click in the window.
    The "Clear" button activation.

Doing it in C

In C Windows programming, we need to register a class to create the application's main window (WNDCLASS structure and RegisterClass function). We have also to write the application's main loop to get and dispatch the messages (functions GetMessage, DispatchMessage). All the messages received by a window are sent to a window procedure (WndProc function) which must be written by the developper to catch the user's events (WM_LBUTTONDOWN and WM_COMMAND).

Here is the C program to perform the above:

#define STRICT
#include <windows.h>
#include <string.h>
#include <stdio.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

HINSTANCE CurrenthInstance;

#define ID_BUTTON_CLEAR 1

#pragma argsused
int PASCAL WinMain (HINSTANCE hInstance,
	HINSTANCE hPrevInstance, LPSTR lpszCmdLine,
	int nCmdShow)
{
	WNDCLASS wClass;
	MSG msg;
	HWND hWnd;

	CurrenthInstance = hInstance;

	if (!hPrevInstance)
	{
		wClass.style = CS_HREDRAW | CS_VREDRAW;
		wClass.lpfnWndProc = WndProc;
		wClass.cbClsExtra = 0;
		wClass.cbWndExtra = 0;
		wClass.hInstance = hInstance;
		wClass.hIcon = LoadIcon (hInstance, IDI_APPLICATION);
		wClass.hCursor = LoadCursor (NULL, IDC_ARROW);
		wClass.hbrBackground = GetStockObject (WHITE_BRUSH);
		wClass.lpszMenuName = NULL;
		wClass.lpszClassName = "TestClass";

		if (!RegisterClass (&wClass))
			return FALSE;
	}

	/* Make the main window */
	hWnd = CreateWindow ("TestClass", "Test",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		NULL, NULL, hInstance, NULL);

	ShowWindow (hWnd, nCmdShow);
	UpdateWindow (hWnd);

	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage (&msg);
		DispatchMessage (&msg);
	}
	return 0;
}

LRESULT CALLBACK WndProc (HWND hWnd, UINT Message,
	WPARAM wParam, LPARAM lParam)
{
	int x, y;
	char buffer[20];
	HDC dc;

	switch (Message)
	{
		case WM_CREATE:
			/* Make a button */
			CreateWindow ("BUTTON", "Clear",
				WS_VISIBLE | WS_CHILD,
				1, 1, 70, 40, hWnd,
				ID_BUTTON_CLEAR,
				CurrenthInstance, NULL);
			break;

		case WM_LBUTTONDOWN:
			/* Write x and y */
			x = LOWORD (lParam);
			y = HIWORD (lParam);
			sprintf (buffer, "(%i, %i)", x, y);
			dc = GetDC (hWnd);
			TextOut (dc, x, y, buffer, strlen (buffer));
			ReleaseDC (hWnd, dc);
			break;

		case WM_COMMAND:
			/* Clear the window */
			if (wParam == ID_BUTTON_CLEAR)
				InvalidateRect (hWnd, NULL, TRUE);
			break;

		case WM_DESTROY:
			PostQuitMessage (0);
			break;

		default:
			return DefWindowProc (hWnd, Message,
				wParam, lParam);
	}
	return 0;
}

Doing it in C++/MFC

The C++ version (using Microsoft Foundation Class library) is more abstract than the C version, but still leaves much to be desired in its encapsulation of Windows, in particular for such aspects as window styles and the window creation process.

Class CMyWindow inherits from CFrameWnd to create a window and a button. To process the messages, some macros must be called to map a C++ method into a Windows message (AFX_MESSAGE). The method InitInstance from CWinApp must be defined to create the application's main window and show it. Programmers still have to contend with a lot of low-level details. This version is slightly longer than the Eiffel-WEL version which follows.

#include <afxwin.h>
#include <string.h>
#include <stdio.h>

#define ID_BUTTON_CLEAR 1

class CMyWindow: public CFrameWnd
{
public:
	CMyWindow ();

	//{{AFX_MSG (CMyWindow)
	afx_msg void OnClear ();
	afx_msg void OnLButtonDown (UINT nFlags, CPoint point);
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP (CMyWindow, CFrameWnd)
	//{{AFX_MSG_MAP (CMyWindow)
	ON_WM_LBUTTONDOWN ()
	ON_COMMAND (ID_BUTTON_CLEAR, OnClear)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP ()

CMyWindow::CMyWindow ()
{
	// Make the main window and a button
	CRect rect (1, 1, 70, 40);
	CButton * button;

	Create (NULL, "Test", WS_OVERLAPPEDWINDOW,
		rectDefault, NULL, NULL);
	button = new CButton;
	button->Create ("Clear", WS_VISIBLE | WS_CHILD,
		rect, this, ID_BUTTON_CLEAR);
}

void CMyWindow::OnClear ()
{
	// Clear the window
	Invalidate (TRUE);
}

void CMyWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
	// Write x and y mouse position
	CDC * dc;
	char buffer [20];

	MessageBeep (0);
	dc = GetDC ();
	sprintf (buffer, "(%i, %i)", point.x, point.y);
	dc->TextOut (point.x, point.y, buffer, strlen (buffer));
	ReleaseDC (dc);

	CFrameWnd::OnLButtonDown(nFlags, point);
}

// Define an application class derived from CWinApp
class CMyApp: public CWinApp
{
public:
	virtual BOOL InitInstance ();
};

// Construct the CMyApp's m_pMainWnd data member
BOOL CMyApp::InitInstance ()
{
	m_pMainWnd = new CMyWindow ();
	m_pMainWnd->ShowWindow (m_nCmdShow);
	m_pMainWnd->UpdateWindow ();
	return TRUE;
}

CMyApp MyApp;

The Eiffel version

Using WEL, the Eiffel version is a lot simpler, clearer - and easier to write.

It consists of two simple classes, MY_APPLICATION and MY_MAIN_WINDOW.

Class MY_APPLICATION inherits from APPLICATION, a WEL class which "knows" how and where to dispatch the messages. All MY_APPLICATION needs to do is to define the application's main window (routine `init_main_window'). Everything else is taken care of automatically by the predefined mechanisms of the reusable library class APPLICATION from WEL.

In the class MY_MAIN_WINDOW, an heir of FRAME_WINDOW, we redefine two routines:

  • We redefine on_wm_command_control so that it clears the window
  • We redefine on_wm_left_button_down so that it will write the current cursor position.
That's it.

Here is the first class:

	indexing
		description: "A small example of WEL programming, %
					%with a main window, where we can detect %
					%user clicks, and let users clear the window."
	class
		MY_APPLICATION

	inherit
		APPLICATION

	creation
		make

	feature

		init_main_window is
				-- Create the main window.
			do
				!MY_MAIN_WINDOW! main_window.make
			end

	end -- class MY_APPLICATION
Here is the second class:
	class
		MY_MAIN_WINDOW

	inherit
		FRAME_WINDOW
			rename
				make_top as frame_make_top,
				make as frame_make
			redefine
				on_wm_command_control,
				on_wm_left_button_down
			end

	creation
		make

	feature {NONE} -- Initialization

		make is
				-- Create the main window and a button.
			local
				button: BUTTON
			do
				frame_make_top ("Test");
				create button.make (Current, "Clear",
					1, 1, 70, 40, Id_button_clear)
			end

	feature {NONE} -- Behaviors

		on_wm_command_control (id_control: INTEGER; window: WINDOW) is
				-- Clear the window.
			do
				if id_control = Id_button_clear then
					invalidate
				end
			end

		on_wm_left_button_down (keys, x_pos, y_pos: INTEGER) is
				-- Write the values of `x_pos' and `y_pos'
			do
				position.wipe_out;
				position.extend ('(');
				position.append_integer (x_pos);
				position.append (", ");
				position.append_integer (y_pos);
				position.extend (')');
				dc.get;
				dc.text_out (x_pos, y_pos, position);
				dc.release
			end

	feature {NONE} -- Implementation

		position: STRING is
				-- The string that will contain the position indication
			once
				create Result.make (20)
			ensure
				result_not_void: Result /= Void
			end

		dc: DC is
				-- Device context used to write the position
			once
				create Result.make_by_window (Current)
			ensure
				result_not_void: Result /= Void
			end

		Id_button_clear: INTEGER is unique

	end -- class MY_MAIN_WINDOW

Even for such a basic and relatively low-level application, the code examples speak for themselves.

Note that even though the classes of the example merely rely on the WEL library mechanisms, they can make some good use of assertions. The library classes themselves use assertions even more extensively for coherence of design, readability and safety.