How to receive different kinds of parameters in main function(eg `winMain`)

Crystal has no explicit main function. If in C,I can write:

int main(int argc, char** argv){
    //...
}

the pre-parameters are stored in ARGC and ARGV in Crystal.

however, sometime program are called with different types of parameters. the winMain function is mostly used in gui applications:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR lpCmdLine, INT nCmdShow)
{
    //your code...
}

it’s even defined with a different call convention.

how should I receive these parameters? they are neccessary in creating my window instance.

I wish I could adapt my main function like that:

# my_app.cr

@[CallConvention("X86_StdCall")]
def winMain(
  instance : HInstance,
  prev_instance : HInstance,
  cmd_line : UInt8*,
  cmd_show : Int32,
) : Int32
  #
  # my code...
end

and assign my entry point to the compiler:

crystal build my_app.cr --entry-point=winMain

How can I achieve this function, or at least get these parameters I need?

Windows already overrides the entry point with an /ENTRY link flag, see src/crystal/system/win32/wmain.cr for an example.

the wmain there is a wrapping of argc and argv,copying it into utf-8 form.

fun wmain(argc : Int32, argv : UInt16**) : Int32

the entry I’m looking for is to deal with other parameters. for example, how can I get hInstance ,and hPrevInstance,aside from argc and argv?

Assuming you want to build a Windows application without a console, this is one way to do it:

@[Link(ldflags: "/ENTRY:wWinMainCRTStartup")] # overrides previous `/ENTRY` setting
@[Link(ldflags: "/SUBSYSTEM:WINDOWS")]
lib LibCrystalMain
end

lib LibC
  fun CommandLineToArgvW(lpCmdLine : LPWSTR, pNumArgs : Int*) : LPWSTR*
  fun LocalFree(hMem : Void*) : Void*
end

@[CallConvention("X86_StdCall")]
fun wWinMain(hInstance : Void*, hPrevInstance : Void*, pCmdLine : UInt16*, nCmdShow : Int32) : Int32
  argv = LibC.CommandLineToArgvW(pCmdLine, out argc)
  status = wmain(argc, argv)
  LibC.LocalFree(argv)
  status
end

module Crystal::System::FileDescriptor
  def self.from_stdio(fd)
    console_handle = false
    handle = LibC._get_osfhandle(fd)
    if handle != -1 && handle != -2 # standard streams are unavailable
      handle = LibC::HANDLE.new(handle)
      old_mode = uninitialized LibC::DWORD
      if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0
        console_handle = true
        if fd == 1 || fd == 2 # STDOUT or STDERR
          if LibC.SetConsoleMode(handle, old_mode | LibC::ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
            at_exit { LibC.SetConsoleMode(handle, old_mode) }
          end
        end
      end
    end

    # don't use `#system_info` to determine whether the descriptor should block,
    # as that raises because `fd` is invalid (non-blocking IO isn't supported yet anyway)
    io = IO::FileDescriptor.new(fd, blocking: true)
    if console_handle
      io.sync = true
    else
      io.flush_on_newline = true
    end
    io
  end
end

Note that the entry point for the linker is not wmain nor wWinMain, but rather wWinMainCRTStartup, which calls wWinMain eventually. There is no need to remove the existing main or wmain, they are simply not called directly by the real entry point. This real entry point never accepts arguments, so there is no general mechanism to pass the parameters from a user entry point like wWinMain to Crystal.main. In this case all the parameters can be retrieved anywhere as below:

lib LibC
  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2

  fun GetCommandLineW : LPWSTR
  fun GetStartupInfoW(lpStartupInfo : STARTUPINFOW*)
end

hInstance = begin
  result = LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule)
  result != 0 ? hmodule : Pointer(Void).null
end

hPrevInstance = Pointer(Void).null

pCmdLine = LibC.GetCommandLineW

nCmdShow = begin
  LibC.GetStartupInfoW(out info)
  info.wShowWindow
end

For GCC / Clang on Linux the entry point is specified similarly:

@[Link(ldflags: "--entry=foo")]
lib LibCrystalMain
end

fun foo
  LibC.printf("abc\n")
  LibC._exit(0)
end

For macOS apparently you need an extra link flag.

2 Likes

I tried translating two functions from the tutorial

