Click here to Skip to main content
Click here to Skip to main content

Writing Win32 Apps with C++ only classes (part 2)

, 26 Apr 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
C++ classes and wrappers to write W32 apps without MFC, ATL or other (part 2).

Introduction

This article is the consequence of this other. Here, I'll go adding other features to the NLib.

The Approach

I'll follow a "modular" approach: avoiding to "frameworkize" the library, and using a "component model" (not necessarily COM or .NET: I'll stay with native language features) allowing us to use that component as needed, without any need to include them or use them when what we do doesn't require them.

As in the previous article, I'll do nothing new that has not yet been done before: you can find as many articles you like to do the same with MFC, or WTL. I just want to test a different coding approach. Am I reinventing wheels ?!

YES: I AM !

But if MFC is best for rain, and WTL for sand ... may be these ones feel better on snow!

The starting point

It is the W2 project of the previous article: a "Hello world" unfeatured window, but already with message maps and command delivery. I created a W3 Win32 empty project, put a copy of W2 sources, and did some settings (adding "NLib" to the solution, set RTTI on, set "use precompiled headers" and "create precomp. header" for stdafx.cpp. Also set the dependency of W3 from NLib).

Some NLIB Modification

During the deployment of this second article, I found it convenient to modify certain parts of the NLIB, for both bug fixing and also improvement.

Message crackers

The message cracker map macros have been modified to carry functions having as last parameter a bool& bHandled. This value is set to true by the macro before calling the passed function, but this arrangement allows you to set it back to false. By setting to false, the chained action is not broken by the handler call, and can continue with other EWnd eventually attached to the same HWND. To avoid confusion, I renamed those macros ONMSG_xxx rather than GETMSG_xxx.

This is particularly comfortable when you want to "spy" a message, without interfering with its dispatching.

Wrappers redesign

While extending the wrappers to create more features, I discovered some "flaws" that - if not better redesigned - put some limitations in usage. And since the cost of this "redesign" is very few, I found convenient to adequate the existing code:

In particular:

  • New types have been introduced: CRH and COH: normally they are as CH and RH, but are intended to be returned by "const" functions. The old type are instead intended to be returned by non-const functions.
  • New functions had been introduced in the traits_wrp_xxx classes: CAccess and CDereference: they return COH and CRH from a IH<CODE>.
  • In WrpBase, operator TT::OH() is no more const, and calls Access. Instead an operator TT::COH() const had been introduced, calling CAccess.
  • In Wrp, operator->() and operator*() had been rewritten in both the const and non-const versions, calling the corresponding traits functions.

Although the supplied implementation of the traits does the same things, this different interface gives us the capability, where is the case, to differentiate between the use of a wrapper to access a data for reading, and the use to modify the hold data.

Strings

Many Windows APIs are defined in terms of TCHAR. Although it is relatively easy to have a string class holding TCHARs by defining a std::basic_string<TCHAR>, the implementation of std::strings provided by PJ Plauger (essentially a specialization of vectors) is not very efficient: strings are considered "values" and are always copied.

In contrast, MFC and ATL CString makes use of shared vectors that are "cloned" only when a string value needs to be modified. To implement a similar scheme, I tested a "WrpBase and traits" override that allows to maintain shared data between wrappers, and do the clone when non-const access is attempted.

Although it worked, there is an overhead due to the internal calls between Wrp, WrpBase and traits. So I preferred a dedicated implementation, more efficient for this specific purpose (also if less flexible).

A new wrapper type (NWrp::SShare<T, nullval>) has been introduced to auto generate, auto clone and share buffers of Ts.

NWin::SString is then derived by NWrp::SShare<TCHAR, _T('\0')>, adding Left, Mid, Right, constructors and operators.

Note that SString is not itself a full OOP class (none of the classes defined here is): it is only a "memory manager". To manipulate strings, I still refer to the Windows API or to the string function taken from tchar.h.

SString converts implicitly to and from LPCTSTR. To modify the buffer, use - instead GetBuffer(int wantedsize), passing the required size, or -1 to let the actual size unchanged. GetBuffer performs a buffer "clone" if the actual buffer is shared, then resizes it accordingly and returns its address.

Working with Menu, commands and IDs

To add an icon or a menu to a window, we don't need a wrapper: the icon ID can be specified during window class registration, and a menu can be loaded while creating the window. Until the HMENU remains associated to a window, it's Windows that provides its destruction.

