www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - getting Win32 Messagebox to work

reply Mark Moorhen <Mark Moorhen.com> writes:
Hi,

I've recently started looking into D, and I'm afraid i'm gonna 
need some support to get me going.

I work mostly on Windows, so i've downloaded the examples from 
"https://github.com/AndrejMitrovic/DWinProgramming". Only these 
examples do not work out-of-the-box on my machine (Win10, 64-bit 
/ DMD32 D Compiler v2.082.0)

Now I'm trying to pass the content for a Messagebox as a variable 
like this:
<code>
import core.runtime;
import win32.windef;
import win32.winuser;

extern (Windows)
void WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR 
lpCmdLine, int iCmdShow)
{
		string content = "Some random content.";
         MessageBox(NULL, content , "Window title", 0);
}
</code>

When compiling I get the error:

HelloMsg.d(9): Error: function `win32.winuser.MessageBoxA(void*, 
const(char)*, const(char)*, uint)` is not callable using argument 
types `(typeof(null), string, string, int)`
HelloMsg.d(9):        cannot pass argument `content` of type 
`string` to parameter `const(char)*`

Can anyone help me out with this?
Oct 26 2018
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 27/10/2018 1:36 AM, Mark Moorhen wrote:
 Hi,
 
 I've recently started looking into D, and I'm afraid i'm gonna need some 
 support to get me going.
 
 I work mostly on Windows, so i've downloaded the examples from 
 "https://github.com/AndrejMitrovic/DWinProgramming". Only these examples 
 do not work out-of-the-box on my machine (Win10, 64-bit / DMD32 D 
 Compiler v2.082.0)
 
 Now I'm trying to pass the content for a Messagebox as a variable like 
 this:
 <code>
 import core.runtime;
 import win32.windef;
 import win32.winuser;
 
 extern (Windows)
 void WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR 
 lpCmdLine, int iCmdShow)
 {
          string content = "Some random content.";
          MessageBox(NULL, content , "Window title", 0);
 }
 </code>
 
 When compiling I get the error:
 
 HelloMsg.d(9): Error: function `win32.winuser.MessageBoxA(void*, 
 const(char)*, const(char)*, uint)` is not callable using argument types 
 `(typeof(null), string, string, int)`
 HelloMsg.d(9):        cannot pass argument `content` of type `string`
to 
 parameter `const(char)*`
 
 Can anyone help me out with this?
alias string = immutable(char)[]; A slice (string in this case) is a length + pointer pair. You need to add .ptr on content with a cast to get to const(char)*.
Oct 26 2018
parent reply Mark Moorhen <Mark Moorhen.com> writes:
On Friday, 26 October 2018 at 12:39:13 UTC, rikki cattermole 
wrote:
 On 27/10/2018 1:36 AM, Mark Moorhen wrote:
 [...]
alias string = immutable(char)[]; A slice (string in this case) is a length + pointer pair. You need to add .ptr on content with a cast to get to const(char)*.
Thanks for your quick reply. However when I do: cast(const(char)*) content.ptr; I get a error: HelloMsg.d(9): Error: `cast(const(char)*)cast(immutable(char)*)content` has no effect
Oct 26 2018
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 27/10/2018 1:46 AM, Mark Moorhen wrote:
 On Friday, 26 October 2018 at 12:39:13 UTC, rikki cattermole wrote:
 On 27/10/2018 1:36 AM, Mark Moorhen wrote:
 [...]
alias string = immutable(char)[]; A slice (string in this case) is a length + pointer pair. You need to add .ptr on content with a cast to get to const(char)*.
Thanks for your quick reply. However when I do: cast(const(char)*) content.ptr; I get a error: HelloMsg.d(9): Error: `cast(const(char)*)cast(immutable(char)*)content` has no effect
This should work: string content = "Some random content."; MessageBoxA(null, cast(const(char)*)content.ptr, cast(const(char)*)"Window title".ptr, 0);
Oct 26 2018
next sibling parent Mark Moorhen <Mark Moorhen.com> writes:
On Friday, 26 October 2018 at 12:56:21 UTC, rikki cattermole 
wrote:
 On 27/10/2018 1:46 AM, Mark Moorhen wrote:
 On Friday, 26 October 2018 at 12:39:13 UTC, rikki cattermole 
 wrote:
 On 27/10/2018 1:36 AM, Mark Moorhen wrote:
 [...]
