SOLVED What data type are strings in Game Maker Studio 2? | Convert const char* to const wchar_t*

EvanSki

Raccoon Jam Host
Yes yes, string is string. But let me explain.

I want to pass a string from Game Maker to a .dll

The .dll wants a LPCWSTR ( Long Pointer to Constant Wide String ) type string

just passing gm strings to the dll gives me this
Untitled.png

Now "This is text" is not, what ever that is.

Now i can do string conversion but I need to know what type of string the GM strings are.
Or if someone else has a better way of passing strings into a .dll, im all ears


Thanks.
 
Last edited:

Samuel Venable

Time Killer
const char * is used for DLL's but I've noticed GameMaker uses std::string internally because if you return a nullptr you'll get a segfault. Anyways use a const char *. The type you mention is a typedef of a const wchar_t *. In the game maker /game dev memes topic I gave you a helper function which convert between these two types.


If you need help using them I can give a better explaination.
 
Last edited:

chamaeleon

Member
Besides what @Samuel Venable said, it should be char* and encoded as utf8. No simple cast will make a char* into wide char pointer correctly. It will require function calls (which ones escape me at the moment, it has been a decade or more since I last did something along those lines) that translates the string from one type to the other.

Edit : I'm apparently tired. The source code from Samuel does the required translation, I guess. 🙂
 

Samuel Venable

Time Killer
iirc GameMaker 8.1 and below don't even recognize const char * correctly and will only take char * as @chamaeleon mentioned, so for backwards compatibility it's always nice to cast out the const to a regular char *. But not many people besides me care to do that these days.

Regardless he makes a good point that whether you use const char * or char * either is fine, but in the case of char * a lot of the time you will need to free memory after using the string which should be done on the c++ side after making a copy of the string when using it as a return value.

You aren't using a string as a return value for your function it seems, but it's also worth noting windows, UWP, and other microsoft based platforms such as the Xbox i believe are the only ones with this issue. MacOS, Linux, mobile, and other consoles will accept char * or const char * with full UTF-8 support.

I also believe I do not need an NDA to share that about console because it did eventually come out as public knowledge Sony and Nintendo products are based on FreeBSD which does support UTF-8 with char * and const char * (being a FreeBSD developer I know this, not as a console developer).

Also when returning a string, you must make it static like so:

C++:
const char *function_name() {
  const char *hi_fam = ...
  static std::string hey_boi; // do not initialize when declaring as static
  hey_boi = hi_fam; // now you may initialize here instead
  return hey_boi.c_str();
}
Otherwise your string might not equal the correct value when returned back to gamemaker because it is not guaranteed to last the lifetime of the function all the way to it's return value.

If you do:

C++:
const char *function_name() {
  const char *hi_fam = ...
  static std::string hey_boi = hi_fam; // big no-no
  return hey_boi.c_str();
}
then whatever string hey_boi is set to will remain the same after the first time the function is called for the lifetime of the program, meaning after the first call, the function will return the same thing for the rest of the time the instance of the game is running. Obviously you probably don't want that.

Lastly, if you don't want to use static inside a function body you may use a variable outside of a function body instead:

C++:
std::string hey_boi; // you may also use static here if you only want this variable accessible in the source it is used in

const char *function_name() {
  const char *hi_fam = ...
  hey_boi = hi_fam;
  return hey_boi.c_str();
}
Here I leave a comment noting outside of a function body the static keyword has a different use completely. This code is also fine but whether you use a variable outside of a function body or inside with static keyword either way it will not be thread safe.
 
Last edited:

EvanSki

Raccoon Jam Host
In the game maker /game dev memes topic I gave you a helper function which convert between these two types.
no suitable constructor exists to convert from "const char *" to std:basic_string<wchar_t, std::char_trairs,wchar_t>, std::allocator<wchar_t>>"

C++:
fn_export double show_alert(const char* message, const char* title) {
    
    std::wstring wstr = message; <--- error here
    wstr.c_str();
    wstring widen(string str) {
        size_t wchar_count = str.size() + 1;
        vector<wchar_t> buf(wchar_count);
        return wstring{ buf.data(),
          (size_t)MultiByteToWideChar(
          CP_UTF8, 0, str.c_str(), -1,
          buf.data(), (int)wchar_count) };
    }


    //LPCWSTR
    int _M = MessageBox(NULL, message, title, MB_ICONERROR | MB_OK);
    return _M;
    //Will return IDOK(1) so check for one before continueing after popup
}
 

chamaeleon

Member
You need to use the widen() function, I would think, assuming it is working correctly. I have not given this rewrite a try at all. Use at your own risk.
C++:
    wstring widen(string str) {
        size_t wchar_count = str.size() + 1;
        vector<wchar_t> buf(wchar_count);
        return wstring{ buf.data(),
          (size_t)MultiByteToWideChar(
          CP_UTF8, 0, str.c_str(), -1,
          buf.data(), (int)wchar_count) };
    }