Thus, here is the modified winmain.

int APIENTRY _tWinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //instantiate a loop
    NUtil::SResID resWapp(IDR_WApp);

    LPCTSTR wcname = _T("W1MainWnd");
    //instantiate a window class
    NWin::SWndClassExReg wc(true, hInstance, wcname, resWapp);     
    CMainWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, 
        LoadMenu(hInstance, resWapp), NULL);

    loop.Loop();

    return 0;
}

Things become a bit more complicated when we want to manage command user interface updates (for example: to disable unused commands).

Two common approaches are the following:

  • WTL approach: a command has an associated "state" that the application manages. GUI elements have the functionality to represent that state when displayed. Menus typically do during WM_INITMENUPOPUP, toolbars during the idle time processing.
  • MFC approach: a GUI element (menus during WM_INITENUPOPUP, toolbars during idle processing) queries the application about its state. The application responds by setting its interface status.

I think the two approaches are "dual": no "best" exists, but one can be favorite than the other depending on cases: if the command state depends on a variety of factors dispersed in many places, probably the MFC approach is simpler to code, while - if the state is a well defined condition, WTL approach is probably simpler.

I find MFC approach more general, so I implemented a similar one.

Command state

We need an object that abstracts the behavior of a GUI element (a menu item or a toolbar button or whatever) that can receive a number of states: Enabled/Disabled, Grayed, Checked, Radiochecked, Indeterminate, Having Text, Having Image.

This is done through an interface: ICmdState, that provides a set of abstract functions.

Those functions can be implemented in various classes managing a specific UI component. For Windows standard menu, this is done by SCmdMnu.

To manage command update, I used two private messages GE_QUERYCOMMANDHANLER and GE_UPDATECOMMANDUI.

In particular, to update menus:

  • During WM_INITMENUPOPUP, a GE_QUERYCOMMANDHANLER message is sent. The LPARAM is a SCmdMnu*, and WPARAM is a command ID.
  • GE_QUERYCOMMANDHANLER is handled by the COMMAND_xxxx_U macros (should be used in place of COMMAND_xxxx, the same that handles WM_COMMANDs) by calling SetHandled.

When a GUI require it is appropriate, (WM_INITMEMUPOPUP, idle handler, or whatever), it must post a GE_UPDATECOMMANDUI message (same parameter as the previous). The application can handle this message by calling ICmdState member functions. Their implementation operates setting the state of the GUI.

For menus, this stuff can easily be handled by a EWnd derived class that can be attached, for example, to the main window. This class is EMenuUpdator: it handles WM_INITMENUPOPUP as indicated.

To use it, we simply create it and attach it to the main window.

int APIENTRY _tWinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //instantiate a loop
    NUtil::SResID resWapp(IDR_WApp);

    LPCTSTR wcname = _T("W1MainWnd");
    //instantiate a window class
    NWin::SWndClassExReg wc(true, hInstance, wcname, resWapp);     
    CMainWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, 
        LoadMenu(hInstance, resWapp), NULL);
    NWin::EMenuUpdator mnuupdt;
    mnuupdt.Attach(wnd);

    loop.Loop();

    return 0;
}

That's it.

To process GE_UPDATECOMMANDUI, I provided the macros ONMSG_GE_UPDATECOMMANDUI, ONMSG_GE_UPDATECOMMANDUI_RANGE, and ONMSG_GE_UPDATECOMMANDUI_RANGE_CODE (they are in CmdUpdt_macros.h) that can be used in message maps.

They all call a function prototype as:

LRESULT function(
  UINT nID,             //command ID
  WORD nCode,           //notification code (if from a control)
  ICmdState* pCmdState, //the ICmdState whose functions modify the GUI state
  bool& bHandled      //setted to true before calling the function. 
  );      //return value: should always be 0.

Pseudo-Bugs fixed

Some pseudo-bugs have been fixed from the previous article. I say "pseudo" because they are not themselves "bugs" (programming mistakes) but "design flaws": they reveal lack of functionalities when needed.

Wrapper types

The function WrpBase::Value, originally returns OH, but -in fact- it should be the return type of the function Access, inherited from the traits, making the Value function a not useful clone. In fact, Value should return the "value" as it is stored, for read-only access, without any conversion. Thus, its return type has been changed into const SH&.