lib LibC
  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2

  fun GetCommandLineW : LPWSTR
  fun GetStartupInfoW(lpStartupInfo : STARTUPINFOW*)
  
    alias WPARAM = UInt32*
    alias LPARAM = Void* 
    alias LRESULT = Void*

    @[CallConvention("X86_StdCall")]
    type WNDPROC =  ( Void*,UInt32,WPARAM,LPARAM ) -> LRESULT
  #type WNDPROC =  ( WNDCLASSW*,UInt32,WPARAM,LPARAM ) -> LRESULT

  alias LPCWSTR = UInt16*
  struct WNDCLASSW 
       style : UInt32
       lpfnWndProc : WNDPROC
      cbClsExtra : Int32
      cbWndExtra : Int32
      hInstance : Void*#HINSTANCE
      hIcon : Void*#HICON
      hCursor : Void* #HCURSOR
      hbrBackground : Void* #HBRUSH
      lpszMenuName : LPCWSTR
      lpszClassName : LPCWSTR
  end
  
  

  alias HWND = WNDCLASSW*
  alias WNDCLASS = WNDCLASSW
  alias ATOM = UInt16
  type WINBOOL = Int32
  struct POINT
    x ,y : Int32
  end
  struct MSG
    hwnd : HWND
     message : UInt32
     wParam : WPARAM
     lParam : LPARAM
     time : Int32
    pt : POINT
  end
  alias LPMSG = MSG*
  @[CallConvention("X86_StdCall")]
  fun RegisterClassW(window_class : WNDCLASS*) : ATOM
  type HMENU = Void*
  type HINSTANCE = Void*
  type LPVOID = Void*
    @[CallConvention("X86_StdCall")]
  fun CreateWindowExW(
    dwExStyle : DWORD,
    lpClassName : LPCWSTR,
    lpWindowName : LPCWSTR,
    dwStyle : DWORD,
    x : Int32,y : Int32,nWidth : Int32,nHeight : Int32,
    hWndParent : HWND,hMenu : HMENU,hInstance : HINSTANCE,lpParam : LPVOID
  ) : HWND
  @[CallConvention("X86_StdCall")]
  fun GetMessageW(lpMsg : LPMSG, hWnd : HWND, wMsgFilterMin : UInt32, wMsgFilterMax : UInt32) : Int32
  @[CallConvention("X86_StdCall")]
  fun TranslateMessage(lpMsg : MSG*) : Int32
  @[CallConvention("X86_StdCall")]
  fun DispatchMessageW(lpMsg : MSG*) : Void*
  @[CallConvention("X86_StdCall")]
  fun ShowWindow(hWnd : HWND, nCmdShow : Int32) : Int32
  @[CallConvention("X86_StdCall")]
  struct RECT 
    left : Int32
    top : Int32
    right : Int32
    bottom : Int32
  end

  type HDC = Void*
  struct PAINTSTRUCT 
    hdc : HDC
     fErase : WINBOOL
     rcPaint : RECT
    fRestore : WINBOOL
     fIncUpdate : WINBOOL
       rgbReserved : BYTE[32]
  end
  alias LPPAINTSTRUCT = PAINTSTRUCT*
  @[CallConvention("X86_StdCall")]
  fun BeginPaint(hWnd : HWND, lpPaint : LPPAINTSTRUCT) : HDC
  type HBRUSH = Void*
  @[CallConvention("X86_StdCall")]
  fun FillRect(hDC : HDC, lprc : RECT*, hbr : HBRUSH) : Int32
  
  @[CallConvention("X86_StdCall")]
  fun EndPaint(hWnd : HWND, lpPaint : PAINTSTRUCT*) : WINBOOL
  @[CallConvention("X86_StdCall")]
  fun DefWindowProcW(hWnd : HWND, msg : UInt32, wParam : WPARAM, lParam : LPARAM) : LRESULT
    @[CallConvention("X86_StdCall")]
    fun PostQuitMessage (nExitCode : Int32) : Void

end

hInstance = begin
  result = LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule)
  result != 0 ? hmodule : Pointer(Void).null
end

hPrevInstance = Pointer(Void).null

pCmdLine = LibC.GetCommandLineW

nCmdShow = begin
  LibC.GetStartupInfoW(out info)
  info.wShowWindow
end

CW_USEDEFAULT = -1_i32

WS_OVERLAPPEDWINDOW = 13565952

@[Link(ldflags: "/ENTRY:wWinMainCRTStartup")] # overrides previous `/ENTRY` setting
@[Link(ldflags: "/SUBSYSTEM:WINDOWS")]
lib LibCrystalMain
end