fn_export double show_alert(const char* message, const char* title) {
    std::wstring wstr = widen(message);
    //LPCWSTR
    int _M = MessageBox(NULL, message, title, MB_ICONERROR | MB_OK);
    return _M;
    //Will return IDOK(1) so check for one before continueing after popup
}
 

EvanSki

Raccoon Jam Host
You need to use the widen() function, I would think, assuming it is working correctly. I have not given this rewrite a try at all. Use at your own risk.
C++:
    wstring widen(string str) {
        size_t wchar_count = str.size() + 1;
        vector<wchar_t> buf(wchar_count);
        return wstring{ buf.data(),
          (size_t)MultiByteToWideChar(
          CP_UTF8, 0, str.c_str(), -1,
          buf.data(), (int)wchar_count) };
    }

fn_export double show_alert(const char* message, const char* title) {
    std::wstring wstr = widen(message);
    //LPCWSTR
    int _M = MessageBox(NULL, message, title, MB_ICONERROR | MB_OK);
    return _M;
    //Will return IDOK(1) so check for one before continueing after popup
}
that works but the issue then becomes that
"wstring is undefined" at the first part at the top
 

EvanSki

Raccoon Jam Host
Then add std:: in front of all cases of wstring for the function. I assume @Samuel Venable used using std::wstring or something to avoid having to specify the prefix.
so i converted it to this
C++:
std::wstring widen(std::string str) {
    size_t wchar_count = str.size() + 1;
    std::vector<wchar_t> buf(wchar_count);
    return std::wstring{ buf.data(),
      (size_t)MultiByteToWideChar(
      CP_UTF8, 0, str.c_str(), -1,
      buf.data(), (int)wchar_count) };
}
so then calling
C++:
std::wstring wstr = widen(message);
So now the question is, how do i get the converted string? like what variable is the widened string? as just using wstr doesnt work
 

chamaeleon

Member
So now the question is, how do i get the converted string? like what variable is the widened string? as just using wstr doesnt work
Could try
C++:
std::wstring wstr = widen(message);
int _M = MessageBox(NULL, wstr.c_str(), title, MB_ICONERROR | MB_OK);
 

EvanSki

Raccoon Jam Host
Could try
C++:
std::wstring wstr = widen(message);
int _M = MessageBox(NULL, wstr.c_str(), title, MB_ICONERROR | MB_OK);
That fixed all the errors
C++:
//convert const char* to const wchar_t*

std::wstring wstr = widen(message);
const wchar_t* W_message = wstr.c_str();

std::wstring wstr2 = widen(title);
const wchar_t* W_title = wstr2.c_str();
lets try it
Untitled.png
Neat!

C++:
std::wstring widen(std::string str) {
    size_t wchar_count = str.size() + 1;
    std::vector<wchar_t> buf(wchar_count);
    return std::wstring{ buf.data(),
      (size_t)MultiByteToWideChar(
      CP_UTF8, 0, str.c_str(), -1,
      buf.data(), (int)wchar_count) };
}

//Function to display a fakepop up
fn_export double show_alert(const char* message, const char* title) {
    
    std::wstring wstr = widen(message);
    const wchar_t* W_message = wstr.c_str();
    
    std::wstring wstr2 = widen(title);
    const wchar_t* W_title = wstr2.c_str();
    
    //LPCWSTR
    int _M = MessageBox(NULL, W_message, W_title, MB_ICONERROR | MB_OK);
    return _M;
    //Will return IDOK(1) so check for one before continueing after popup
}
 

EvanSki

Raccoon Jam Host
Tried it with a text function to see if maybe the function was screwing it up

C++:
fn_export const wchar_t* test_string(const char* text) {
    std::wstring wstr = widen(text);
    const wchar_t* W_text = wstr.c_str();
   
    return(W_text);
}
Untitled.png

I think something is wrong with the conversion
 

chamaeleon

Member
MessageBox() is being compiled in such a way it uses MessageBoxW() implictly, and you don't need to specify that explicitly?
 

EvanSki

Raccoon Jam Host
MessageBox() is being compiled in such a way it uses MessageBoxW() implictly, and you don't need to specify that explicitly?
no because it is a function built into windows.h

Edit also changing to include the W does not change the output
 

chamaeleon

Member
Does the following work, without any of the other stuff?
C++:
wchar_t title[] = L"My Title";
wchar_t message[] = L"My Message";
MessageBox(NULL, title, message, MB_OK);
Edit: I would assume it does, as I just checked your source code page, and it looks like you have a function that does the equivalent more or less.
 

chamaeleon

Member
Just spotted something that seems wrong to me from the original widen() function definition, but maybe I have forgotten something about C++...
C++:
return wstring { ... };
Seems like that should be using parentheses instead of braces... Or am I confused?
C++:
return wstring(...);
 

EvanSki

