GameMaker Putting a n-bytes C++ value in a buffer for GM to read

vdweller

Member
Hi all!

I am working on a C++ dll's ability to return a char array.

My first question is: The template specifies a return type of char pointer . Is there a problem if the function returns an unsigned char pointer? I did some tests and it seems like it has no problem, but maybe it won't be OK for other machines, or am I missing some other important detail?

Now, let's assume I want the C++ dll to return an array of 64-bit integer elements. Let's not dispute the "why" for a moment.

One way I tested and found (so far) working is, in C++, breaking down the 64-bit value into 8 8-bit (char) chunks and putting each one into the char array in sequence. When done, the "string" is returned to GM.

Then, in GameMaker, by performing a buffer_read(buffer,buffer_u64), the value seems to be OK.

My second question is: Is this way OK/safe? Is there a better way to do this? Are there any details I should keep in mind (endianness? something else?)
 

Mert

Member
Hi all!

I am working on a C++ dll's ability to return a char array.

My first question is: The template specifies a return type of char pointer . Is there a problem if the function returns an unsigned char pointer? I did some tests and it seems like it has no problem, but maybe it won't be OK for other machines, or am I missing some other important detail?

Now, let's assume I want the C++ dll to return an array of 64-bit integer elements. Let's not dispute the "why" for a moment.

One way I tested and found (so far) working is, in C++, breaking down the 64-bit value into 8 8-bit (char) chunks and putting each one into the char array in sequence. When done, the "string" is returned to GM.

Then, in GameMaker, by performing a buffer_read(buffer,buffer_u64), the value seems to be OK.

My second question is: Is this way OK/safe? Is there a better way to do this? Are there any details I should keep in mind (endianness? something else?)
Game Maker's extension & external-code-interference politics have changed in the last 5 years, but one thing has never been changed for 20 years : Being limited to double & string. We are still limited to double & string data types since everything in Game Maker are either double or string.

At least, we should be able to pass pointers.
 

chamaeleon

Member
Just looking at the template code example, I'd worry about leaking memory from strdup() or the potential for crashes if GMS tries to free the memory, as a general thing. The DLL may have its own memory heap that it manages, and the pointer may not make sense for the GMS memory management to free. Hence the traditional way of providing deallocation functions in DLLs for every type of pointer returned that has been allocated in the DLL.
 

GMWolf

aka fel666
If you want to return anything else than a single number, I wod recommend you ways use a buffer.
Create the buffer, use buffer_get_address to get the pointer. Pass it to dll. Have dll fill it out.
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Is this way OK/safe?
If your sequence contains a zero-byte, that ends the string.
Is there a better way to do this?
You are supposed to use buffer_get_address to get a point which you can then pass to DLL and fill up with actual data.
Just looking at the template code example, I'd worry about leaking memory from strdup() or the potential for crashes if GMS tries to free the memory, as a general thing.
GMS makes a copy of the string returned from DLL - in most of my extensions you can see the extension reusing the same string buffer over and over for new returned strings.
 

chamaeleon

Member
GMS makes a copy of the string returned from DLL - in most of my extensions you can see the extension reusing the same string buffer over and over for new returned strings.
The problem in the template code is that there is no code/function on the DLL side that safely frees the memory allocated by strdup().
Code:
char s[1024];
snprintf(&s[0], 1023, "Total = %d", total);
char* pString = strdup(s);
return pString;
The executable cannot assume that its free() call will utilize the same heap as the DLL so therefore it cannot safely do it internally in the game executable. So assuming that it does not do that, calling that particular function over and over would leak more and more memory over time. A MyExtension_free() function should be implemented in the DLL and called on the GMS side once the use of the returned pointer is finished. Unless I'm missing some critical aspect of how GMS interacts with extension DLLs (I'm not an expert on this!).
 

vdweller

Member
If your sequence contains a zero-byte, that ends the string.

You are supposed to use buffer_get_address to get a point which you can then pass to DLL and fill up with actual data.

GMS makes a copy of the string returned from DLL - in most of my extensions you can see the extension reusing the same string buffer over and over for new returned strings.
There seems to be a misunderstanding, probably from lack of details on my part. I am already using buffer_get_address to pass a buffer to C++ .

My questions were about whether it is okay for C++ to return an unsigned char* type instead of just a char* , and also whether breaking a long int into 1-byte chunks, inserting them in the buffer sequentially and then returning the buffer to GM, then using buffer_read(buffer,buffer_u64) to read back the long int is good practice or if there's a better way.

EDIT: @chamaeleon
cdecl :expects the caller (GM) to clean up
stdcall: expects the callee (DLL) to clean up
Also, you don't have to use strdup, you can just rip it away. But let's not derail this thread too much ;)
 
Last edited:

chamaeleon

Member
There seems to be a misunderstanding, probably from lack of details on my part. I am already using buffer_get_address to pass a buffer to C++ .

My questions were about whether it is okay for C++ to return an unsigned char* type instead of just a char* , and also whether breaking a long int into 1-byte chunks, inserting them in the buffer sequentially and then returning the buffer to GM, then using buffer_read(buffer,buffer_u64) to read back the long int is good practice or if there's a better way.
Unsigned or not makes no difference. It is just 8 bits with the same bits set, only difference is how functions or operators work on the char data. Instead of breaking up the int, just memcpy() sizeof(long int) bytes from &my_long_int on the C/C++ side into the buffer (maybe that's what you're doing anyway, and I misunderstand what you mean with 1 byte chunks). I don't think there's anything inherently wrong or dangerous in what you're doing. Sounds like you're using the existing facilities as intended.
 