@[Link("gdi32")]
@[Link("kernel32")]
lib LibC
  fun CommandLineToArgvW(lpCmdLine : LPWSTR, pNumArgs : Int*) : LPWSTR*
  fun LocalFree(hMem : Void*) : Void*
end

CLASS_NAME   = "Sample Window Class".to_utf16
WINDOW_TITLE = "Learn to Program Windows".to_utf16

@[CallConvention("X86_StdCall")]
fun wWinMain(hInstance : Void*, hPrevInstance : Void*, pCmdLine : UInt16*, nCmdShow : Int32) : Int32
  # argv = LibC.CommandLineToArgvW(pCmdLine, out argc)
  # status = wmain(argc, argv)
  wc = LibC::WNDCLASSW.new
  wc.lpfnWndProc = ->windowProc( LibC::HWND,UInt32,LibC::WPARAM,LibC::LPARAM).unsafe_as(LibC::WNDPROC)
  wc.hInstance = hInstance
  wc.lpszClassName = CLASS_NAME
  LibC.RegisterClassW(pointerof(wc))
  # Create the window.
  hwnd = LibC.CreateWindowExW(
    0,          # Optional window styles
    CLASS_NAME, # Window class
    WINDOW_TITLE,
    WS_OVERLAPPEDWINDOW, # Window style
    # Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    nil,       # Parent window
    nil,       # Menu
    hInstance.as(LibC::HINSTANCE), # Instance handle
    nil        # Additional application data
  )
  unless hwnd
    return 0
  end
  LibC.ShowWindow(hwnd, nCmdShow)
  # Run the message loop.
  msg = LibC::MSG.new()
  while (LibC.GetMessageW(pointerof(msg), nil, 0, 0) > 0)
    LibC.TranslateMessage(pointerof(msg))
    LibC.DispatchMessageW(pointerof(msg))
  end
  #LibC.LocalFree(argv)
  return 0
end
COLOR_WINDOW = 5
WM_DESTROY = 2
WM_PAINT = 0xF
@[CallConvention("X86_StdCall")]
def windowProc(hwnd : LibC::HWND, uMsg : UInt32, wParam : LibC::WPARAM, lParam : LibC::LPARAM) : LibC::LRESULT
  case (uMsg)
  when WM_DESTROY
    LibC.PostQuitMessage(0)
    return nil.as(Void*)
  when WM_PAINT
    ps = LibC::PAINTSTRUCT.new
    hdc  = LibC.BeginPaint(hwnd, pointerof(ps))
    # All painting occurs here, between BeginPaint and EndPaint.
    psrcPaint = ps.rcPaint 
    LibC.FillRect(hdc, pointerof(psrcPaint), (COLOR_WINDOW + 1).unsafe_as(LibC::HBRUSH))
    ps.rcPaint = psrcPaint
    LibC.EndPaint(hwnd, pointerof(ps))
    return nil.as(Void*)
  end
  return LibC.DefWindowProcW(hwnd, uMsg, wParam, lParam)
end

module Crystal::System::FileDescriptor
  def self.from_stdio(fd)
    console_handle = false
    handle = LibC._get_osfhandle(fd)
    if handle != -1 && handle != -2 # standard streams are unavailable
      handle = LibC::HANDLE.new(handle)
      old_mode = uninitialized LibC::DWORD
      if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0
        console_handle = true
        if fd == 1 || fd == 2 # STDOUT or STDERR
          if LibC.SetConsoleMode(handle, old_mode | LibC::ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
            at_exit { LibC.SetConsoleMode(handle, old_mode) }
          end
        end
      end
    end

    # don't use `#system_info` to determine whether the descriptor should block,
    # as that raises because `fd` is invalid (non-blocking IO isn't supported yet anyway)
    io = IO::FileDescriptor.new(fd, blocking: true)
    if console_handle
      io.sync = true
    else
      io.flush_on_newline = true
    end
    io
  end
end

compile it, and get such error

PS C:\Users\homodeluna\Desktop\crtestwin> crystal build .\winapp.cr

_main.obj : error LNK2019: unresolved external symbol RegisterClassW,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol CreateWindowExW,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol ShowWindow,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol GetMessageW,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol TranslateMessage,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol DispatchMessageW,referred from function wWinMain 
_main.obj : error LNK2019: unresolved external symbol PostQuitMessage,referred from function .2A.windowProc.3C.Pointer.28.LibC.3A..3A.WNDCLASSW.29..2C..20.UInt32.2C..20.Pointer.28.UInt32.29..2C..20.Pointer.28.Void.29..3E..3A.Pointer.28.Void.29. 
_main.obj : error LNK2019: unresolved external symbol BeginPaint,referred from function .2A.windowProc.3C.Pointer.28.LibC.3A..3A.WNDCLASSW.29..2C..20.UInt32.2C..20.Pointer.28.UInt32.29..2C..20.Pointer.28.Void.29..3E..3A.Pointer.28.Void.29. 
_main.obj : error LNK2019: unresolved external symbol DefWindowProcW,referred from function .2A.windowProc.3C.Pointer.28.LibC.3A..3A.WNDCLASSW.29..2C..20.UInt32.2C..20.Pointer.28.UInt32.29..2C..20.Pointer.28.Void.29..3E..3A.Pointer.28.Void.29. 
_main.obj : error LNK2019: unresolved external symbol FillRect,referred from function .2A.windowProc.3C.Pointer.28.LibC.3A..3A.WNDCLASSW.29..2C..20.UInt32.2C..20.Pointer.28.UInt32.29..2C..20.Pointer.28.Void.29..3E..3A.Pointer.28.Void.29. 
_main.obj : error LNK2019: unresolved external symbol EndPaint,referred from function .2A.windowProc.3C.Pointer.28.LibC.3A..3A.WNDCLASSW.29..2C..20.UInt32.2C..20.Pointer.28.UInt32.29..2C..20.Pointer.28.Void.29..3E..3A.Pointer.28.Void.29. 
C:\Users\homodeluna\Desktop\crtestwin\winapp.exe : fatal error LNK1120: 11 unresolved external commands

I believe I have spelled these function names right. Is it because I didn’t add link to all required libs?

Try @[Link("User32")]

after adding @Link("User32") ,it passed compiling, but running it still get nothing. I tried printing some log to stdout , but got nothing.
the code now:

lib LibC
  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2

  fun GetCommandLineW : LPWSTR
  fun GetStartupInfoW(lpStartupInfo : STARTUPINFOW*)

  alias WPARAM = UInt32*
  alias LPARAM = Void*
  alias LRESULT = Void*

  @[CallConvention("X86_StdCall")]
  type WNDPROC = (Void*, UInt32, WPARAM, LPARAM) -> LRESULT
  # type WNDPROC =  ( WNDCLASSW*,UInt32,WPARAM,LPARAM ) -> LRESULT

  alias LPCWSTR = UInt16*

  struct WNDCLASSW
    style : UInt32
    lpfnWndProc : WNDPROC
    cbClsExtra : Int32
    cbWndExtra : Int32
    hInstance : Void*     # HINSTANCE
    hIcon : Void*         # HICON
    hCursor : Void*       # HCURSOR
    hbrBackground : Void* # HBRUSH
    lpszMenuName : LPCWSTR
    lpszClassName : LPCWSTR
  end

  alias HWND = WNDCLASSW*
  alias WNDCLASS = WNDCLASSW
  alias ATOM = UInt16
  type WINBOOL = Int32

  struct POINT
    x, y : Int32
  end

  struct MSG
    hwnd : HWND
    message : UInt32
    wParam : WPARAM
    lParam : LPARAM
    time : Int32
    pt : POINT
  end

  alias LPMSG = MSG*
  @[CallConvention("X86_StdCall")]
  fun RegisterClassW(window_class : WNDCLASS*) : ATOM
  type HMENU = Void*
  type HINSTANCE = Void*
  type LPVOID = Void*
  @[CallConvention("X86_StdCall")]
  fun CreateWindowExW(
    dwExStyle : DWORD,
    lpClassName : LPCWSTR,
    lpWindowName : LPCWSTR,
    dwStyle : DWORD,
    x : Int32, y : Int32, nWidth : Int32, nHeight : Int32,
    hWndParent : HWND, hMenu : HMENU, hInstance : HINSTANCE, lpParam : LPVOID
  ) : HWND
  @[CallConvention("X86_StdCall")]
  fun GetMessageW(lpMsg : LPMSG, hWnd : HWND, wMsgFilterMin : UInt32, wMsgFilterMax : UInt32) : Int32
  @[CallConvention("X86_StdCall")]
  fun TranslateMessage(lpMsg : MSG*) : Int32
  @[CallConvention("X86_StdCall")]
  fun DispatchMessageW(lpMsg : MSG*) : Void*
  @[CallConvention("X86_StdCall")]
  fun ShowWindow(hWnd : HWND, nCmdShow : Int32) : Int32
  @[CallConvention("X86_StdCall")]
  struct RECT
    left : Int32
    top : Int32
    right : Int32
    bottom : Int32
  end

  type HDC = Void*

  struct PAINTSTRUCT
    hdc : HDC
    fErase : WINBOOL
    rcPaint : RECT
    fRestore : WINBOOL
    fIncUpdate : WINBOOL
    rgbReserved : BYTE[32]
  end

  alias LPPAINTSTRUCT = PAINTSTRUCT*
  @[CallConvention("X86_StdCall")]
  fun BeginPaint(hWnd : HWND, lpPaint : LPPAINTSTRUCT) : HDC
  type HBRUSH = Void*
  @[CallConvention("X86_StdCall")]
  fun FillRect(hDC : HDC, lprc : RECT*, hbr : HBRUSH) : Int32

  @[CallConvention("X86_StdCall")]
  fun EndPaint(hWnd : HWND, lpPaint : PAINTSTRUCT*) : WINBOOL
  @[CallConvention("X86_StdCall")]
  fun DefWindowProcW(hWnd : HWND, msg : UInt32, wParam : WPARAM, lParam : LPARAM) : LRESULT
  @[CallConvention("X86_StdCall")]
  fun PostQuitMessage(nExitCode : Int32) : Void
end

hInstance = begin
  result = LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule)
  result != 0 ? hmodule : Pointer(Void).null