Raccoon Jam Host
Does the following work, without any of the other stuff?
C++:
wchar_t title[] = L"My Title";
wchar_t message[] = L"My Message";
MessageBox(NULL, title, message, MB_OK);
Edit: I would assume it does, as I just checked your source code page, and it looks like you have a function that does the equivalent more or less.
Yes this works exactly as if I call the function in the dll only:
C++:
//Function to display a fakepop up
fn_export double show_alert() {
    LPCWSTR W_message = L"This is the message!"
    LPCWSTR W_title = L"This is the title!";
    
    int _M = MessageBoxW(NULL, W_message, W_title, MB_ICONERROR | MB_OK);
    return _M;
}
But i wish for the title and message able to be set in game maker then have the dll use them for the message box function
 

EvanSki

Raccoon Jam Host
Just spotted something that seems wrong to me from the original widen() function definition, but maybe I have forgotten something about C++...
C++:
return wstring { ... };
Seems like that should be using parentheses instead of braces... Or am I confused?
C++:
return wstring(...);
Chaning the { } to ( ) does no change to the output
 

chamaeleon

Member
There's a newfangled thing called wstring_convert() that was not around (I think) when I last played with this kind of conversion. I'm seeing usage like this
C++:
#include <locale>
#include <codecvt>
...
std::wstring s2ws(const std::string& str)
{
    using convert_typeX = std::codecvt_utf8<wchar_t>;
    std::wstring_convert<convert_typeX, wchar_t> converterX;

    return converterX.from_bytes(str);
}
 

chamaeleon

Member
I have created a small dll test that worked for me
Message.h
C++:
#pragma once

#ifdef DLLEXPORT
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif

    DLLAPI double show_message(const char* title, const char* message);

#ifdef __cplusplus
}
#endif
Message.cpp
C++:
#include <windows.h>
#include <cstring>
#include <cstdlib>
#include <locale>
#include <codecvt>
#include "Message.h"

std::wstring string2wstring(const std::string& str)
{
    using convert_typeX = std::codecvt_utf8<wchar_t>;
    std::wstring_convert<convert_typeX, wchar_t> converterX;

    return converterX.from_bytes(str);
}

double show_message(const char* title, const char* message)
{
    std::wstring t = string2wstring(title);
    std::wstring m = string2wstring(message);
    return (double)(MessageBox(NULL, t.c_str(), m.c_str(), MB_OK));
}
Defined an extension function called cpp_show_message to the show_message function in the dll
Some Create event
GML:
cpp_show_message("GMS Title", "GMS Message");
Displays the message correctly for me.
 

chamaeleon

Member
I'm happy to see you got the same result. :)

Edit: If you need conversion in the other direction, the stackoverflow link I gave indicates it is just a small change, calling a different member function.
C++:
std::string wstring2string(const std::wstring& wstr)
{
    using convert_typeX = std::codecvt_utf8<wchar_t>;
    std::wstring_convert<convert_typeX, wchar_t> converterX;

    return converterX.to_bytes(wstr);
}
 
Last edited:

Samuel Venable

Time Killer
I would like to point out that function I wrote does work and I use it in all of my extensions to boot.

You weren't using it right, I wish I was online when all this happened. Also: codecvt_utf8 is deprecated in the C++ standard and i believe might even be removed by now if you compile using C++20. I would strongly recommend doing this as it won't go stale likely within our lifetime:

C++:
std::wstring widen(std::string str) {
    size_t wchar_count = str.size() + 1;
    std::vector<wchar_t> buf(wchar_count);
    return std::wstring{ buf.data(),
      (size_t)MultiByteToWideChar(
      CP_UTF8, 0, str.c_str(), -1,
      buf.data(), (int)wchar_count) };
}

fn_export double show_alert(const char* message, const char* title) {
    std::wstring wstr = widen(message);
    std::wstring wstr2 = widen(title);
    int _M = MessageBoxW(NULL, wstr.c_str(), wstr2.c_str(), MB_ICONERROR | MB_OK);
    return _M;

}
 

chamaeleon

Member
I would like to point out that function I wrote does work and I use it in all of my extensions to boot.
I can confirm it works in my test dll. :) I wish I had had the test dll before I started looking for other ways to implement, then I would have been able to just drop it in.

My response in post #6 was to get the usage right, but when it did not yield the desired result apparently I looked for some other piece of code.

And rereading it I now obviously see the missing use of converted strings. I was only concerned with the conversion function being declared and used, and did not look at the message box call at all.

As a side-note, I do hope the standards committee comes up with something to suitably replace std::codecvt if it is going to be deprecated (some brief searching indicates this is the case). Removing it will mean going back to #ifdef wrapping the conversion based on the compiler environment, using MultiByteToWideChar() on Windows, mbtowc() on Linux, etc., or making use of some 3rd party library that hides the details. If it didn't exist in the first place it would be fine, but since it does exist, eventually removing it without a cross-platform replacement seems off to me.
 
Last edited:
Top