Skip to main content
Email Password   helpLost your password?

Introduction

I wrote this article a few for joke and few as a challenge to myself: I'd been a Win32 C programmer, but, with C++, I always relied on MFC, WTL, .NET, WxWin or some "framework" or wrapper.

One of the advantages is that they "simplify" certain operations by hiding certain details. In contrast, when you have to do something different than usual, the entanglement that exist between the framework classes can make the work quite hard.

Do you ever have the feeling - when overriding certain MFC classes or function - to hack-in somebody else's defined behavior? How often did you find with strange "side effects" due to the fact that Document, Frames and Views often destroy each other in a not always obvious order?

What follows is a way to introduce a different approach. Of course, I don't have the man power of the entire MS AFX team, so don't expect a full replacement of WTL or ATL. I just want to introduce a different possible point of view.

And because I'd like to make the proposed classes reusable, I tried a design able to reduce the classes entanglement to the minimum. In particular, if class A needs class B, I wanted to avoid that class B also needed class A. Thus, you can use this classes also outside my framework, may be even in a MFC application as well.

However, I only relied to STL containers and algorithms and to some CP authors' ideas: in particular Paul Bartlett and Alex Farber. (I didn't reuse their code, but - in fact, I reinterpreted their ideas.)

Conventions

I deployed all the classes in agreement with this article. I also used namespaces, an put all the namespaces in a root namespace named "GE_". If this names gives trouble to some other libraries, just replace all the occurrence in files with something else.

Shared Ownership Wrappers

This is the key for all this implementation: Win32 API is full of handles of various kinds, you must "obtain", "use" and "release".

In my intention, a wrapper should not rewrite C API: I don't feel any particular need to replace ShowWindow(hWnd, SW_SHOW) with pWnd->ShowWindow(SW_SHOW): the "actors" are exactly the same, and rewriting thousands of prototypes just to put the first parameter outside the parenthesis ... it's not a good investment, for me.

More interesting is to wrap handles to give them protection from improper destruction politics, or forgetting to destroy them, or offering more effective constructors for functions having often many parameters that takes often same values.

In this implementation, a Wrapper is something similar to a "reference counting smart pointer" (in fact, it is a generalization): it may be an "owner" of the handle it wraps (thus, destroying it when no more "owners" are holding it) or an "observer" that behaves as "invalid" when all "owners" have left.

But, because we may have a number of different handles with different management politics, I decided to split the implementation in a number of template classes inheriting from each other.

The Architecture

The heart of all the mechanism is a cooperation between a WrpBase and a XRefCount.

WrpBase provides the "protocol" for a wrapper: refers to a "reference counter" and can be "Attached" and "Detached" to the object it wraps. It also can be set as "owner" or "observer", be "Valid" or not, and return STL iterators to walk onto a wrapper collection.

XRefCount, in turn, maintains information about how many (and eventually "who") are the wrappers wrapping a given value or handle, and provides some helper functions to the WrpBase to retrieve and update those information.

WrpBase is designed to be derived (one of its template parameters, W, is the class you derive from it) and calls all it functions after static_cast-ing its "this" pointer to W*. This allows you to override its functions.

Also, the behavior of WrpBase depends on a number of "helpers" that are provided by a traits class WrpBase inherits from. The purpose of this "traits class" is also to provide a convenient number of typedef-s that defines the way the wrapped object is stored in the wrapper, is received as a parameter from member functions, is given as a return value, or is de-referenced.

Traits classes are implemented in various versions and inherits the type definition from "types classes".

To close everything into something that can be assigned, copied etc., a Wrp class is then designed to inherit from a WrpBase or derived class, applying assignment operator, copy constructors, equality (==) and ordering (<) operators, access (->) and dereference (*) and so on, using the WrpBase defined protocol (after your customization, if any).

The following picture gives a sketch on all this stuff.

Wrp architecture

Inheritance goes from top to bottom. XRefCount knows (because of a template parameter) about "YourClass", Wrp knows because of inheritance, and calls WrpBase prototyped functions in the way you can eventually override them. WrpBase, in turn, uses what inherited from "traits" and from "types".

The implementation

Types

"types" is implemented in two different ways:

Both the classes define the following types:

type description types_wrp_hnd types_wrp_ptr
SH What the wrapper stores to represent H H H*
IH What the wrapper will receive as input parameter in functions const H& H*
OH What the wrapper will return from its functions const H& H*
RH What the wrapper will return as "dereference" const H& H&

In specific implementations, may be SH, IH, OH and RH can be different (for example: you can return a proxy object to store a reference ...).

In any case, it is required that implicit conversion can work when converting IH into SH, SH into OH, and OH into IH.

Traits

"traits" is implemented in three different ways:

Normally, the "Types" parameter defaults to types_wrp_hnd<H> in the first case and to types_wrp_ptr<H> in the other two cases. But - for some particular cases - other solutions can be afforded.

The purpose of the "traits" classes is to provide some low-level helper functions to standardize to the upper "layer" classes an interface to operate on the various "types".

An example is the code of traits_wrp_hnd itself:

template<class H, class Types=types_wrp_hnd<H> >
struct traits_wrp_hnd: public Types
{
    typedef Types TT;
    static bool equal(TT::IH left, TT::IH right) { return left == right; }
    static bool less(TT::IH left, TT::IH right) { return left < right; }
    static LPVOID Key(TT::IH h)  { return (LPVOID)h; }
    template<class A> static void from(const A& a, TT::SH& h) { h = (H)a; }
    static TT::IH invalid() {static TT::SH h(NULL); return h; }
    static TT::OH Access(TT::IH h) { return h; }
    static TT::RH Dereference(TT::IH h) { return h; }
    void Create(TT::SH& h) { h = invalid(); }
    void Destroy(TT::SH& h) { h = invalid(); }
};

Note that "invalid" is implemented to return a "NULL" value. For certain kind of handles, this will be probably overridden.

WrpBase

It is defined as:

template<
    class H,    //what we are wrapping

    class W,    //outside wrapper (derived from here)

    class t_traits, //invalid H behaviour

    class t_refCount    //reference counter type used

>
class WrpBase:
    public t_traits
{
public:
    typedef WrpBase TWrpBase;
    typedef W TW;
    typedef t_refCount TRefCount;
    typedef t_traits TTraits;
    typedef H TH;
    typedef TTraits::TT TT;
    friend WrpBase;
    friend TRefCount;
    friend TTraits;
protected:
    TT::SH _hnd;
    t_refCount* _pRC;
    bool _bOwn;
    ...
}

It provides storage for a SH member, a pointer to a t_refCount class (supposed to have the same prototyping as XRefCount_base), and a bool defining if this wrapper is an "owner" or an "observer". It also implements the following:

They are implemented in terms of other functions (always through the pThis() function, that converts this into W*) and in terms of TRefCount functions. It also provides to the TRefCOunt a set of "do nothing" call back functions:

Reference Counting

Must derive from XRefCount_base, overriding its functions:

class XRefCount_base
{
public:
    bool _bValid;
    XRefCount_base() { _bValid = true; }

    int owners() { return 0; }
    int observers() { return 0; }
    int reference() { return 0; }

    void AddRef(W* pW, bool bOwn)
    {}
    
    bool Release(W* pW, bool bOwn)
    {return false;}

    //doesn't iterate

    iterator begin() { return NULL; }
    iterator end() { return NULL; }
};

It's actually implemented in two versions:

The first can be used with types that requires only a reference counting, the last with types you need to keep track of "who" are the wrappers.

Reverse Mapping

XMap hosts a static std::map<LPVOID, XRefCount_base*>, providing Map, Unmap and Find functions.

Wrappers know what they wrap, and know who is the associated reference counter. But if we're assigning a value to a wrapper, we must have a way to find out (in the case that "value" is already wrapped by someone else) if there is (and who is) the associated XRefCount.

One of the function that comes with the traits classes is Key(IH). It converts the wrapped type into an LPVOID.

This can be done with C-syle cast (as with traits_wrp_hnd), static_cast (as with non polymorphic pointers, in traits_wrp_ptr) and dynamic_cast (for polymorphic pointers, as in traits_wrp_dynptr).

Particularly interesting is the last one: given a complex object (with many bases) different pointers to different components can be different (in terms of absolute value) but a dynamic_cast to LPVOID always return the same base address (the one of the entire object). This allows to share the ownership of a complex object between different smart pointers referring to the different components: they all can find out the same reference counting object.

Operators ( Wrp<B> )

