On Sunday, 8 September 2013 at 23:52:46 UTC, Stewart Gordon wrote:
1. Define a hierarchy of dummy classes for the handle types.
No actual objects will exist of these types, but since classes
are reference types they can be set to null.
But there's a nasty bug lurking in this: if somebody tries to
compare handles using ==, it will dereference the pointer, and
look in vain for the vtable and the opEquals method defined
therewithin ... cue major chaos.
This is a showstopper. It breaks existing code in subtle ways.
2. Do 1, but use pointers to these classes as the handle types.
class HANDLE_ {}
alias const(HANDLE_)* HANDLE;
class HWND_ : HANDLE_ {}
alias const(HWND_)* HWND;
This would avoid the dereferencing behaviour. It's to be hoped
that all Windows programmers know that, although handles are
declared as pointer types, they cannot meaningfully be
dereferenced. But what would the GC do, especially given that
there are two levels of indirection neither of which points to
an appropriate memory location?
This is fine as far as the GC goes. Error messages are not great,
though: "cannot implicitly convert expression (h) of type
const(HANDLE_)* to const(HWND_)*". Not sure about object code -
one possibility would be to fix DMD to support class declarations
(e.g. "class HWND_ : HANDLE_;" with no body).
3. Keep the current implementation, but implement an enum
member NULL in each handle type, like this:
struct HWND {
HANDLE h;
alias h this;
enum HWND NULL = cast(HWND) 0;
}
Programmers still can't use null, but writing HWND.NULL might
be acceptable as the next best thing.
This is not much better than writing HWND.init (which is how I've
been working around the issues).
4. Abandon this hierarchy idea and just define DECLARE_HANDLE
the same way as the MinGW C headers do.
Supporting the functionality of the STRICT define would be a good
start; I don't think that involves any hierarchies. I think an
important goal is to avoid breaking most correct code and
maximize compatibility with C code, so using a special literal
for null is out.
By the way, another problem with the current implementation is
casts. At the moment, they can get quite verbose, and confusing
to implement (one needs to look up the hierarchy of the handle
types).
For example, calling LocalFree with a LPCWSTR as when using
FormatMessage now looks like this:
LocalFree(HLOCAL(HANDLE(lpMsgBuf)));
Having the handle types be some kind of pointer would allow using
a more conventional casting syntax.