Author Topic: Console Win32  (Read 12854 times)

Offline jedi

  • Jr. Member
  • *
  • Posts: 9
Console Win32
« on: December 20, 2012, 11:42:23 PM »
I've been struggling with the console function for many days. I can't accomplish anything useful unless I can get output..! I've done as much reading as I could but I've failed. I've tried hundreds of variations. Would someone please help me get the following program to print "Hello" to a console. Thank you!

Code: [Select]
;Compiled with "nasm -f win32 hello.asm"
;Linked with GoLink "golink hello.obj kernel32.dll"
;Ran as "hello.exe"
;This function does NOTHING but exits without crashing.

%include "windows.inc"

SECTION .data             
message: dd 'Hello',0   
STDOUTPUTHANDLE: equ -11h

SECTION .bss
len: resb 100
 
SECTION .text           
                global start
extern WriteConsoleA
extern ExitProcess
extern GetStdHandle

start:                       
push STDOUTPUTHANDLE
call GetStdHandle

                ; Reserved; must be NULL.
push dword 0

; A pointer to a variable that receives the number of characters actually written.
push dword len

; The number of characters to be written.
push dword 0Ah

; A pointer to a buffer that contains characters to be written to the console screen buffer.
push dword message

; A handle to the console screen buffer.
; If you specify NULL, the object gets a default security descriptor.
; Check out this for more information: http://msdn.microsoft.com/en-us/library/ms682062(v=vs.85).aspx
push dword eax

; Information on this function can be found here: http://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
call WriteConsoleA
ret

; Terminates the program.
; Without this the program will crash.
call ExitProcess

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Console Win32
« Reply #1 on: December 21, 2012, 12:54:13 AM »
I have VERY little experience with the Windows API. Even when I was running Windows, I mostly targetted DOS, and the very few Windows programs I tried were with "-f obj" (which I do not recommend). I did manage to get output from WriteFile (pretty much the same parameters as you're using). On the optimistic philosophy that "code is code", here's what I see... (haven't changed your code, just added comments).

Code: [Select]
;Compiled with "nasm -f win32 hello.asm"
;Linked with GoLink "golink hello.obj kernel32.dll"

; "/entry start"? - possibly "/subsystem console"?

;Ran as "hello.exe"
;This function does NOTHING but exits without crashing.

%include "windows.inc"

SECTION .data             
message: dd 'Hello',0   
; probably should be "db" not "dd".

STDOUTPUTHANDLE: equ -11h

SECTION .bss
len: resb 100

; probably only need "resd 1" here,
; but this should work fine.
 
SECTION .text           
                global start
extern WriteConsoleA
extern ExitProcess
extern GetStdHandle

start:                       
push STDOUTPUTHANDLE
call GetStdHandle

                ; Reserved; must be NULL.
push dword 0

; A pointer to a variable that receives the number of characters actually written.
push dword len

; The number of characters to be written.
push dword 0Ah
; seems too much - shouldn't hurt.

; A pointer to a buffer that contains characters to be written to the console screen buffer.
push dword message

; A handle to the console screen buffer.
; If you specify NULL, the object gets a default security descriptor.
; Check out this for more information: http://msdn.microsoft.com/en-us/library/ms682062(v=vs.85).aspx
push dword eax

; Information on this function can be found here: http://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
call WriteConsoleA
ret
; doubt if you want to "ret" here!

; Terminates the program.
; Without this the program will crash.

; push 0 (or other exitcode)
call ExitProcess

This looks to me like it should work, as is. My suggestions "might" help. I'm sure (I hope) a Windows programmer will be along soon to straighten you out!

"The first days are the hardest days,
don't you worry any more,
'cause when life looks like easy street,
there is danger at your door." - Grateful Dead

Best,
Frank


Offline Gunner

  • Jr. Member
  • *
  • Posts: 74
  • Country: us
    • Gunners Software
Re: Console Win32
« Reply #2 on: December 21, 2012, 01:32:45 AM »
You are better off using WriteFile since it can handle console redirection.  BUT WriteConsole can handle Unicode characters.  What you can do is call GetConsoleMode and if it succeeds, and the handle is a console handle, call WriteConsole, otherwise the output is redirected so call WriteFile

Try this code with both WriteFile and WriteConsole, since the parameters are the same.  One biggie I notice, is you declare your string as a DWORD array, not a BYTE array... typo on your part maybe?
code:
Code: [Select]
%define     STD_OUTPUT_HANDLE -11
%define     NULL 0

SECTION .data       
message:    db 'Hello',0 
message_len equ $ - message


SECTION .bss
stdout          resd 1
lpNumRead       resd 1
 
SECTION .text           
        global start
extern WriteFile
        extern WriteConsoleA
extern ExitProcess
extern GetStdHandle

start:                       
    ;~ Some initialization
    push    STD_OUTPUT_HANDLE               ;
    call    GetStdHandle                    ; get handle of console input
    mov     [stdout], eax                   ; save handle, you'll probably use it again'

    push    NULL                            ;
    push    lpNumRead                       ;
    push    message_len                     ;
    push    message                         ;
    push    dword [stdout]                  ;
    call    WriteConsoleA                       ;

    push 0
call ExitProcess

makefile:
Code: [Select]
APP=Console

all: clean $(APP)

$(APP): $(APP).obj
GoLink.exe /console /entry start /mix $(APP).obj @imports.inc

$(APP).obj: $(APP).asm
nasm -f win32 $(APP).asm -o $(APP).obj

clean:
del $(APP).exe
del $(APP).obj
del $(APP).res

vars:
asmvars $(APP).asm
   

 
« Last Edit: December 21, 2012, 01:34:18 AM by Gunner »

Offline Bryant Keller

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 360
  • Country: us
    • About Bryant Keller
Re: Console Win32
« Reply #3 on: December 21, 2012, 02:30:34 AM »
The three biggest issues with his code off the top of my head was:

- he declared STDOUTPUTHANDLE as -11h when it should have been -11.
- he used a ret for some unknown reason.
- he didn't pass a parameter to ExitProcess

I'm not sure if it's because I'm using MinGW to compile or if it's because I'm using Wine, but GetConsoleA doesn't seem to work when I update his example with the above changes. WriteFile however does. This might be because of the console redirection thing you was talking about.

Code: [Select]
; Compiled as: nasm -f win32 --prefix _ -o Build/main.o Source/main.asm
; Linked as: i586-mingw32msvc-gcc -Wl --subsystem,console  -o hello.exe Build/main.o
; Ran as: wine ./hello.exe


; I don't have your windows.inc file so I'll just do this the "hard-way"(TM)
;%include "windows.inc"

SECTION .data
; All I've changed here was adding 10 (newline) to the output and changed the type to BYTE
message: db 'Hello', 10, 0
; This equate will make the below code more readable.
message_size equ ($-message)
; STD_OUTPUT_HANDLE is -11 decimal, not hex
STDOUTPUTHANDLE equ -11

SECTION .bss
; As Frank said, this should be resd 1
len: resd 1
 
SECTION .text

;; This may look weird to you, but this is because I'm using MinGW to compile your demo.
; My entry point
                global main

; Various API routines we use by mangled name.
extern WriteFile@20
extern ExitProcess@4
extern GetStdHandle@4

;; NOTE:
; The above don't have the underscore prefix because I passed that on the command line. See first comment.
main:
push STDOUTPUTHANDLE
call GetStdHandle@4

                ; Reserved; must be NULL.
push dword 0

; A pointer to a variable that receives the number of characters actually written.
push dword len

; The number of characters to be written.
push dword message_size

; A pointer to a buffer that contains characters to be written to the console screen buffer.
push dword message

; A handle to the console screen buffer.
; If you specify NULL, the object gets a default security descriptor.
; Check out this for more information: http://msdn.microsoft.com/en-us/library/ms682062(v=vs.85).aspx
push dword eax

; Information on this function can be found here: http://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
call WriteFile@20
;ret

; Terminates the program.
; Without this the program will crash.
push dword 0
call ExitProcess@4

About Bryant Keller
bkeller@about.me

Offline jedi

  • Jr. Member
  • *
  • Posts: 9
Re: Console Win32
« Reply #4 on: December 21, 2012, 08:26:51 PM »
Thank you so much for your help guys.

These variations all produce a program that doesn't crash but I do not see any output to the console i.e. the "hello."

Not sure what to do, without output I can't do anything useful.

If I ever get a working version (hopefully soon) I will post my results to help the future Windows programmer :). Or else I might skip to Linux.

Thanks again for the help.

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Console Win32
« Reply #5 on: December 22, 2012, 07:04:09 AM »
That's distressing, Jedi. It isn't a deliberate ploy to force you into Linux, honest! Wouldn't hurt ya - you might even like it - but the whole idea of Nasm is to support choices. You wanna write a "hello world" for Windows... this should not be difficult!

I've got some examples that "used to work" (using WriteFile, not WriteConsole). I don't think much of 'em. They use "-f obj" and... Nasm's 32-bit extensions to "-f obj" aren't really very good. I even have some macros - from NaGoA - that will produce an executable with Nasm's "-f bin" and no pesky linker at all! If all else fails, we can revert to that...

One thing I missed in your original code was defining STDOUTPUTHANDLE as -11h, not -11.  That could keep it from working! Your "windows.inc" may already define it - try just using it.

The string wants to be "db" not "dd". The worst I can see happening from that is just getting the 'h'.

You don't want the "ret" in there after the call to WriteConsole/WriteFile (you've tried both, right?), but as I recall, you can "ret" from a Windows program, so it might work. ExitProcess is better, but at worst it should crash after printing your string.

Same with not pushing a parameter to ExitProcess - any problem should come later...

My real uncertainty is the command line to GoLink. I thought "/entry start" which Gunner confirms. I guessed "/subsystem", but that was from Alink or some other linker, I guess. Gunner says just "/console". Gunner also says "/mix" - I don't know what that does (RTFM, if all else fails). Gunner also says "@imports.inc"... Something that comes with GoLink? The NASMX package seems to use only "/entry _main" as a command line to GoLink. I would expect GoLink to complain if it doesn't find an entrypoint... maybe it doesn't, or maybe it defaults to "start"...

I'm stumped! This really shouldn't be this hard! Thanks for offering to post your results. We really shouldn't need it at this late date, but apparently we do. :)

Courage!

Best,
Frank


Offline Mathi

  • Jr. Member
  • *
  • Posts: 82
  • Country: in
    • Win32NASM
Re: Console Win32
« Reply #6 on: December 22, 2012, 01:20:09 PM »
Probably, this is already solved by now.
Still, the following code should work fine.

Code: [Select]
;Compiled with "nasm -f win32 hello.asm"
;***Linked with GoLink "golink /entry start /console hello.obj kernel32.dll"    ****
;Ran as "hello.exe"

%include "windows.inc"

SECTION .data             
message: db 'Hello',0   

SECTION .bss
len: resd 1
 
SECTION .text           
                global start
extern WriteConsoleA
extern ExitProcess
extern GetStdHandle

start:                       
push STD_OUTPUT_HANDLE
call GetStdHandle

                ; Reserved; must be NULL.
push dword 0

; A pointer to a variable that receives the number of characters actually written.
push dword len

; The number of characters to be written.
push dword 6

; A pointer to a buffer that contains characters to be written to the console screen buffer.
push dword message

; A handle to the console screen buffer.
; If you specify NULL, the object gets a default security descriptor.
; Check out this for more information: http://msdn.microsoft.com/en-us/library/ms682062(v=vs.85).aspx
push eax

; Information on this function can be found here: http://msdn.microsoft.com/en-us/library/ms687401(v=vs.85).aspx
call WriteConsoleA

; Terminates the program.
; Without this the program will crash.
push dword 0
call ExitProcess

Regards,
Mathi.
« Last Edit: December 22, 2012, 01:22:54 PM by Mathi »

Offline jedi

  • Jr. Member
  • *
  • Posts: 9
Re: Console Win32
« Reply #7 on: December 22, 2012, 05:47:24 PM »
Thanks Mathi, that works. I fell short with Golink. Thanks Frank, Gunner, Bryant for helping me write to console. This is a nice community.

Offline Gunner

  • Jr. Member
  • *
  • Posts: 74
  • Country: us
    • Gunners Software
Re: Console Win32
« Reply #8 on: December 22, 2012, 05:47:38 PM »
@Frank, that is the cool thing about GoLink - you can pass all the dlls to search for the functions on the command line, or put them in a file and pass that file which GoLink parses.  That is what my imports file is.

The /mix was left from a copy and paste - that makefile was from a project that uses a C dll, basically /mix doesn't add an underscore to the imports or something like that.

The default entry label GoLink looks for is start, not sure why I passed that parameter.

As you are, I am stumped as to why the code does not work for the OP.  The code I posted works on WinXP, 2K, and 7, both with WriteConsole and WriteFile.  That is basically standard boilerplate code.

Offline Bryant Keller

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 360
  • Country: us
    • About Bryant Keller
Re: Console Win32
« Reply #9 on: December 23, 2012, 06:03:45 AM »
As you are, I am stumped as to why the code does not work for the OP.  The code I posted works on WinXP, 2K, and 7, both with WriteConsole and WriteFile.  That is basically standard boilerplate code.

One of the issues he was having with his original version was that he wasn't using the /console switch to GoLink. It's possible he didn't use the makefile you posted for him. Same issue with my code, I didn't have access to GoLink so I used MinGW to build it. The best answer here was Mathi's because he not only directly addressed the issues in the OP's source, he also used the exact tools and proper command line that the user had.

Short overview:
 Frank - Incorrect usage of GoLink.
 Bryant - Completely failed to use GoLink.
 Gunner - Assumed user had 'make' utility.
 Mathi - Spot on!

Thanks Mathi, that works. I fell short with Golink. Thanks Frank, Gunner, Bryant for helping me write to console. This is a nice community.

Yeah, luckily there are enough of us code monkeys hitting keys to create a correct answer short of infinity. :P

About Bryant Keller
bkeller@about.me