Conversion constructors, copy constructors, assignment operators etc. cannot be inherited as ordinary functions. Their override requires particular attention, so I preferred to concentrate all this stuff into a specific template class. Wrp<B> is the "upper layer class". B is the class Wrp is made to derive from. It can be every WrpBase derived class. It implements assignment, copy, conversion and destruction in terms of Detach/Attach.

Some specific cases: smart pointers

To specialize a wrapper, a method could be the following:

  1. declare a struct derived from WrpBase specifying the required parameters, then
  2. typedef a Wrp<yourstruct>, to give assign and copy functionality.

A very simple case when this happens is the definition of smart pointers:

template<class T>
struct Ptr
{
    struct StatBase:
        public WrpBase<T, StatBase, 
          traits_wrp_ptr<T>, XRefCount<StatBase> >
    {};

    struct DynBase:
        public WrpBase<T, DynBase, 
          traits_wrp_dynptr<T>, XRefCount<DynBase> >
    {};

    typedef Wrp<StatBase > Static;
    typedef Wrp<DynBase > Dynamic;
};

The two structures define the behavior for a static_cast and a dynamic_cast pointer wrapper.

If A is a class, a smart pointer to A can be ...

typedef Ptr<A>::Dynamic PA;.

Smart Handle

In the same way, we get "smart handle" using:

template<class H, class W>
struct HndBase:
    public WrpBase<H, W, traits_wrp_hnd<H>, XRefCount<W> >
{};

Note that, while Ptrxxx structs close the wrapper inheritance (they give themselves to WrpBase as W parameter) HndBase doesn't: there is still a W parameter.

This is because, while for pointer it is clear that new and delete are the creation and destruction policy (Create and Destroy comes from the traits classes this way), for handles, it's all still to be defined. Different handles have different creation and destruction API. HDC, then, have many of them! So we must complete later.

Of course, for certain particular objects, you may also want to do something else than "new" or "delete": in this case, you must do yourself the definition of a struct that derives from WrpBase (and closes it inheritance) and then wrap it into a Wrp.

Chained actions

Using XRefChain as WrpBase parameter, we can chain "wrappers to the same entity" together and walk on them.

An interesting thing that can be done is to associate actions to this "walking". The idea is to provide - for example- a way to dispatch Windows messages to HWND wrappers. But not only.

EChainedAction<W, I, A> is a class designed do be derived (W is its derived class) that associates an action having A as parameter to the walk on the iterator I, that must be an STL compliant forward iterator, that dereferences into W* (whose I::operator*() returns a W*). Such an iterator can be obtained, for example, by WrpBase::get_range(...).

The function Act(const A& a, iterator first, iterator last), iterates from first to last calling the member function OnAction(const A& a) that is supposedly overridden in W.

