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

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