alias string = immutable(char)[]; A slice (string in this case) is a length + pointer pair. You need to add .ptr on content with a cast to get to const(char)*.
Thanks for your quick reply. However when I do: cast(const(char)*) content.ptr; I get a error: HelloMsg.d(9): Error: `cast(const(char)*)cast(immutable(char)*)content` has no effect
This should work: string content = "Some random content."; MessageBoxA(null, cast(const(char)*)content.ptr, cast(const(char)*)"Window title".ptr, 0);
Thanks again. It does work indeed. Now I'll have to try and get my head around it. But that will work out. eventually...
Oct 26 2018
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 26 October 2018 at 12:56:21 UTC, rikki cattermole 
wrote:
 MessageBoxA(null, cast(const(char)*)content.ptr, 
 cast(const(char)*)"Window title".ptr, 0);
Get rid of those casts, they are unnecessary.
Oct 26 2018
parent reply Mark Moorhen <Mark Moorhen.com> writes:
On Friday, 26 October 2018 at 13:04:21 UTC, Adam D. Ruppe wrote:
 On Friday, 26 October 2018 at 12:56:21 UTC, rikki cattermole 
 wrote:
 MessageBoxA(null, cast(const(char)*)content.ptr, 
 cast(const(char)*)"Window title".ptr, 0);
Get rid of those casts, they are unnecessary.
I got to this: string content = "Some random content."; const(char)* content2 = content.ptr; MessageBox(NULL, content2 , "Window title", 0); But how would you do this without casts?
Oct 26 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 26 October 2018 at 13:13:07 UTC, Mark Moorhen wrote:
 But how would you do this without casts?
If that doesn't compile without casts, you don't want it to compile - casting is often a mistake; the compiler is trying to tell you something. Let me guess, it is saying something like cannot implicitly cast X of type const(char)* to const(wchar)* ? That difference between char and wchar is very important, and casting it away will not run correctly. Whereas just immutable -> const is an implicit cast and the compiler will do it for you since it will not generate wrong code. But this is why rikki posted MessageBoxA and your code has just MessageBox - it is a way around it (not the best way, but a working way). I'm typing a longer post right now explaining this in more detail.
Oct 26 2018
parent Mark Moorhen <Mark Moorhen.com> writes:
On Friday, 26 October 2018 at 13:20:17 UTC, Adam D. Ruppe wrote:
 If that doesn't compile without casts, you don't want it to 
 compile - casting is often a mistake; the compiler is trying to 
 tell you something.
Now you've mentioned, I also tried: { const(char)* content = "Some random content."; MessageBox(NULL, content , "Window title", 0); } And it compiles like a charm.
Oct 26 2018
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
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! :)
Oct 26 2018
parent reply Mark Moorhen <Mark Moorhen.com> writes:
On Friday, 26 October 2018 at 13:59:17 UTC, Adam D. Ruppe wrote:
 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:
....
 Welcome to the wonderful world of wide strings and D Windows 
 programming! :)
Wow, that's a lot of info, but it makes things a lot clearer. I ended up with this: import core.runtime; import std.utf; import core.sys.windows.windows; extern (Windows) void WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) { const(wchar*) foo = toUTF16z("hello"w[0 .. 2]); MessageBoxW(null, foo, "hi", 0); } It does compile, but does not run as expected. Any clues?
Oct 26 2018
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 26 October 2018 at 15:10:14 UTC, Mark Moorhen wrote:
 It does compile, but does not run as expected. Any clues?
You imported, but never actually called Runtime.initalize(). Just stick that at the top, first thing you do inside WinMain, and it will work. (or use main instead of WinMain!)
Oct 26 2018