This has no influence on normal wrappers (SH is H and OH is const HANDLE&, so that's the same) or pointers (SH is H*, and OH is H* again, but this means "copy on return", so const SH& becomes const H*&, that assigns to an H* in the same way), but can be substantial in more complex cases, where the returned types are not the same as the wrapper stored values.

And here is the real case.

Reading custom resources

To read a custom resource, the windows API require the following steps:

  1. Call FindResource passing a HMODLULE and an ID, to get a HRSRC.
  2. Call LoadResource passing the HRSRC to get a pseudo HGLOBAL.
  3. Call LockResource to get a LPVOID to cast appropriately.
  4. use the data, then ...
  5. Call UnlockResource passing the HGLOBAL and then ...
  6. Call FreeResource again with the HGLOBAL.

While step 1 does not involve memory allocation, steps 5 & 6 are the inverse of 3 & 2 respectively.

Thus, a comfortable wrapper can attach to HRSRC loading the "data", let them available using dereference, and free the data on detach. If more wrappers are around the same HRSRC, loading must be done by the first and freeing must be done by the last. The "data" ... should be a template parameter.

So, a resource wrapper for a toolbar resource can be (namespaces apart) a Wrp<SResourceData<XToolBarData> >. If wrpTbrRes is a variable of that type, operator-> will return an XToolbarData*.

SResourcedata, then, derives from WrpBase, closing the inheritance (passing itself as the "W" parameter) and having specific traits and reference counters holder.

Because to load a resource we need not only a HRSRC, but also a HMODULE, we have to store the HMODULE as "initial parameter" into SResourceData, providing an initialization function (void SetHModule(HMODULE hMod)). Then, we need to store the HGLOBAL and the Data pointer. Because these values must survive until there are wrappers around a given resource, the more comfortable place to store is the shared reference counters holder.

Thus:

  • an XRefCountRes<Data> has been defined, derived from XRefCount<SResourceData<Data> >, holding also two additional members: a HGLOBAL and a Data*.
  • types have been definded to receive a const HRSRC& (that's IH), store an HRSRC(that's SH), access a Data* (OH) and dereference to a Data& (as RH)
  • Access and Dereference are redefined to access _pRC (the reference count holder) and returning the Data* or Data& accordingly. These functions are defined as "static" in the traits, and inherited in the WrpBase. We cannot rewrite the traits because, at that level, no reference counter object pointer is defined. Thus I override them in SResourceData, where they are no more static, but become const member functions. Because they're always called via pThis() (that converts into a W*, where W is the wrapper: SResourceData, in this case) the overrides will be called.
  • OnFirstAttach and OnLastDetach are overridden to do the "dirty work": Load and Lock the first, Unlock and Free, the second.

For example, to read a RT_TOOLBAR resource into a XToolBarData, we can use the following code:

    // Note: suppose we already have
    //  HINSTANCE hInst; LPCTSTR lpszResourceName;
    //
    struct XToolBarData
    {
        WORD wVersion;
        WORD wWidth;
        WORD wHeight;
        WORD wItemCount;
        //WORD aItems[wItemCount]

        WORD* items()
            { return (WORD*)(this+1); }
    };
    
    typedef NWrp::Wrp<NWrp::SResourceData<XToolBarData> > TTbrData;
    TTbrData TbrData;
    TbrData.SetHModule((HMODULE)hInst);
    TbrData.Attach(FindResource(hInst, lpszResourceName, RT_TOOLBAR));

    XToolBarData* pData = &*TbrData;
    ASSERT(pData && pData->wVersion == 1);
    // use pData as needed.
    // resource will be release when leaving the scope

Adding images to menu

A simple way to bind commands to images is to use the Visual Studio toolbar editor, to create a bitmap and a "toolbar" resource.

During menu initialization, we can convert all the items into "owner draw" (a task that can be accomplished by an EWnd derived, called EMenuImager), but ... where do we store the data ?

For commands, we can think to a global map, indexed by command IDs: after all ... command IDs are global. The "value" of the map will be a SCmdDraw::XCmdChara holding the resource ID the image is from, and the relative image index.

Then, we can convert to owner-draw all the menu items during WM_INITMENUPOPUP and convert them back to normal during WM_UNINITMENUPOPUP. May be not so efficient, but it is safe in terms of memory management (we don't keep all strings having no ID, for example in submenus, from a menu).

It is important that those message are processed after eventual menu state modification induced by other wrappers. To be sure of this, I let the message handler to mark the messages as "handled" (no further processing), but I called Default() at the beginning of the handler.

So:

  • If you wrap first (for example) with EMenuUpdator and then with EMenuImager, the subclass window procedure will call EMenuImager that calls Default, causing EMenuUpdator to process first.
  • If you wrap first with EMenuImager and then with EMenuUpdator, the subclass procedure will call EMenuUpdator first and (because it left the messages as "unhandled") then EMenuImager.

In both cases, the sequence is first update the items, then, adapt them to be drawn.

Items drawing

Because there are various ways to draw menu items (in terms of effects and state rendering: think of Win2K or WinXP or VS-IDE), we can think of different implementation strategies to handle WM_DRAWITEM.

  1. Templetize EMenuImager and provide a parametric "traits" class that provides the OnDrawItem function: it has the drawback that the code is "static": the user cannot switch between different "traits": we have to provide different template instantiation, but this also means a multiple static or global data structure instantiation.
  2. Declare OnDrawItem as abstract, and implement it in different derived classes.
  3. Don't respond to WM_DRAWITEM, and do it in a separate EWnd derived, letting to the Windows kernel to do the polymorphism.
  4. Derive EMenuManager, give it a new message map that chains with the base's one (WTL style).

While A is not suitable for implementations like this, B introduces some constraints about the use of the functions.

C means declare a EMenuImager_xxx, to attach to the same HWND wrapped by EMenuImager, where xxx is the aspect we want to provide. It is probably the most flexible solution, but it's prone to generate strongly entangled classes: you define a Exxx that seems a HWND wrapper, but it is not functional by itself and relies on data generated (and defined) by another independent class.

D is the most traditional, but in this case seems optimum. The implementation provided is NGDI::EMenuImagerIDE.

Displaying Accelerators

A common practice in programs using accelerators, is to display the shortcut keys for commands on the right of the menu text.

Writing the shortcut names in the MENU resource it is not - however - a good practice: Message translation is based on HACCEL that is loaded from an "accelerator table" that is not itself the menu. You change the shortcuts by editing the accelerator table, but you must also change the menu text. And if a same command appear in many different menus ... may be it is not so easy to track.

An alternative can be to avoid to place the text in the MENU resource, but provide a way to alter the menu text before displaying it. This can be easily done in EMenuUpdator. By making this class aware of an accelerator table and by providing it the textual descriptions for all the keyboard keys, we can - just before displaying a command - browse the given accelerator table, find the accelerator for a given command, retrieve its text and then add it to the right of the menu.

And because this means "to associate an accelerator table to a window", we can hook to the SMsgFiltrerEvent singleton when becoming active (and unhook when becoming inactive), and call the TranslateAccelerator API. Because this event is generated by the message loop just before dispatching a message, this will - in fact - make the accelerators associated to the window, working.

All this is implemented in EMenuUpdator.

In particular, to create the text to be used to describe the shortcuts, I used a RC_DATA resource that is a sequence of a pair, composed by a WORD followed by a C-string. The WORD is a VK_xxx constant representing a keyboard functional key, and the C-string is its description. The last three record of the resource must have 0 as ID, and report "Shift+", "Ctrl+" and "Alt+" (or whatever description they have in a given localization) respectively.

In EMenuUpdator, the XKeyNames structure is a helper to read this resource: I used it wrapped into a SResourceData<>, attached to the HRSRC given by FindResource. The XKeyTexts structure is instead where the description are stored, indexed by the VK_xxx ID. (See its Load function implementation.)

To stay within the functionality of the IDE, I provided to the NLIB project the NLib.rc resource file, and the Accels.rc2 file. And also a NLib_res.h file containing the manifest constant definitions.

NLib.rc is configured to include NLib_res.h as its own header and the Accels.rc2 as an included resource file. Because Nlib is a library, NLib.rc must not be itself compiled. It must instead be included in the "exe" project resource file: in our case, the "W3" project has a resource file (W3.rc) that includes the traditional resouce.h and also the Nlib_res.h, and also includes in its body the NLib.rc file.

Resource files relations

The test project

I generated some projects to test and as samples of usage.

The executable is a pure Win32 project making use of NLib as static library.

The stdafx.h file includes the NLib/StdAfx.h, and - for non debug versions - defines the GE_FORCEINLINE symbol: this results in the NLib CPP files to be included at the end of their respective .h files, with all functions declared as "inline".

Because the solution contains two projects and because W3 depends on NLib, there is the need, even when the all code is inlined, to still have a Nlib.lib to link. This is obtained by providing a emptylib.cpp source just defining a local hidden symbol (no meaning, just to have something to compile, or no library is generated), and by differentiating the Debug configuration (where all files but emptylib.cpp are included in the generation process) and the release version (where the generation rule is inverted). emptylib.cpp is also configured to not use the precompiled headers.

The WinMain function is very simple:

int APIENTRY _tWinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPTSTR lpCmdLine, 
    int nCmdShow)
{
    NUtil::STrace::_Filter() = 2;
    
    NUtil::SWinMain winmainparams(hInstance);
    NWin::SMsgLoop loop(true);  //instantiate a loop

    CMainWnd wnd; 
    wnd.Create(hInstance);

    loop.Loop();

    return 0;
}

Essentially a CMainWnd is created and a message loop executed.

CMainWnd is declared as follows:

class CMainWnd: public NWin::EWnd
{
protected:
    NWin::EMenuUpdator _mnuupdt;
    NWin::EMenuImagerIDE _mnuimg;
public:
    CMainWnd() {}
    bool Create(HINSTANCE hInstance);
protected:
    LRESULT OnFileExit(WORD wNotifyCode, 
           WORD wID, HWND hWndCtl, bool& bHandled);
    LRESULT OnExecSomeCommand(WORD wNotifyCode, 
           WORD wID, HWND hWndCtl, bool& bHandled);
    LRESULT OnCreate(LPCREATESTRUCT lpcs, bool& bHandled);
    LRESULT CMainWnd::OnPaint(bool& bHandled);

    BEGIN_MSG_MAP(CMainWnd)
        ONMSG_WM_CREATE(OnCreate)
        ONMSG_WM_PAINT(OnPaint)
        COMMAND_ID_HANDLER_U(ID_FILE_EXIT, OnFileExit)
        COMMAND_ID_HANDLER_U(ID_HELP_ABOUT, OnExecSomeCommand)
        COMMAND_ID_HANDLER_U(ID_FILE_NEW, OnExecSomeCommand)
        COMMAND_ID_HANDLER_U(ID_FILE_OPEN, OnExecSomeCommand)
        COMMAND_ID_HANDLER_U(ID_FILE_SAVE, OnExecSomeCommand)
    END_MSG_MAP()
};

Note the COMMAND_ID_HANDLER_U macros in the message map, and note that some commands had been implemented through the same "placeholder" function OnExecSomeCommand. The Create function is a shortcut to handle WNDCLASSEX registration and the real window creation, via EWnd::CreateWnd(...).

The OnCreate function (handler for WM_CREATE) initializes the other member wrappers and attach them to the same CWnd. Of course, there's no need for those wrappers to be members, but enclosing them into a same class makes the data structure more ordered.

Here are the two bodies:

bool CMainWnd::Create(HINSTANCE hInstance)
{
    LPCTSTR wcname = _T("W3MainWnd");
    NUtil::SResID resWapp(IDR_WApp);
    static NWin::SWndClassExReg clsrg(true, hInstance, wcname, resWapp);
    //instantiate a window class
    if(!CreateWnd(hInstance, wcname, _T("W3 test"), 
        WS_VISIBLE|WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, 
        LoadMenu(hInstance, resWapp), NULL))
        return false;
    
    return true;
}

LRESULT CMainWnd::OnCreate(LPCREATESTRUCT lpcs, bool& bHandled)
{
    Default();
    NUtil::SResID resWapp(IDR_WApp);
    
    _mnuupdt.LoadAccelerators(HInstance(), resWapp);
    _mnuupdt.Attach(*this);
    _mnuimg.GetCmdImgs().LoadCmdImages(HInstance(), resWapp);
    _mnuimg.Attach(*this);
    
    return 0;
}

Note that at the moment of attaching the additional wrapper, the window already exists. In fact, at the end of OnCreate, the window appears to have three referring wrappers. Note also that non "chain" is done in the message map: this is automatically done by the EWnd::Attach-ing and Detach-ing processes.

The menu commands File/New, File/Open, File/Save and Help/About have been implemented through a dummy function.

The commands Menu/AutoUpdate and Menu/Images are toggles. They are checked if the _mnuupdt and _mnuimg are respectively attached. The commands are implemented by Attaching / Detaching the inner wrappers alternatively.

Note that, when detaching _mnuupdt, menus stop to update their own state. If the detachment is done immediately after the application startup, without displaying other menus, menus will appear as "all enabled", and without shortcut descriptions. If it is done later, menus will retain the "last set state".

Debug "verbosity"

You can see at the very beginning of WinMain, the line "NUtil::STrace::_Filter() = 2;". This has been done to reduce the debug output as generated by STrace and STRACE.

In the code, I set all message retrieving as having "levl 0" and message dispatching / memory allocation to have "level 1". If you like to have a more verbose debug output, you can set the value to 1 or even to 0. The inconvenience is that menu navigation becomes "slow" because of the wrapping / unwrapping activity of GDI objects and all the messages that activity generates.

Conclusion

May be I'm still reinventing wheels, but - strange - I really enjoyed while enhancing these features even more while writing code with WTL.

I don't pretend all this to become a new library for "real good apps". The only thing I hope is someone learns something with this.

And since it was a nice experiment, I will continue: docking toolbars, docking windows, serializations ... many arguments to treat in future articles.

History

  • 2004/3/29 - some text revision.
  • 2004/4/26 - code bug fixed

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Emilio Garavaglia
Architect
Italy Italy
Born and living in Milan (Italy), I'm an engineer in electronics actually working in the ICT department of an important oil/gas & energy company as responsible for planning and engineering of ICT infrastructures.
Interested in programming since the '70s, today I still define architectures for the ICT, deploying dedicated specific client application for engineering purposes, working with C++, MFC, STL, and recently also C# and D.

Comments and Discussions

 
GeneralMy vote of 1 PinmemberAndy Bantly13-Nov-10 6:17 
GeneralRe: My vote of 1 PinmemberEmilio Garavaglia14-Nov-10 3:24 
GeneralPart 3 is out Pinmemberemilio_g27-May-04 2:20 
Questionhow about the thunk? PinmemberChauJohnthan9-May-04 15:34 
AnswerRe: how about the thunk? Pinmemberemilio_g9-May-04 21:07 
GeneralRe: how about the thunk? PinmemberChauJohnthan13-May-04 14:11 
GeneralRe: how about the thunk? Pinmemberemilio_g13-May-04 22:25 
GeneralRe: how about the thunk? PinmemberChauJohnthan15-May-04 7:01 
GeneralRe: how about the thunk? PinmemberChauJohnthan22-May-04 19:57 
GeneralRe: how about the thunk? Pinmemberemilio_g23-May-04 2:11 
GeneralRe: how about the thunk? PinmemberChauJohnthan23-May-04 6:32 
GeneralRe: how about the thunk? Pinmemberemilio_g24-May-04 4:51 
GeneralRe: how about the thunk? PinmemberChauJohnthan25-May-04 0:14 
GeneralRe: how about the thunk? PinmemberChauJohnthan24-Apr-05 10:38 
GeneralRe: how about the thunk? Pinmemberemilio_grv25-Apr-05 22:34 
GeneralRe: how about the thunk? PinmemberChauJohnthan26-Apr-05 0:07 
GeneralRe: how about the thunk? Pinmemberemilio_grv26-Apr-05 7:58 
GeneralRe: how about the thunk? PinmemberChauJohnthan26-Apr-05 10:33 
GeneralRe: how about the thunk? Pinmemberemilio_grv26-Apr-05 22:16 
GeneralRe: how about the thunk? PinmemberChauJohnthan26-Apr-05 23:28 
GeneralGreat endeavor...crash on exit in VC7.1 PinmemberNorman Bates3-Apr-04 9:23 
GeneralRe: Great endeavor...crash on exit in VC7.1 Pinmemberemilio_g4-Apr-04 8:33 
GeneralRe: Great endeavor...crash on exit in VC7.1 PinmemberNorman Bates5-Apr-04 4:04 
GeneralRe: Great endeavor...crash on exit in VC7.1 Pinmemberemilio_g5-Apr-04 5:00 
GeneralRe: Great endeavor...crash on exit in VC7.1 Pinmemberemilio_g5-Apr-04 22:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 27 Apr 2004
Article Copyright 2004 by Emilio Garavaglia
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid