Page 1 of 1

NASM - Cross OS app for Linux/Windows using GTK

#1 GunnerInc  Icon User is online

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,300
  • Joined: 28-March 11

Post icon  Posted 18 September 2012 - 06:19 PM

To be honest, this was a PITA to get working as there was nothing explaining how to do this.

NASM and GTK+ on Windows kicked my ass!

I will teach you how to write a GUI app that will run on Linux and Windows with the same code using GTK. Since both OS's have a different executable format, you will have to Assemble and Link the code on each OS. No big deal here, we can use one makefile for this.

The only real prerequisite is to install GTK on windows, Linux should already have it installed if you are using a GNOME desktop (I think KDE uses it now), if not you will need to install it on Linux also.

Down here in our land, you do not need the GTK Developer package, you only need to download the GTK Runtimes. The GTK Developer package is a good thing to have though, since it includes all the headers and stuff.

GTK Download

Next, I use Geany on Linux and they have a Windows port, so grab that if you want to use the Geany Project file. Use your Package Manager to install it on Linux.

Of course you should already have NASM installed on both Linux and Windows.

The linker I use on Windows is GoLink
The linker on Linux is GCC.

You also need make for windows. You could probably use mingw32-make.exe from MinGW. I use GNU Make from GNUWin32 which contains a few GNU/open source Linux apps for Windows.

You should also have the GTK Documentation page bookmarked.

Now, you could totally create the whole GUI through code with GTK, but lets not!! We will use GLADE to create the xml file GTK uses to create the GUI. Guess what, they have both a Linux an Windows version! What are you waiting for, install it for Windows! For Linux, you can install it from your Package Manager or compile the sources.

We will create a very simple program to view text files (anything really if it is UTF-8 encoded). To keep it simple, there is no error checking whatsoever.

Attached Image

Attached Image

SECTION .text 
main:     
    push    0 
    push    0
    call    gtk_init
    add     esp, 4 * 2   
    
    call    gtk_builder_new
    mov     [oBuilder], eax
        
    push    NULL  
    push    szGladeFile
    push    eax 
    call    gtk_builder_add_from_file
    add     esp, 4 * 3 
    
    push    szIDMainWin
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oMain], eax

    push    szIDAbout
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oAbout], eax
 
    push    szIDFileDlg
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oFileDlg], eax

    push    szIDFileBuffer
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oFileBuffer], eax

    push    dword [oBuilder]  
    call    gtk_builder_connect_signals
    add     esp, 4 * 1
    
    push    dword [oBuilder]
    call    g_object_unref 
    add     esp, 4 * 1   

    push    dword [oMain]
    call    gtk_widget_show
    add     esp, 4 * 1

    call    gtk_main
	
    call	exit

File_Open:
    push    dword [oFileDlg]
    call    gtk_dialog_run
    add     esp, 4 * 1
    cmp		eax, GTK_RESPONSE_ACCEPT
    jne		.done
    
    push	dword [oFileDlg]
    call	gtk_file_chooser_get_filename
    add		esp, 4 * 1
    mov		esi, eax
    
	push	esi
	call	DisplayFileContents
	
	push	esi
	call	g_free
	add		esp, 4 * 1

.done:
    push    dword [oFileDlg]
    call    gtk_widget_hide
    add     esp, 4 * 1 
	ret

DisplayFileContents:
	push	ebp
	mov		ebp, esp
	sub		esp, 8
	push	esi
	push	edi
	
	lea		edi, [ebp - 8]
	lea		esi, [ebp - 4]
	
	push	NULL
	push	edi	
	push	esi
	push	dword [ebp + 8]
	call	g_file_get_contents
	add		esp, 4 * 4

	push	dword [edi]
	push	dword [esi]
	push	dword [oFileBuffer]
	call	gtk_text_buffer_set_text
	add		esp, 4 * 3
	
	push	dword [esi]
	call	g_free
	add		esp, 4 * 1
	
	pop		edi
	pop		esi
	add		esp, 8
	mov		esp, ebp
	pop		ebp
	ret		4 * 1
	
; +++++++++++++++++++++++++++++++++++++++++ 
about:    
    push    dword [oAbout]
    call    gtk_dialog_run
    add     esp, 4 * 1
    
    push    dword [oAbout]
    call    gtk_widget_hide
    add     esp, 4 * 1    
That's it!

First thing we need to do is initialize GTK with gtk_init. This will initialize everything GTK will need, and parse command line options which we don't use here.
    push    0 
    push    0
    call    gtk_init
    add     esp, 4 * 2 


We now need to create a new GtkBuilder object which is responsible for loading the glade file and all widgets.
    call    gtk_builder_new
    mov     [oBuilder], eax

Now we can have GTKBuilder load our glade file and create the GUI. Here I keep the glade file in the app directory. You will need to ship your glade file with your app. You could also load the glade file from a resource or a string in your data section.
    push    NULL  
    push    szGladeFile
    push    eax 
    call    gtk_builder_add_from_file
    add     esp, 4 * 3

Next we get the widget object so we can interact with them. gtk_builder_get_object returns an object we request, it returns what amounts to a handle on Windows AFAIK.
 push    szIDMainWin
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oMain], eax

    push    szIDAbout
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oAbout], eax
 
    push    szIDFileDlg
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oFileDlg], eax

    push    szIDFileBuffer
    push    dword [oBuilder]
    call    gtk_builder_get_object 
    add     esp, 4 * 2
    mov     [oFileBuffer], eax

