getting Win32 Messagebox to work

Adam D. Ruppe destructionator at gmail.com
Fri Oct 26 13:59:17 UTC 2018


On Friday, 26 October 2018 at 12:36:42 UTC, Mark Moorhen wrote:
> Can anyone help me out with this?

Yeah, let me make a few general points here:

* The win32.xxx packages shouldn't be necessary any longer, 
because they have been merged into the druntime core since those 
tutorials were written, so instead of `win32.xxx` try 
`core.sys.windows.xxx`. It should mostly work better, though you 
might see some differences about stuff like nothrow compared to 
the examples, but solving those are as simple as tossing 
`nothrow` on your function too. (It will also have bug fixes, 
especially relating to 64 bit builds!)

Regardless, generally, tossing `import core.sys.windows.windows;` 
at the top of your file should give you most access to the win32 
api.

* The Windows API, for any function that works with strings, will 
have three names: FunctionA, FunctionW, and just Function. 
MessageBox is one of these. The difference is if they take Ascii 
or Wide strings.

The Ascii versions are inferior to the Wide versions because they 
do not support non-ascii unicode characters and may get other 
legacy/limiting behavior (for example, CreateFileA has a filename 
size limit that CreateFileW does not have), so the Wide ones 
should be preferred. The naked name is an alias for the W 
version, unless you build with -version=ANSI (or are using a 
really old library - like the tutorial copy might be, it is 6 
years old lol).

* D's string and "hello" literals are UTF-8... which kinda looks 
like Ascii, but isn't. It will compile to pass them to the A 
version of the functions, and even work as long as you don't have 
any multi-byte characters in there. So fine for hello world, but 
asking for trouble later.

Instead, use D's wstring and wchar types and associated "hello"w 
strings. That w after the quotes means it is a wide string 
literal.

const(wchar)* msg = "hello, world!".ptr; // but beware of this 
later
MessageBoxW(null, msg, "Hi there"w, 0); // note the w there

The compiler will take care of wide string conversions for you 
here.

* I said beware because the .ptr trick only works if the string 
is zero terminated. The Windows API generally works with C-style, 
zero-terminated strings (unless the documentation for that 
specific function says differently).

D's string *literals* are zero terminated, and the compiler knows 
it. That's why it allows you to pass "a string literal"w to the 
function without any casts, .ptr calls, or errors. So that will 
always work.

But D's string *types* are not necessarily zero terminated, which 
is why passing:

wstring foo;
MessageBoxW(null, foo, "hi"w, 0);

will generate an error: the compiler doesn't know if foo is 
properly formed. You can shut it up with .ptr... but behold the 
following:

wstring foo = "hello"w[0 .. 2];
MessageBoxW(null, foo.ptr, "hi"w, 0);


The [x .. y] operator in D will slice the string or array, 
returning a reference to just the elements between the two 
indexes (including the first index, excluding the last). In this 
case, it'd return the first two elements (btw a string element is 
not necessarily the same as a character - some characters span 
multiple elements, even on wide strings. Try an emoji in there, 
for example. But for English letters, this shortcut works, and I 
don't want to go TOO deep in this little post.)

So, given the slice, you'd expect that MessageBox to say "he", 
right? Try it though... it will say "hello". Why? Because the 
zero terminator is still at the same place it was before! And 
using .ptr will bypass the D length part and just give the raw 
pointer, which keeps going until it hits that zero....

... or if there is no zero, such as if the string was generated 
by another function like the x ~ y concatenation operator, it 
will keep going and print gibberish, or until the program crashes.

* To solve this, append a zero to the end of the slice before 
passing it. D's standard library has a function to do this: 
toUTF16z.

import std.utf;
const(wchar*) foo = toUTF16z("hello"w[0 .. 2]); // now this foo 
is safe to pass to the Windows function, and will print "he".

Dox:
http://dpldocs.info/experimental-docs/std.utf.toUTF16z.html


That function will also convert other forms of D strings to 
wstring for you, if necessary.

Note that D's standard library also has a toStringz function that 
does this, but that only works with plain string - the A version 
in Windows talk. It is helpful for other C functions you might 
call, but not ideal for Windows, where toUTF16z shines.


Important: before calling the D standard library, you must 
initialize the D runtime. Which brings me to:

* I would suggest avoiding WinMain in D code. Instead, just write 
an ordinary main() function. If you need hInstance, you can call 
GetModuleHandle. If you need lpCmdLine, you can call 
GetCommandLine. hPrevInstance is useless in 32 and 64 bit builds, 
so you won't need it. nCmdShow is only used in one case: passing 
it to ShowWindow (and it can be ignored by the system there 
too!), and you can pass SW_SHOWDEFAULT instead, so it is 
unnecessary as well.

Using regular main instead of WinMain will let the druntime 
initialize itself for better interop with other D code and 
libraries. If you don't do that, using D features may crash the 
program! It is possible to call core.runtime.Runtime.initialize 
from inside WinMain too.... but really, it is easier to just 
write main() and get it for free.

If you want to start without a console with a main (WinMain does 
also instruct the linker not to give you a console), pass 
`-L/subsystem:windows` to your dmd build command in addition to 
the other settings.


* Lastly, if you are using some D function that only takes 
string, and you want to pass wstrings, you need a conversion. 
Before you do this though, try just using the wstring. You may 
find that it just works, because many D functions will work with 
wstring as well as plain string. (Now, there are advantages of 
D's string vs D's wstring in memory consumption and a few other 
places, though I'd point out each can do all the same stuff as 
the other, hold all the same text, etc. If not working with 
Windows, I recommend plain string. But since you are working with 
Windows, you can avoid a lot of conversions by just embracing 
wstring.)

You'll want conversion functions if necessary. Windows has one: 
MultiByteToWideChar 
https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar  which can go string to wstring (D's string is CP_UTF8 encoded btw)...

But the D function is a bit simpler. We already saw toUTF16z 
above. There is also toUTF16:

http://dpldocs.info/experimental-docs/std.utf.toUTF16.html

also in `import std.utf;`, and toUTF8

http://dpldocs.info/experimental-docs/std.utf.toUTF8.html

which go the two directions.

string s;
wstring w = toUTF8(s); // string to wstring with toUTF8
s = toUTF16(w); // the other way around with toUTF16


(See, the z in toUTF16z just means "with zero terminator at the 
end".)


* For efficiency, you can often create a string buffer on the 
stack with a `wchar[N]` and pass its pointer to the Windows 
functions.

wchar[16] myBuffer;
myBuffer[0] = 'H';
myBuffer[1] = 'i';
myBuffer[2] = 0; // always put zero terminator at the end

MessageBoxW(null, myBuffer.ptr, "msg"w, 0);

and that will work too. Just mind your lengths if you choose to 
do it this way.


* I consistently wrote MessageBoxW here instead of plain 
MessageBox, but if you are using the new bindings in 
core.sys.windows, both should work exactly the same way. 
Nevertheless, I like putting the W there to be explicit about 
what I want. If you don't and want your program to still compile 
either way, there are TCHAR aliases in the API ... but meh, not 
worth the hassle ever since Windows 98 came out lol.


* Last note: the MSDN documentation for Windows functions is the 
best source to learn more about their arguments and quirks. Any 
example from C or C++ can be easily translated to D, so don't 
worry about finding D-specific resources. (And if you do bump 
into something, feel free to ask us here.)


Welcome to the wonderful world of wide strings and D Windows 
programming! :)


More information about the Digitalmars-d-learn mailing list