end

hPrevInstance = Pointer(Void).null

pCmdLine = LibC.GetCommandLineW

nCmdShow = begin
  LibC.GetStartupInfoW(out info)
  info.wShowWindow
end

CW_USEDEFAULT = -1_i32

WS_OVERLAPPEDWINDOW = 13565952

@[Link(ldflags: "/ENTRY:wWinMainCRTStartup")] # overrides previous `/ENTRY` setting
@[Link(ldflags: "/SUBSYSTEM:WINDOWS")]
lib LibCrystalMain
end

@[Link("gdi32")]
@[Link("kernel32")]
@[Link("User32")]
lib LibC
  fun CommandLineToArgvW(lpCmdLine : LPWSTR, pNumArgs : Int*) : LPWSTR*
  fun LocalFree(hMem : Void*) : Void*
end

CLASS_NAME   = "Sample Window Class".to_utf16
WINDOW_TITLE = "Learn to Program Windows".to_utf16

@[CallConvention("X86_StdCall")]
fun wWinMain(hInstance : Void*, hPrevInstance : Void*, pCmdLine : UInt16*, nCmdShow : Int32) : Int32
  # argv = LibC.CommandLineToArgvW(pCmdLine, out argc)
  # status = wmain(argc, argv)
  puts "begin to create window class"
  wc = LibC::WNDCLASSW.new
  puts "wc created"
  wc.lpfnWndProc = ->windowProc(LibC::HWND, UInt32, LibC::WPARAM, LibC::LPARAM).unsafe_as(LibC::WNDPROC)
  wc.hInstance = hInstance
  wc.lpszClassName = CLASS_NAME
  
  LibC.RegisterClassW(pointerof(wc))
  puts "wc registerclass"
  # Create the window.
  hwnd = LibC.CreateWindowExW(
    0,          # Optional window styles
    CLASS_NAME, # Window class
    WINDOW_TITLE,
    WS_OVERLAPPEDWINDOW, # Window style
    # Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    nil,                           # Parent window
    nil,                           # Menu
    hInstance.as(LibC::HINSTANCE), # Instance handle
    nil                            # Additional application data
  )
  puts "hwnd created"
  unless hwnd
    return 0
  end
  
  LibC.ShowWindow(hwnd, nCmdShow)
  puts "window shown"
  # Run the message loop.
  msg = LibC::MSG.new
  while (LibC.GetMessageW(pointerof(msg), nil, 0, 0) > 0)
    puts "get msg"
    LibC.TranslateMessage(pointerof(msg))
    LibC.DispatchMessageW(pointerof(msg))
  end
  puts "return"
  # LibC.LocalFree(argv)
  return 0