The way the iteration is done, however, is not a pure flat loop: it is instead a recursion over stored parameters: Act fills in a data structure on stack and saves its address into a per-thread saved static variable, keeping the previous address (a per-thread variable is a variable that's accessed through a map whose key is the current thread ID) than call Default(). Default retrieves the data structure and iterates calling OnAction. If OnAction returns true, Default returns immediately true, otherwise iterates to the next element until "last" is reached. At that point returns false.

Thus, in your OnAction override, you can:

More or less, it's like subclassing a window procedure with another, and if needed, call the previous. An arbitrary number of times.

Events

A combination between smart pointers and chained actions are events.

Here, they are implemented as "functors" to be declared as member variables of a class (the event source) that are attached by internal objects (the "sinks") that dispatch an action to registered "receivers".

Event<A> is a struct that's few more than an operator()(const A&) member, that supposes that the event has to be wrapped by chainable wrappers. The operator gets the chain, and calls Act.

EventRcv<D> is a base for a generic derived class (D), providing RegisterEvent<A>(Event<A>&, bool(D::*pmemfun)(consty A&) ) and UnregisterEvent<A>(Event<A>&). The first function creates an XTypedSync<A,R> that associates the event to the given member function. The second clears the association.

XTypedSync<A,R> derives from XSync<A>, that is a chainable wrapper for Events having parameter A. XSync implements OnAction delegating to a virtual DoAction function, that is implemented in the derived XTypedSync, calling the given member function (via a pointer to a member saved during association). The use of virtual function is required because different XTypedSync (referring to different receivers, so having different R parameters) must chain together all as XSync<A>.

Thus, calling an event, will call the registered member function for all the associated receivers.

Registering a WNDCLASS

If you think that filling in the field of certain data structure like WNDCLASSEX is a tremendously annoying task, this wrapper is for you:

SWndClassExReg registers a window class, un-registering it when no more references are available.

It derives from WrpBase, customizing Destroy.

Constructors attempt to fill a WNDCLASSEX with the supplied parameters:

The message loop (SMsgLoop)

The message loop is implemented by SMsgLoop. You can create as many instances you need. Typically one will be in WinMain, while others may be where "modal loops" are required (for example: when asking mouse coordinates to detect where to place an object).

The "loop" itself is implemented by a LoopBody function (that is not itself a loop) implementing message retrieving and dispatching, with also a bit of message filtering / translation and idle processing. Such a function is called by LoopWhile that loops until a passed bool reference becomes false or until WM_QUIT is received. LoopWhile is called by Loop, that provides an "always true" value to LoopWhile.

This "breaking the loop" in three components allow to use SMessageLoop also for modal loop to be run inside an application (using LoopWhile) or to dispatch message during a loop calculation (calling LoopBody from inside the hosting loop).

SMessageLoop provides also a member to store an HWND (that can be the application main window) whose destruction will cause WM_QUIT to be posted.

If SMsgLoop is created with the bAutoQuit parameter set to true, the first window that is created through that message loop becomes the message loop main window.

Idle processing and Message filtering

Idle processing and message filtering are implemented through global events.

This solution avoids the risk to create entanglement between the message loop implementation and - for example - a window implementation. You can use my windows also in other frameworks where my message loop is not used. The only requirement is to properly generate the events.

SIdleEvent and SMsgFilterEvent are both EGlobals and Events.

The SIdleEvent parameter is the SMsgLoop that calls the event. If you are originating yourself the event, you can pass NULL.

SMsgFilterEvent parameter is the Win32 MSG structure. In your event hander, return true to indicate that the passed message was filtered.

Wrapping HWND: subclassing windows

HWND wrappers must have the capability to receive window messages. This implies them to be both chaining WrpBase and EChainedAction, but that's not enough.

There's also the need to get the messages from the system. This requires a mechanism of subclassing/reclassing. And also, there is the need to dispatch messages to possibly different member functions. There are different approaches to do this:

None of those approaches are perfect: the first two strongly deal on macros, and the third requires a heavy data structure and is not "symmetrical": there is an object that "owns" the HWND and others attaching to it.

This "imperfections" have been compensated by a number of wizards that help in compiling message maps or chaining event handlers. Considering the big number of messages we have to deal with, they are probably the best compromise between programming techniques and tool usability. In my experience, I found the ATL/WTL approach more flexible, so I did the following:

  1. Wrappers for the same HWND chain together. The first attachment and the last release are used to subclass / reclass the wrapped HWND.
  2. OnFirstAttach is overridden to do window subclassing.
  3. A subclassing window procedure "Act"s on the chained action relative to HWND.
  4. OnAction is overridden to call a well-known virtual function with the same prototyping of the ATL PocessWindowMessage.
  5. Default is overridden to call the previous window procedure if the action returns unhandled.
  6. To store the previous window procedure, we need a place that is shared among all the wrappers of the same window. Thus the XRefChain must be derived to host this value.

The result for all this is EWnd.

Its declaration is the following:

class EWnd:
    public IMessageMap,
    public NWrp::WrpBase<HWND, EWnd,NULL,traits_wrp_types<HWND>, XRefChainWnd>,
    public NWrp::EChainedAction<EWnd, EWnd::iterator, LPWndProcParams>,
    public NUtil::EAutodeleteFlag
{
public:
    void OnFirstAttach(NSmart::XRefCount_base* pRC); //do window subclassing

    static bool Default();  //do superwndproc calling

    void Destroy(HWND& hWnd);  //destroy HWND (only owners does !)

    bool OnAction(const LPWndProcParams& a); //call ProcessWindowMessage

    
    //copy and assign

    EWnd() {SetOwnership(false);}
    EWnd(const EWnd& w) { SetOwnership(false); AttachRefCounting(w); }
    EWnd& operator=(const EWnd& w) { AttachRefCounting(w); return *this; }
    EWnd& operator=(HWND h) { Attach(h); return *this; }
    explicit EWnd(HWND h) { SetOwnership(false);  Attach(h); }

    //IMessageMap defaul implementation

    virtual bool ProcessWindowMessage(
        HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, 
        LRESULT& rResult, DWORD msgMapID) { return false; }

    //some helpers common for all the windows

    LRESULT ForwardNotifications(UINT uMsg, 
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    LRESULT ReflectNotifications(UINT uMsg, 
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    BOOL DefaultReflectionHandler(HWND hWnd, UINT uMsg, 
        WPARAM wParam, LPARAM lParam, LRESULT& lResult);

    //window creation

    bool CreateWnd(
        HINSTANCE hInst,
        LPCTSTR clsName,
        LPCTSTR wndName,
        DWORD dwStyle,
        LPCRECT lprcPlace,
        HWND hwndParent,
        HMENU hMenu,
        LPVOID lpParams
        );
};

Notes:

  1. IMessageMap is an interface declaring the ProcessWindowMessage function in ATL style.
  2. derives from ERefChaining, but it uses XRefChainWnd. It derives from XRefChain, but also hosts a WNDPROC function pointer and a recursion counter.
  3. derives from EChainedAction, using LPWndProcParams. XWndProcParams is a struct taking hWnd, uMsg, wParam, lParam and lResult.
  4. derives from EAutoDeleteFlag. If set to true (the default is false) the wrapper deletes itself when the window is destroyed
  5. the wrapper is always created as "non owner" (observer). Setting it as "owner" will destroy the HWND when all the owners are detached.

The "idiot" program (adjective, not genitive!)

Too simple to be useful, apart demonstrates all the stuff at work.

#include "stdafx.h"


#include "NLib/MsgLoop.h"

#include "NLib/Wnd.h"


using namespace GE_;

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

    
    LPCTSTR wcname = _T("W1MainWnd");
    NWin::SWndClassExReg wc(true, hInstance, wcname); 
    //instantiate a window class

    
    NWin::EWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, 
        WS_VISIBLE|WS_OVERLAPPEDWINDOW, 
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, NULL, NULL);

    loop.Loop();

    return 0;
}

Create a window based on DefWndProc (passed as default parameter to SWndClassExReg) that does ... really absolutely nothing ... in 76KB executable.

Not the shortest exe I've ever seen in my old life, but consider all traits functions, std::maps, lists etc. They improve flexibility but have a cost!

Hello World: GDI wrappers

AS a first step, we need to derive EWnd into SWnd and implement a message map dispatching WM_PANT.

Then we need message map macros,...

Then we need "featured" GDI object wrappers ... or include the GDI+ library.

Message map macros

Inspired by ATL macros, they've the same names to allow the IDE wizard to do their dirty work in the same way, but .. they're not the same of ATL (they'd been designed to work into an EWnd wrapper, not CWnd or CWindow!). They are included in MessageMap.h. ATL basic macros work also well, but not WTL macros.

Message cracker macros, again have same names and parameters of WTL ones, but different implementation.

Note: I obtained them with a heavy use of "find and replace" from the original sources. They are hundredths, so I cannot test all them singularly, so, because the process had been the same for all, and because the ones I tested worked, ... I suppose all of them work.

But it is very easy to test as they are used. Simply ... don't forget!

At this point, a very simple "hello world" program could be this:

// W2.cpp: W32 test application


#include "stdafx.h"


#include "NLib/MsgLoop.h"

#include "NLib/Wnd.h"


using namespace GE_;

class CMainWnd: public NWin::EWnd
{
public:
    BEGIN_MSG_MAP(CMainWnd)
        GETMSG_WM_PAINT(OnPaint)
    END_MSG_MAP()
    void OnPaint();
};


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

    
    LPCTSTR wcname = _T("W1MainWnd");
    NWin::SWndClassExReg wc(true, hInstance, wcname); 
    //instantiate a window class

    
    CMainWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, 
        WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, NULL, NULL);

    loop.Loop();

    return 0;
}

void CMainWnd::OnPaint()
{
    PAINTSTRUCT ps;
    BeginPaint(*this, &ps);
    
    NWin::SRect R; 
    GetClientRect(*this, &R);
    DrawText(ps.hdc, _T("Hallo World !"),-1, 
          R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    EndPaint(Value(), &ps);
}

Isn't it nice? Not yet: what are those BeginPaint - EndPaint pairs? Why don't we wrap HDC into a wrapper that manages properly the Begin-End or Get-Release pairs? And what about saving and restoring the state before quit ?

Wrapping HDC

The idea to save the state of a DC when wrapping, it seems good, but ... where should be the state stored ?

There are two possible answers:

Given the above consideration, I provided a set of HDC wrappers that can interoperate (they use the same XRefCount) but with different Attach/Detach policy.

All classes are based on SHdcSave_base<W>, that override OnAddRef and OnRelease to save and restore the DC state. Then:

With SHdcPaint, the OnPaint method becomes this:

void CMainWnd::OnPaint()
{
    NGDI::SHdcPaint dc(*this);

    NWin::SRect R;
    GetClientRect(*this, R);
    DrawText(dc, _T("Hallo World !"),-1, 
         R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

HPEN, HBRUSH, HFONT

GDI objects can be created in a variety of ways, but always destroyed with the same API: DeleteObject.

Thus, SGdiObj<H> wraps any HGDIOBJECT with an HndBase, overriding the Destroy callback by calling DeleteObject.

It is important that GDI object destruction takes place when objects are not selected into some HDC. This can be obtained by initializing the GDI wrappers before to initialize HDC wrapper. This will let HDC wrapper to go out of scope before restoring its state and leave free the GDI object that can be destroyed by their own wrappers.

Here is the "OnPaint" method, with a different font:

void CMainWnd::OnPaint()
{
    NWin::SRect R; 
    GetClientRect(*this, R);

    NGDI::TFont font(CreateFont(60,0,0,0, FW_BOLD,
        0,0,0, 0,0,0,ANTIALIASED_QUALITY, FF_SWISS, NULL));
    
    NGDI::SHdcPaint dc(*this);
    SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(dc, GetSysColor(COLOR_WINDOW));
    SelectObject(dc, font);

    DrawText(dc, _T("Hallo World !"),-1, 
        R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

Note that font is created before dc, hence dc will be destroyed first. When this happens, "RestoreDC, and then, EndPaint" are called. RestoreDC will deselect the font, EndPaint will close the painting. At that point, font destruction will delete the GDI object.

Further issues

OK: now the big picture is done. I'm going to further refinements and to some more specific and detailed classes. But is is probably a better subject for subsequent articles.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionCan't compile in vs2005 Pin
wk19830318
23:11 3 Jun '08  
AnswerRe: Can't compile in vs2005 Pin
emilio_grv
22:21 4 Jun '08  
GeneralPart 2 is out Pin
emilio_g
22:23 25 Mar '04  
GeneralCode updated Pin
emilio_g
7:44 2 Mar '04  
GeneralCode updated Pin
emilio_g
6:57 17 Feb '04  
GeneralGood, but... Pin
Rui Dias Lopes
8:09 5 Feb '04  
GeneralRe: Good, but... Pin
emilio_g
21:59 5 Feb '04  
GeneralWTL? Pin
zarzor
7:29 5 Feb '04  
GeneralRe: WTL? Pin
emilio_g
22:01 5 Feb '04  
GeneralRe: WTL? Pin
John M. Drescher
5:30 6 Feb '04  
GeneralRe: WTL? Pin
Robert Edward Caldecott
23:22 8 Feb '04  
GeneralRe: WTL? Pin
emilio_g
3:46 9 Feb '04  
GeneralRe: WTL? Pin
John M. Drescher
8:30 9 Feb '04  
GeneralRe: WTL? Pin
Robert Edward Caldecott
8:51 9 Feb '04  
GeneralRe: WTL? Pin
melwyn
19:47 10 Feb '04  
GeneralRe: Lots wrong with WTL! Pin
TomM
14:28 8 Mar '04  
GeneralRe: Lots wrong with WTL! Pin
emilio_g
4:33 11 Mar '04  
GeneralRe: Lots wrong with WTL! Pin
John Carson
21:31 28 May '04  
GeneralYou might be interested in the VCF [modified] Pin
Jim Crafton
7:21 5 Feb '04  
GeneralRe: You might be interested in the VCF Pin
emilio_g
22:03 5 Feb '04  
GeneralRe: You might be interested in the VCF Pin
Jim Crafton
8:52 6 Feb '04  


Last Updated 1 Mar 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009