• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Assembler, GMS 1.4 and DLL

S

sysop50

Guest
Hi there.

I'm trying to create a DLL using assembly (FASM) but I'm having some issues.

First I create a buffer in GMS, poke in some values (chars 49, 50 and 51 - or '1' '2' and '3'), and get its address (pointer) in mem that I send to the DLL function via a string argument.

In the DLL function I get that address, poke in some new values and return to GMS where I check to see if everything went ok.

Well, it didn't. It doesn't change the values.
I checked in the DLL function and everything seems to be fine - I get the string pointer (that point to the pointer address - so a pointer in a pointer thing :) ). I tested with printing out the values while being in the DLL and both when I first get the pointer(s) and after I change the values are correct. But not when I return to GMS.

Here's the code in GMS:
buffer_poke(b,0,buffer_u8,49) // 1
buffer_poke(b,1,buffer_u8,50) // 2
buffer_poke(b,2,buffer_u8,51) // 3
blow(adr)
print(buffer_peek(b,0,buffer_u8)) // print() is just a script for show_debug_message(string(value))
print(buffer_peek(b,1,buffer_u8))
print(buffer_peek(b,2,buffer_u8))

Here's the FASM code:
proc Blow buf
mov BYTE [buf],71
inc [buf]
mov BYTE [buf],72
inc [buf]
mov BYTE [buf],73
ret
endp

FASM automatically push and pop the registers when using this, so 'buf' is really just another word for eax register.

But it's not working, the actual buffer does not get affected at all with this. What am I missing??
 

lolslayer

Member
Okay I have a few ideas how it might work, but I'm not very familiar with assembly, not at all, so here goes nothing.

Is eax the right register type? From the manual, I found that for general registers, these were the keywords:
Code:
8 bits:
al cl dl bl ah ch dh bh
16 bits:
ax cx dx bx sp bp si di
32 bits:
eax ecx edx ebx esp ebp esi edi
In your buffer you're working with 8-bit data, doesn't that mean that you need to choose one of the 8-bit registers?

And for the increment part, I found this example in the manual:
Code:
inc ax          ; increment register by one
inc byte [bx]   ; increment memory by one
it seems like you're incrementing the memory that the register is pointing at, instead of incrementing the pointer itself (because of the [brackets] around the variable).

I can be very wrong in this regard though because I very rarely work with assembly, but I hope that I was of any use for you here :)

For incrementing I have these 3 ideas that might fix it (with the upper one being the most believable in my eyes):
Code:
inc buf
Code:
inc byte [buf]
Code:
inc byte buf
 
Last edited:
S

sysop50

Guest
Any help is always very much appreciated :)

And actually you were right about the 'inc' thing (though I haven't read up on it yet, so can't be 100% sure). But it might just be that 'inc buf' or 'inc [buf] will increase by the amount of bytes the register will contain - so a 'inc eax' will increase eax by 4 as it is a 32-bit register.

Anyway, I could see there was some other mistakes in my code, besides the 'inc', but even it's corrected it still won't work.

Here's the code:
Code:
proc Blow buf
     mov ebx,[buf]
     mov [ebx],BYTE 71
     add ebx,1
     mov [ebx],BYTE 72
     add ebx,1
     mov [ebx],BYTE 73
     ret
endp
To explain the [ and ] .... well, it's an in-direct address thing. It means we want to do something at the address where e.g. 'eax' or 'buf' is pointing at.

GMS sends the pointer I need wrapped into a string. But GMS doesn't send the actual string, just a pointer to it.
So first I unwrap the 'buf' by looking at the place it is pointing at and put that value into the 'ebx' register - that's the value I really want; the buffer pointer.
Then I move the value 71 into the place where 'ebx' is pointing at.
Then I add 1 to the 'ebx' pointer so next time I move a value (72) it will be 1 byte after the previous one.

But but but .... doesn't seems to matter what I do - I've tried a million things by now and it just won't work. I have inserted a messagebox in the beginning of the function, in the middle and in the end, just to verify that I get the pointer to the buffer, and to check if those 'mov ....' actually do something, and it works fine (that's why I use some ordinary ascii characters like 1,2,3 and F,G,H so I can check through the messagebox), but back at GMS nothing has happened in the buffer I created.

It's like the GMS buffer is 'read-only' or a 'shadow memory' though that would be ...... hmmm..... a weird thing to do from YoYo's side.
 

lolslayer

Member
I'll get back to you tomorrow, I'm currently too tired for something this complicated. I'll also try to get a friend of mine who's more familiar with assembly to take a look as well.
 

lolslayer

Member
It's like the GMS buffer is 'read-only' or a 'shadow memory' though that would be ...... hmmm..... a weird thing to do from YoYo's side.
I don't believe that this is the case, I work with buffers too in DLL's and it works perfectly fine, but I use C++ instead.

I believe however that the problem is found in your GM code.

