www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - WindowsAPI - Problem with DECLARE_HANDLE definition

reply Stewart Gordon <smjg_1998 yahoo.com> writes:
It has just come to my attention that there's a problem with the DECLARE_HANDLE
template 
in the Win32 bindings.

This is the definition in MinGW:

     #define

And this is the definition in our bindings:

     package template DECLARE_HANDLE(string name, base = HANDLE) {
         mixin ("struct " ~ name ~ " {
             " ~ base.stringof ~ " h;
             alias h this;
         }");
     }

which when mixed in becomes something like

     struct HWND {
         HANDLE h;
         alias h this;
     }

The idea behind this was to effectively create a taxonomy of handle types, each
implicitly 
convertible to handle types higher up the chain.  This was straightforward when
we had 
typedefs.

The problem is that null no longer works.  How to fix?  Ideas that come to mind:


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.


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?

Moreover, will defining classes in the bindings cause object code to be
generated for 
them, which the program will later rely on in order to link?  This is something
I am 
trying to get rid of completely.


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.


4. Abandon this hierarchy idea and just define DECLARE_HANDLE the same way as
the MinGW C 
headers do.


What do people think we should do?

Stewart.

-- 
My email address is valid but not my primary mailbox and not checked regularly.
 Please 
keep replies on the 'group where everybody may benefit.
Sep 08 2013
next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On 9/9/2013 8:52 AM, Stewart Gordon wrote:
 What do people think we should do?
Eliminate declare handle and alias all HANDLE types to void*.
Sep 08 2013
parent "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Monday, 9 September 2013 at 01:25:00 UTC, Mike Parker wrote:
 On 9/9/2013 8:52 AM, Stewart Gordon wrote:
 What do people think we should do?
Eliminate declare handle and alias all HANDLE types to void*.
I think this is the lazy and shortsighted answer. Even the official Windows headers have a strict typing mode (the STRICT define), which is recommended for new applications: http://msdn.microsoft.com/en-us/library/windows/desktop/aa383731(v=vs.85).aspx Most code would benefit from additional compile-time checks, as the number of places where a cast is required is small. Stewart is attempting to reduce this number even further by defining a handle type hierarchy, something which isn't possible in C but might be possible in D.
Sep 08 2013
prev sibling next sibling parent "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
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.
Sep 08 2013
prev sibling parent reply "Kagamin" <spam here.lot> writes:
On Sunday, 8 September 2013 at 23:52:46 UTC, Stewart Gordon wrote:
 The problem is that null no longer works.  How to fix?
I'd say, strong handles shouldn't act as pointers (and shouldn't contain pointers), so null shouldn't work, I use HANDLE(0) instead of null. Use void* for maximum compatibility (weak handles).
Sep 11 2013
next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 9/11/13, Kagamin <spam here.lot> wrote:
 I'd say, strong handles shouldn't act as pointers (and shouldn't
 contain pointers), so null shouldn't work.
"NULL" is already used in a ton of WinAPI C/C++ code and MSDN documentation, it would be a major pain in the ass to have to use e.g. HWND(0) instead.
Sep 11 2013
parent reply "Kagamin" <spam here.lot> writes:
On Wednesday, 11 September 2013 at 18:29:09 UTC, Andrej Mitrovic 
wrote:
 On 9/11/13, Kagamin <spam here.lot> wrote:
 I'd say, strong handles shouldn't act as pointers (and 
 shouldn't
 contain pointers), so null shouldn't work.
"NULL" is already used in a ton of WinAPI C/C++ code and MSDN documentation, it would be a major pain in the ass to have to use e.g. HWND(0) instead.
In my experience writing a winapi call already takes some effort and specifying HANDLE(0) instead of NULL doesn't add any comparable difficulty.
Sep 19 2013
parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 9/19/13, Kagamin <spam here.lot> wrote:
 In my experience writing a winapi call already takes some effort
 and specifying HANDLE(0) instead of NULL doesn't add any
 comparable difficulty.
It adds difficulty in having to fix existing code that will be broken.
Sep 19 2013
parent "Kagamin" <spam here.lot> writes:
I don't think strong handles are backwards compatible: legacy 
code may as well rely on HANLDE being void* or compatible with 
each other - exactly what strong handles break.
Sep 19 2013
prev sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-09-11, 20:29, Andrej Mitrovic wrote:

 On 9/11/13, Kagamin <spam here.lot> wrote:
 I'd say, strong handles shouldn't act as pointers (and shouldn't
 contain pointers), so null shouldn't work.
"NULL" is already used in a ton of WinAPI C/C++ code and MSDN documentation, it would be a major pain in the ass to have to use e.g. HWND(0) instead.
How's about enum NULL = HANDLE(0); Honestly I hate that, and am convinced the correct way to fix this is for D to add opImplicitCastFrom. -- Simen
Sep 11 2013
parent reply "Kagamin" <spam here.lot> writes:
On Wednesday, 11 September 2013 at 20:20:13 UTC, Simen Kjaeraas 
wrote:
 On 2013-09-11, 20:29, Andrej Mitrovic wrote:

 On 9/11/13, Kagamin <spam here.lot> wrote:
 I'd say, strong handles shouldn't act as pointers (and 
 shouldn't
 contain pointers), so null shouldn't work.
"NULL" is already used in a ton of WinAPI C/C++ code and MSDN documentation, it would be a major pain in the ass to have to use e.g. HWND(0) instead.
How's about enum NULL = HANDLE(0); Honestly I hate that, and am convinced the correct way to fix this is for D to add opImplicitCastFrom.
Or multiple alias this: struct _NULL { HANDLE zero; void* ptr; alias zero this; alias ptr this; }
Sep 19 2013
parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On 2013-09-19, 10:06, Kagamin wrote:

 On Wednesday, 11 September 2013 at 20:20:13 UTC, Simen Kjaeraas wrote:
 On 2013-09-11, 20:29, Andrej Mitrovic wrote:

 On 9/11/13, Kagamin <spam here.lot> wrote:
 I'd say, strong handles shouldn't act as pointers (and shouldn't
 contain pointers), so null shouldn't work.
"NULL" is already used in a ton of WinAPI C/C++ code and MSDN documentation, it would be a major pain in the ass to have to use e.g. HWND(0) instead.
How's about enum NULL = HANDLE(0); Honestly I hate that, and am convinced the correct way to fix this is for D to add opImplicitCastFrom.
Or multiple alias this: struct _NULL { HANDLE zero; void* ptr; alias zero this; alias ptr this; }
If you want opaque handles, that does not solve the problem. What would solve the problem is something like this: struct DECLARE_HANDLE(int line = __LINE__, string file = __FILE__T) { private void* payload; private this(void* value) { payload = value; } ref DECLARE_HANDLE opAssign(typeof(null) value) { payload = null; return this; } DECLARE_HANDLE opImplicitCastFrom(typeof(null) value) { return DECLARE_HANDLE(null); } } alias DECLARE_HANDLE!(__LINE__, __FILE__) HANDLE; // BUG11074 alias DECLARE_HANDLE!(__LINE__, __FILE__) HWND; // BUG11074 void test() { void* voidPointer; HANDLE handle; HWND window; SomeWindowsFunctionTakingAHandle(null); // No problem! SomeWindowsFunctionTakingAHandle(handle); // No problem! SomeWindowsFunctionTakingAHandle(voidPointer); // Does not compile. SomeWindowsFunctionTakingAHandle(window); // Does not compile. } -- Simen
Sep 20 2013