Next we connect any signals we have set up in the glade file. These are not the same as Linux system signals. You can think of GTK signals as Windows Messages. Click a button, and the procedure you set up is called.
    push    dword [oBuilder]  
    call    gtk_builder_connect_signals
    add     esp, 4 * 1

You need to export these procedures in order for glade to find and call them.

We are done the the Builder Object, so we unreference it
    push    dword [oBuilder]
    call    g_object_unref 
    add     esp, 4 * 1   

Our window just doesn't appear on the screen, so we need to tell GTK to show it
    push    dword [oMain]
    call    gtk_widget_show
    add     esp, 4 * 1

Just like in Windows, we need a message loop to listen for and process any happenings.
    call    gtk_main

GTK will exit its loop when it gets the destroy signal, at which time we can exit our app
    call	exit

If you wanted to, you could call ExitProcess here instead, but that would not work on Linux.

When the About button is clicked or Alt+A is used, GTK will call our About proc since that is what we set up in the glad file to be called for the clicked signal.
; +++++++++++++++++++++++++++++++++++++++++ 
about:    
    push    dword [oAbout]
    call    gtk_dialog_run
    add     esp, 4 * 1
    
    push    dword [oAbout]
    call    gtk_widget_hide
    add     esp, 4 * 1    
    ret

We are using a standard About box and it is set to destroy with parent, so we hide it each time we are done with it.

When the Open button is clicked, it will call our File_Open function
File_Open:
    push    dword [oFileDlg]
    call    gtk_dialog_run
    add     esp, 4 * 1
    cmp		eax, GTK_RESPONSE_ACCEPT
    jne		.done
    
    push	dword [oFileDlg]
    call	gtk_file_chooser_get_filename
    add		esp, 4 * 1
    mov		esi, eax
    
	push	esi
	call	DisplayFileContents
	
	push	esi
	call	g_free
	add		esp, 4 * 1
  
.done:
    push    dword [oFileDlg]
    call    gtk_widget_hide
    add     esp, 4 * 1 
	ret

We "run" the dialog with gtk_dialog_run and wait for the user to finish. The function will return a code telling us what happened (just like a windows message box), if the OK button was not selected (the return was not GTK_RESPONSE_ACCEPT) then we just skip the file open code. You can set up your own return ID in the glade file.

So somebody either double clicked on a file in the file browser or selected a file and choose ok, now we need the path and file name, we do this with gtk_file_chooser_get_filename this function will return a pointer to a buffer GTK allocates with the path and file name. We then pass this pointer on to DisplayFileContents and free the pointer with g_free

DisplayFileContents:
	push	ebp
	mov		ebp, esp
	sub		esp, 8
	push	esi
	push	edi
	
	lea		edi, [ebp - 8]
	lea		esi, [ebp - 4]
	
	push	NULL
	push	edi	
	push	esi
	push	dword [ebp + 8]
	call	g_file_get_contents
	add		esp, 4 * 4

	push	dword [edi]
	push	dword [esi]
	push	dword [oFileBuffer]
	call	gtk_text_buffer_set_text
	add		esp, 4 * 3
	
	push	dword [esi]
	call	g_free
	add		esp, 4 * 1
	
	pop		edi
	pop		esi
	add		esp, 8
	mov		esp, ebp
	pop		ebp
	ret		4 * 1

We set up space on the stack for 2 DWORD variables with sub esp, 8. g_file_get_contents will allocate a buffer to hold the file contents and the filesize so we need to vars for this. Now that we have the contents of the file, we display it in the textview buffer with gtk_text_buffer_set_text
Once done with the file contents, we free it with g_free. This g_free looks different then the earlier on because get_contents gives us a pointer to a pointer.

That is it. Fairly simple.

Now the makefile we use:
ifeq ($(OS),Windows_NT)
# Windows 
cross_os: cross_os.obj
	GoLink.exe /fo "Cross OS - Windows.exe" /console /entry main cross_os.obj @imports.inc @exports.inc

cross_os.obj: cross_os.asm
	nasm -f win32 cross_os.asm -o cross_os.obj
	
else
# Linux 
cross_os: cross_os.o    
	gcc -o cross_os cross_os.o `pkg-config --cflags --libs gtk+-2.0` -export-dynamic 
	
cross_os.o: cross_os.asm
	nasm -f elf cross_os.asm 
	
endif

Make will detect which OS it is on and run the appropriate commands.
You can lookup the meanings of the command line options to learn more about them. For the Windows app, I link it as a console app in case I wanted to print out strings or ints to the console with g_print The GUI program will still run fine.

We don't need lib files to link with when using GoLink. It will take a list of dlls and use them, this is what the imports.inc file is for.

As I said earlier, you need to export some things for gtk_builder_connect_signals
For Linux/GCC:
1. We mark them as global in the globals.inc and
2. We use the -export-dynamic command line option
global  main, File_Open, About


For Windows/GoLink:
we mark them as exportable in exports.inc
/export File_Open, About

The GTK runtimes consist of 25 files for a total of 13MB. These need to be in your app directory. Actually, you only need to ship the dlls that your app needs. This is trial and error of removing them. I am sure you can put them in your /system32 directory, haven't tried.

The icon for the app and about box, need to be in the app directory.

Attached File(s)



Is This A Good Question/Topic? 0
  • +

Page 1 of 1