end
COLOR_WINDOW =   5
WM_DESTROY   =   2
WM_PAINT     = 0xF
@[CallConvention("X86_StdCall")]
def windowProc(hwnd : LibC::HWND, uMsg : UInt32, wParam : LibC::WPARAM, lParam : LibC::LPARAM) : LibC::LRESULT
  case (uMsg)
  when WM_DESTROY
    LibC.PostQuitMessage(0)
    return nil.as(Void*)
  when WM_PAINT
    ps = LibC::PAINTSTRUCT.new
    hdc = LibC.BeginPaint(hwnd, pointerof(ps))
    # All painting occurs here, between BeginPaint and EndPaint.
    psrcPaint = ps.rcPaint
    LibC.FillRect(hdc, pointerof(psrcPaint), (COLOR_WINDOW + 1).unsafe_as(LibC::HBRUSH))
    ps.rcPaint = psrcPaint
    LibC.EndPaint(hwnd, pointerof(ps))
    return nil.as(Void*)
  end
  return LibC.DefWindowProcW(hwnd, uMsg, wParam, lParam)
end

module Crystal::System::FileDescriptor
  def self.from_stdio(fd)
    console_handle = false
    handle = LibC._get_osfhandle(fd)
    if handle != -1 && handle != -2 # standard streams are unavailable
      handle = LibC::HANDLE.new(handle)
      old_mode = uninitialized LibC::DWORD
      if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0
        console_handle = true
        if fd == 1 || fd == 2 # STDOUT or STDERR
          if LibC.SetConsoleMode(handle, old_mode | LibC::ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
            at_exit { LibC.SetConsoleMode(handle, old_mode) }
          end
        end
      end
    end

    # don't use `#system_info` to determine whether the descriptor should block,
    # as that raises because `fd` is invalid (non-blocking IO isn't supported yet anyway)
    io = IO::FileDescriptor.new(fd, blocking: true)
    if console_handle
      io.sync = true
    else
      io.flush_on_newline = true
    end
    io
  end
end

You should definitely not put your code inside wWinMain directly because that skips Crystal’s runtime initialization. Just put the code in the usual top-level namespace.

The standard streams are completely unavailable for graphical-mode applications, the above is only a workaround to allow the IO::FileDescriptor constructor to not fail. To show unhandled exceptions:

@[Link("User32")]
lib LibC
  fun MessageBoxW(hWnd : Void*, lpText : LPWSTR, lpCaption : LPWSTR, uType : UInt) : Int
end

module Crystal
  def self.exit(status : Int32, exception : Exception?) : Int32
    status = Crystal::AtExitHandlers.run status, exception

    if exception
      LibC.MessageBoxW(nil, exception.inspect_with_backtrace.to_utf16, "Unhandled exception".to_utf16, 0x10)
    end

    status
  end
end
1 Like

now it just partially worked, at least error message is shown,now the focus is somewhat about windows api functions. the same window class runs normally in c,but fails in crystal.

lib LibC
  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2

  fun GetCommandLineW : LPWSTR
  fun GetStartupInfoW(lpStartupInfo : STARTUPINFOW*)

  alias WPARAM = UInt32*
  alias LPARAM = Void*
  alias LRESULT = Void*

  @[CallConvention("X86_StdCall")]
  type WNDPROC = (Void*, UInt32, WPARAM, LPARAM) -> LRESULT
  # type WNDPROC =  ( WNDCLASSW*,UInt32,WPARAM,LPARAM ) -> LRESULT

  alias LPCWSTR = UInt16*

  struct WNDCLASSW
    style : UInt32
    lpfnWndProc : WNDPROC
    cbClsExtra : Int32
    cbWndExtra : Int32
    hInstance : Void*     # HINSTANCE
    hIcon : Void*         # HICON
    hCursor : Void*       # HCURSOR
    hbrBackground : Void* # HBRUSH
    lpszMenuName : LPCWSTR
    lpszClassName : LPCWSTR
  end

  alias HWND = WNDCLASSW*
  alias WNDCLASS = WNDCLASSW
  alias ATOM = UInt16
  type WINBOOL = Int32

  struct POINT
    x, y : Int32
  end

  struct MSG
    hwnd : HWND
    message : UInt32
    wParam : WPARAM
    lParam : LPARAM
    time : Int32
    pt : POINT
  end

  alias LPMSG = MSG*
  @[CallConvention("X86_StdCall")]
  fun RegisterClassW(window_class : WNDCLASS*) : ATOM
  type HMENU = Void*
  type HINSTANCE = Void*
  type LPVOID = Void*
  @[CallConvention("X86_StdCall")]
  fun CreateWindowExW(
    dwExStyle : DWORD,
    lpClassName : LPCWSTR,
    lpWindowName : LPCWSTR,
    dwStyle : DWORD,
    x : Int32, y : Int32, nWidth : Int32, nHeight : Int32,
    hWndParent : HWND, hMenu : HMENU, hInstance : HINSTANCE, lpParam : LPVOID
  ) : HWND
  @[CallConvention("X86_StdCall")]
  fun GetMessageW(lpMsg : LPMSG, hWnd : HWND, wMsgFilterMin : UInt32, wMsgFilterMax : UInt32) : Int32
  @[CallConvention("X86_StdCall")]
  fun TranslateMessage(lpMsg : MSG*) : Int32
  @[CallConvention("X86_StdCall")]
  fun DispatchMessageW(lpMsg : MSG*) : Void*
  @[CallConvention("X86_StdCall")]
  fun ShowWindow(hWnd : HWND, nCmdShow : Int32) : Int32
  @[CallConvention("X86_StdCall")]
  struct RECT
    left : Int32
    top : Int32
    right : Int32
    bottom : Int32
  end

  type HDC = Void*

  struct PAINTSTRUCT
    hdc : HDC
    fErase : WINBOOL
    rcPaint : RECT
    fRestore : WINBOOL
    fIncUpdate : WINBOOL
    rgbReserved : BYTE[32]
  end

  alias LPPAINTSTRUCT = PAINTSTRUCT*
  @[CallConvention("X86_StdCall")]
  fun BeginPaint(hWnd : HWND, lpPaint : LPPAINTSTRUCT) : HDC
  type HBRUSH = Void*
  @[CallConvention("X86_StdCall")]
  fun FillRect(hDC : HDC, lprc : RECT*, hbr : HBRUSH) : Int32

  @[CallConvention("X86_StdCall")]
  fun EndPaint(hWnd : HWND, lpPaint : PAINTSTRUCT*) : WINBOOL
  @[CallConvention("X86_StdCall")]
  fun DefWindowProcW(hWnd : HWND, msg : UInt32, wParam : WPARAM, lParam : LPARAM) : LRESULT
  @[CallConvention("X86_StdCall")]
  fun PostQuitMessage(nExitCode : Int32) : Void
end

@[Link(ldflags: "/ENTRY:wWinMainCRTStartup")] # overrides previous `/ENTRY` setting
@[Link(ldflags: "/SUBSYSTEM:WINDOWS")]
lib LibCrystalMain
end

@[Link("gdi32")]
@[Link("kernel32")]
@[Link("User32")]
lib LibC
  fun CommandLineToArgvW(lpCmdLine : LPWSTR, pNumArgs : Int*) : LPWSTR*
  fun LocalFree(hMem : Void*) : Void*
end

@[CallConvention("X86_StdCall")]
fun wWinMain(hInstance : Void*, hPrevInstance : Void*, pCmdLine : UInt16*, nCmdShow : Int32) : Int32
  argv = LibC.CommandLineToArgvW(pCmdLine, out argc)
  status = wmain(argc, argv)
  return status
  LibC.LocalFree(argv)
end

module Crystal::System::FileDescriptor
  def self.from_stdio(fd)
    console_handle = false
    handle = LibC._get_osfhandle(fd)
    if handle != -1 && handle != -2 # standard streams are unavailable
      handle = LibC::HANDLE.new(handle)
      old_mode = uninitialized LibC::DWORD
      if LibC.GetConsoleMode(handle, pointerof(old_mode)) != 0
        console_handle = true
        if fd == 1 || fd == 2 # STDOUT or STDERR
          if LibC.SetConsoleMode(handle, old_mode | LibC::ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
            at_exit { LibC.SetConsoleMode(handle, old_mode) }
          end
        end
      end
    end

    # don't use `#system_info` to determine whether the descriptor should block,
    # as that raises because `fd` is invalid (non-blocking IO isn't supported yet anyway)
    io = IO::FileDescriptor.new(fd, blocking: true)
    if console_handle
      io.sync = true
    else
      io.flush_on_newline = true
    end
    io
  end
end

@[Link("User32")]
lib LibC
  fun MessageBoxW(hWnd : Void*, lpText : LPWSTR, lpCaption : LPWSTR, uType : UInt) : Int
end

module Crystal
  def self.exit(status : Int32, exception : Exception?) : Int32
    status = Crystal::AtExitHandlers.run status, exception

    if exception
      LibC.MessageBoxW(nil, exception.inspect_with_backtrace.to_utf16, "Unhandled exception".to_utf16, 0x10)
    end

    status
  end
end

hInstance = begin
  result = LibC.GetModuleHandleExW(LibC::GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, out hmodule)
  result != 0 ? hmodule : Pointer(Void).null
end.not_nil!

hPrevInstance = Pointer(Void).null

pCmdLine = LibC.GetCommandLineW

nCmdShow = begin
  LibC.GetStartupInfoW(out info)
  info.wShowWindow
end

CW_USEDEFAULT = Int32::MIN

WS_OVERLAPPEDWINDOW = 13565952

CLASS_NAME   = "Sample Window Class".to_utf16
WINDOW_TITLE = "Learn to Program Windows".to_utf16

make_log = ->(text : String) do
  LibC.MessageBoxW(nil, text.to_utf16, "winapp log".to_utf16, 0x10)
end

make_log.call "begin to create window class"

CS_HREDRAW = 1
CS_VREDRAW = 2

wc = LibC::WNDCLASSW.new
wc.lpfnWndProc = ->windowProc(LibC::HWND, UInt32, LibC::WPARAM, LibC::LPARAM).pointer.unsafe_as(LibC::WNDPROC).not_nil!
wc.hInstance = hInstance
wc.lpszClassName = CLASS_NAME
wc.style = CS_HREDRAW | CS_VREDRAW
if LibC.RegisterClassW(pointerof(wc)) == 0
  err = LibC.GetLastError
  raise "window class register failed because #{err}"
end
make_log.call "wc registerclass"
# Create the window.
hwnd = LibC.CreateWindowExW(
  0,          # Optional window styles
  CLASS_NAME, # Window class
  WINDOW_TITLE,
  WS_OVERLAPPEDWINDOW, # Window style
  # Size and position
  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  nil,                                  # Parent window
  nil,                                  # Menu
  hInstance.unsafe_as(LibC::HINSTANCE), # Instance handle
  nil                                   # Additional application data
)
make_log.call "hwnd created"
unless hwnd
  err = LibC.GetLastError
  raise "hwnd isn't valid because #{err}"
end

LibC.ShowWindow(hwnd, nCmdShow)
make_log.call "window shown"
# Run the message loop.
msg = LibC::MSG.new
while (LibC.GetMessageW(pointerof(msg), nil, 0, 0) > 0)
  make_log.call "get msg"
  LibC.TranslateMessage(pointerof(msg))
  LibC.DispatchMessageW(pointerof(msg))
end
make_log.call "return"

COLOR_WINDOW =   5
WM_DESTROY   =   2
WM_PAINT     = 0xF
@[CallConvention("X86_StdCall")]
def windowProc(hwnd : LibC::HWND, uMsg : UInt32, wParam : LibC::WPARAM, lParam : LibC::LPARAM) : LibC::LRESULT
  case (uMsg)
  when WM_DESTROY
    LibC.PostQuitMessage(0)
    return nil.as(Void*)
  when WM_PAINT
    ps = LibC::PAINTSTRUCT.new
    hdc = LibC.BeginPaint(hwnd, pointerof(ps))
    # All painting occurs here, between BeginPaint and EndPaint.
    psrcPaint = ps.rcPaint
    LibC.FillRect(hdc, pointerof(psrcPaint), (COLOR_WINDOW + 1).unsafe_as(LibC::HBRUSH))
    ps.rcPaint = psrcPaint
    LibC.EndPaint(hwnd, pointerof(ps))
    return nil.as(Void*)
  end
  return LibC.DefWindowProcW(hwnd, uMsg, wParam, lParam)
end

image

My views with playing with your code is that your wWinMain fun is not being called and hInstance is not properly set. You’re trying to override the entry point in the code but thats not being taken by the VS linker.

I think what needs to be done is that the file crystal/wmain.cr at master · crystal-lang/crystal · GitHub needs to change so that if a flag is set for subsystem to “WINDOWS” then it will alter the wmain function to be what you would need for a windows application. You can still pull the command line args with windows functions I believe.

I was able to get it to work. I started a library for WinAPI.

1 Like