vdweller

Member
Unsigned or not makes no difference. It is just 8 bits with the same bits set, only difference is how functions or operators work on the char data. Instead of breaking up the int, just memcpy() sizeof(long int) bytes from &my_long_int on the C/C++ side into the buffer (maybe that's what you're doing anyway, and I misunderstand what you mean with 1 byte chunks). I don't think there's anything inherently wrong or dangerous in what you're doing. Sounds like you're using the existing facilities as intended.
Yes, memcpy is what I had in mind, I just used the "break down" example for clarity! Glad you are confirming the unsigned char* thing!

A bonus question:

You can send a buffer to C++, do stuff to it and get it back.

But...can you just send a char array created from C++ (a "string", as far as GML is concerned) and treat it like a buffer in GM? I did a few experiments but it didn't seem to work. Can you shed some light on that please? Maybe a short code example on the C++ side of things?
 
Last edited by a moderator:
L

Lonewolff

Guest
A bonus question:

You can send a buffer to C++, do stuff to it and get it back.

But...can you just send a char array created from C++ (a "string", as far as GML is concerned) and treat it like a buffer in GM? I did a few experiments but it didn't seem to work. Can you shed some light on that please? Maybe a short code example on the C++ side of things?
You can, but you have to create the buffer first at the correct size, send that pointer to C++, and fill the data at the address of the buffer.

I put in a request a week ago or so to allow the buffer to be created C++ side, essentially telling the buffer that it is at a given address and the buffer is xx bytes long.

Only time will tell if anything comes of it.


Game Maker's extension & external-code-interference politics have changed in the last 5 years, but one thing has never been changed for 20 years : Being limited to double & string. We are still limited to double & string data types since everything in Game Maker are either double or string.

At least, we should be able to pass pointers.
You can.

The string data type is in-fact a pointer. It actually isn't a string at all, as the manual would have you believe.

Hi all!

I am working on a C++ dll's ability to return a char array.

My first question is: The template specifies a return type of char pointer . Is there a problem if the function returns an unsigned char pointer? I did some tests and it seems like it has no problem, but maybe it won't be OK for other machines, or am I missing some other important detail?
Nope all good. A pointer is a pointer, it doesn't have a sign.


Now, let's assume I want the C++ dll to return an array of 64-bit integer elements. Let's not dispute the "why" for a moment.

One way I tested and found (so far) working is, in C++, breaking down the 64-bit value into 8 8-bit (char) chunks and putting each one into the char array in sequence. When done, the "string" is returned to GM.

Then, in GameMaker, by performing a buffer_read(buffer,buffer_u64), the value seems to be OK.

My second question is: Is this way OK/safe? Is there a better way to do this? Are there any details I should keep in mind (endianness? something else?)
As long as you don't unintentionally go out of bounds of the buffer you are fine. Endianness isn't isn't an issue as all x86 / x64 based windows machines are the same. As you are using a DLL, it is safe to assume that Windows is your target.
 
Last edited by a moderator:

Mert

Member
You can, but you have to create the buffer first at the correct size, send that pointer to C++, and fill the data at the address of the buffer.

I put in a request a week ago or so to allow the buffer to be created C++ side, essentially telling the buffer that it is at a given address and the buffer is xx bytes long.

Only time will tell if anything comes of it.




You can.

The string data type is in-fact a pointer. It actually isn't a string at all, as the manual would have you believe.



Nope all good. A pointer is a pointer, it doesn't have a sign.




As long as you don't unintentionally go out of bounds of the buffer you are fine. Endianness isn't isn't an issue as all x86 / x64 based windows machines are the same. As you are using a DLL, it is safe to assume that Windows is your target.
I was sleepy a bit, I meant pointer references :)
(Or is there a way to do that?)
 

vdweller

Member
I was sleepy a bit, I meant pointer references :)
(Or is there a way to do that?)
memcpy a pointer into a double (safe even for 64 bits), return it to GM. Just don't do any arithmetics with it in GM ;)

Actually what I did was allocate memory with the new keyword:
Code:
unsigned char *ptr= new unsigned char[4];
What I am trying to do is to pass a string to GM and fool GM into thinking it's a buffer. I don't send any buffer pointer to C++, I just create and return a string in C++. Example:
Code:
unsigned char *ptr= new unsigned char[4];
ptr[0]=0b00000001;
ptr[1]=0b00000001;
ptr[2]=0b00000001;
ptr[3]=0b00000001;
return ptr;
Trying to treat the returned string as a buffer doesn't seem to work.
However, if I do this in GM:
Code:
var a = external_call(my_function);
var buf=buffer_create(1024,buffer_fixed,1);
buffer_seek(buf, buffer_seek_start, 0);
buffer_write(buf,buffer_string,a);
buffer_seek(buf, buffer_seek_start, 0);
print(buffer_read(buf,buffer_u64));
It prints 16843009, which is indeed the expected value of 00000001000000010000000100000001 .
But! If any array element happens to be zero, this counts as a zero termination and the string gets botched. Example:
Code:
unsigned char *ptr= new unsigned char[4];
ptr[0]=0b00000001;
ptr[1]=0b00000000; //zero
ptr[2]=0b00000001;
ptr[3]=0b00000001;
This should resolve to the number 00000001000000000000000100000001 = 16777473
However GM prints 1!
Hm...if there was a way to circumvent zero char value, this could work.
 
Top