This is the code you provided here:
Code:
buffer_poke(b,0,buffer_u8,49) // 1
buffer_poke(b,1,buffer_u8,50) // 2
buffer_poke(b,2,buffer_u8,51) // 3
blow(adr)
print(buffer_peek(b,0,buffer_u8))
print(buffer_peek(b,1,buffer_u8))
print(buffer_peek(b,2,buffer_u8))
As we know, when you poke or peek in a buffer, you move the buffer reading address further through the buffer with the same amount of bytes you just poked or peeked. In GM you pass a pointer that points at the start of the buffer to FASM, and FASM writes data to the first 3 values in the buffer. When you try to read those values in GM you start at the 4th value in the buffer, because you left off where you ended writing to the buffer in GM.

So knowing this, maybe this will work:
Code:
buffer_poke(b,0,buffer_u8,49) // 1
buffer_poke(b,1,buffer_u8,50) // 2
buffer_poke(b,2,buffer_u8,51) // 3
blow(adr)
buffer_seek(buff, buffer_seek_start, 0);
print(buffer_peek(b,0,buffer_u8))
print(buffer_peek(b,1,buffer_u8))
print(buffer_peek(b,2,buffer_u8))
 
S

sysop50

Guest
Sorry for the late reply. Damn life got in the way :(

I will try it out in a few hours. I'm currently downloading, and installing, VS 2019 again to give C++ a (small) chance - at least to see if that one works..... I installed VS 2019 about a week ago and when I loaded up the C++ part to write a little code, it instantly pops up with a "windows.h is missing" .......... of all the effing files, THAT'S the one it's missing - I kinda knew how that would end so I'm installing it again, and the installer want to download all again :( (I'm on expensive data here)

Anyways, I'll be back :)
 

rIKmAN

Member
Sorry for the late reply. Damn life got in the way :(

I will try it out in a few hours. I'm currently downloading, and installing, VS 2019 again to give C++ a (small) chance - at least to see if that one works..... I installed VS 2019 about a week ago and when I loaded up the C++ part to write a little code, it instantly pops up with a "windows.h is missing" .......... of all the effing files, THAT'S the one it's missing - I kinda knew how that would end so I'm installing it again, and the installer want to download all again :( (I'm on expensive data here)

Anyways, I'll be back :)
If you're using 1.4 then for assured compatibility you should be using VS 2013 as per the Required SDKs article.
Better to get the recommended version working before moving onto trying the latest version of VS just to rule out any issues being caused from you using a version that isn't officially supported or recommended.

VS 2019 might work - but I wouldn't use it as the first version I was trying to get working.
 

lolslayer

Member
Sorry for the late reply. Damn life got in the way :(

I will try it out in a few hours. I'm currently downloading, and installing, VS 2019 again to give C++ a (small) chance - at least to see if that one works..... I installed VS 2019 about a week ago and when I loaded up the C++ part to write a little code, it instantly pops up with a "windows.h is missing" .......... of all the effing files, THAT'S the one it's missing - I kinda knew how that would end so I'm installing it again, and the installer want to download all again :( (I'm on expensive data here)

Anyways, I'll be back :)
That's okay man, interested to see if you get it working now :)
 
S

sysop50

Guest
Code:
buffer_poke(b,0,buffer_u8,49) // 1
buffer_poke(b,1,buffer_u8,50) // 2
buffer_poke(b,2,buffer_u8,51) // 3
blow(adr)
buffer_seek(buff, buffer_seek_start, 0);
print(buffer_peek(b,0,buffer_u8))
print(buffer_peek(b,1,buffer_u8))
print(buffer_peek(b,2,buffer_u8))
Didn't work unfortunately :(
But on the other hand, I didn't expected it to, as peek and poke are independent from the ordinary buffer commands, so they work on the direct offset from the buffer., but couldn't hurt to try at least. One never know.

My VS 2019 download also went haywire, so for now that's a dead end - so next time there's data available I'll go for the VS 2013 instead.
The assembly doesn't seem to work out either. What annoys me the most is that it Should work, but choose not to and I can't find the reason.
 

lolslayer

Member
Didn't work unfortunately :(
But on the other hand, I didn't expected it to, as peek and poke are independent from the ordinary buffer commands, so they work on the direct offset from the buffer., but couldn't hurt to try at least. One never know.
Damn, I was almost certain that that was it :\
 
L

Lonewolff

Guest
If you're using 1.4 then for assured compatibility you should be using VS 2013 as per the Required SDKs article.
Better to get the recommended version working before moving onto trying the latest version of VS just to rule out any issues being caused from you using a version that isn't officially supported or recommended.

VS 2019 might work - but I wouldn't use it as the first version I was trying to get working.
Not for DLL's. You can use any version of VS (or any other language that produces DLL's for that matter). And to add to that DLL's are self contained and don't have any SDK dependencies for interfacing to GMS.

I have made DLL's for GMS 1.2+ and GMS 2.0+ using versions of VS dating back to VS2003 (and every VS version after that).
 

rIKmAN

Member
Not for DLL's. You can use any version of VS (or any other language that produces DLL's for that matter). And to add to that DLL's are self contained and don't have any SDK dependencies for interfacing to GMS.

I have made DLL's for GMS 1.2+ and GMS 2.0+ using versions of VS dating back to VS2003 (and every VS version after that).
Yeah you're right, I was thinking of YYC and all the threads where people have issues setting up the correct paths and bat files etc with later versions than specified in the FAQ.

Nice to see you back around.
 
L

Lonewolff

Guest
Thanks man!

Yeah, YYC is pretty particular and won't build unless the SDK's are right.

Not to confuse @sysop50 though. You can use the YYC and still interface to DLL's that are built with different versions of an SDK.

In other words you can use a correctly configured YYC (requiring specific SDK) interfaced to a DLL (built with any SDK).

Interesting thread though. If buffers do actually return a pointer to memory (as advertised), then there is no reason why this shouldn't work.

I'll have a bit of a play and get back to you. A nice little warm up to get me back into the swing of things.
 
Last edited by a moderator:
L

Lonewolff

Guest
Righto, I just did a proof of concept (in C++) and all works as expected.

Code:
/// Create event

buff = buffer_create(10, buffer_fixed, 1);

buffer_poke(buff, 0, buffer_u8, 65);
buffer_poke(buff, 1, buffer_u8, 66);
buffer_poke(buff, 2, buffer_u8, 67);
buffer_poke(buff, 3, buffer_u8, 0);

buffer_read_back(buffer_get_address(buff)); // Read back initial contents
buffer_modify(buffer_get_address(buff));    // Makes changes to the buffer
buffer_read_back(buffer_get_address(buff)); // Read back modified contents
Code:
/// Draw event

var buff_string = "";
for(i = 0; i<buffer_get_size(buff);i++)
    buff_string += string(chr(buffer_peek(buff, i, buffer_u8)));

draw_text(20, 20, string(buff_string));
Code:
// C++ code

#define fn_export extern "C" __declspec (dllexport)

#include <Windows.h>

fn_export double buffer_read_back(char* buffer)
{
    MessageBox(NULL, buffer, buffer, NULL);
    return 1;
}

fn_export double buffer_modify(char* buffer)
{
    buffer[0] = 77;
    buffer[1] = 78;
    buffer[2] = 79;

    return 1;
    // I usually return '1' here just so I can prove that GMS is communicating with the DLL properly.
    // GMS will return '0' if it doesn't even see the DLL so a zero return value can be misleading.
}
With all this working as expected, this means there is a problem within the assembly source code.

I'll brush off my assembler and recreate this in that and get back to you. :)
 
Last edited by a moderator:
L

Lonewolff

Guest
Ok, I got this working from my end.

It is pretty bare bones and doesn't require any header files, SDK, or anything. Just requires a path to user32.lib (during the dll build time only).

It takes a pointer to a buffer as input, displays a message box of the current buffer contents ('ABC') in my test and changes the first three letters to 'DEF'.

Once you draw the contents back to the screen in GMS, it reflects the changes.

Works in GMS 1 and 2. :cool:

Code:
.model flat, stdcall
option casemap :none

includelib .\lib\user32.lib


.code
MessageBoxA             PROTO :DWORD,:DWORD,:DWORD,:DWORD


; Bare minimum required to construct a vaild DLL entrypoint
LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD

    mov eax, 1
    ret

LibMain endp


buffer$ = 8
dll_test proc
  
    ; Preserve EBP register.
    ; This is something to do with the calling address and the path back to the calling
    ; program. I don't really know the full details behind this one. Application crashes I
    ; don't do it.
    push ebp
    mov ebp, esp
  
    ; Preserve the EAX register
    ; This is done because the Windows Message Handler is contantly using the EAX
    ; register. Not preserving this will make the application behave screwy, if you
    ; use the EA registers in your program.
    push eax

    ; Display current contents of buffer to a windows message box.
    mov    eax, DWORD PTR buffer$[ebp]
    push 0
    push eax
    push eax
    push 0
    call MessageBoxA
  
    ; Re-write the first three characters in the buffer.
    mov    eax, DWORD PTR buffer$[ebp]
    mov bl, 68
    mov [eax+0], bl
    mov bl, 69
    mov [eax+1], bl
    mov bl, 70
    mov [eax+2], bl

    ; Restore the EAX register back to how it was when you started.
    pop eax

    ; Restore EBP register.
    pop ebp

    ; Return code '1' to the calling application.
    fld1
    ret    0

dll_test endp


end LibMain
Hope this helps :)
 
L

Lonewolff

Guest
How did you go? This is one of the topics that I find really interesting.
 
S

sysop50

Guest
Sorry :) life again
It didn't go very well. I'm consulting with the guys on flatassembler.net and hope they can share some light on it. Because it should really be a simple thing, though somehow ..... (tearing my hair out) ..... it's not. Basically, if I strip out your code with the messagebox, your code looks Very much like mine - except for the prolog with the ebp and esp that flatassembler takes care of (and the ending as well).
 
L

Lonewolff

Guest
Sorry, I don't have any experience with Flat Assembler.

My version was purely done in notepad and compiled at the command line using ml.exe and link.exe that comes with a standard a Visual Studio install.
 
Top