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