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

Discussion in 'Advanced Programming Discussion' started by vdweller, Jul 18, 2019.

  1. vdweller

    vdweller Member

    Joined:
    Jun 24, 2016
    Posts:
    135
    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?)
     
  2. Mert

    Mert Member

    Joined:
    Jul 20, 2016
    Posts:
    388
    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.
     
  3. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    975
    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.
     
  4. GMWolf

    GMWolf aka fel666

    Joined:
    Jun 21, 2016
    Posts:
    3,442
    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.
     
  5. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,405
    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.
     
  6. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    975
    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!).
     
  7. vdweller

    vdweller Member

    Joined:
    Jun 24, 2016
    Posts:
    135
    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: Jul 18, 2019
  8. chamaeleon

    chamaeleon Member

    Joined:
    Jun 21, 2016
    Posts:
    975
    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.
     
  9. vdweller

    vdweller Member

    Joined:
    Jun 24, 2016
    Posts:
    135
    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: Jul 19, 2019
  10. Lonewolff

    Lonewolff Member

    Joined:
    Jan 8, 2018
    Posts:
    1,207
    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.
     
    Last edited by a moderator: Jul 19, 2019
    Lord KJWilliams and PNelly like this.
  11. Mert

    Mert Member

    Joined:
    Jul 20, 2016
    Posts:
    388
    I was sleepy a bit, I meant pointer references :)
    (Or is there a way to do that?)
     
  12. vdweller

    vdweller Member

    Joined:
    Jun 24, 2016
    Posts:
    135
    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.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice