Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Win: define Win32 API functions #4832

Merged
merged 14 commits into from
Sep 8, 2017
1 change: 1 addition & 0 deletions src/compiler/crystal/semantic/flags.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Crystal::Program
set.add "freebsd" if set.any?(&.starts_with?("freebsd"))
set.add "openbsd" if set.any?(&.starts_with?("openbsd"))
set.add "unix" if set.any? { |flag| %w(cygnus darwin freebsd linux openbsd).includes?(flag) }
set.add "win32" if set.any?(&.starts_with?("windows")) && set.any? { |flag| %w(gnu msvc).includes?(flag) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the "windows" flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason is given in #4832 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't see that.


set.add "x86_64" if set.any?(&.starts_with?("amd64"))
set.add "i686" if set.any? { |flag| %w(i586 i486 i386).includes?(flag) }
Expand Down
42 changes: 42 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/errno.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
lib LibC
# source https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-doserrno-sys-errlist-and-sys-nerr
EPERM = 1
ENOENT = 2
ESRCH = 3
EINTR = 4
EIO = 5
ENXIO = 6
E2BIG = 7
ENOEXEC = 8
EBADF = 9
ECHILD = 10
EAGAIN = 11
ENOMEM = 12
EACCES = 13
EFAULT = 14
EBUSY = 16
EEXIST = 17
EXDEV = 18
ENODEV = 19
ENOTDIR = 20
EISDIR = 21
EINVAL = 22
ENFILE = 23
EMFILE = 24
ENOTTY = 25
EFBIG = 27
ENOSPC = 28
ESPIPE = 29
EROFS = 30
EMLINK = 31
EPIPE = 32
EDOM = 33
ERANGE = 34
EDEADLK = 36
ENAMETOOLONG = 38
ENOLCK = 39
ENOSYS = 40
ENOTEMPTY = 41
EILSEQ = 42
STRUNCATE = 80
end
241 changes: 241 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/winapi.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
lib LibC
STD_INPUT_HANDLE = 0xFFFFFFF6_u32
STD_OUTPUT_HANDLE = 0xFFFFFFF5_u32
STD_ERROR_HANDLE = 0xFFFFFFF4_u32

FILE_TYPE_UNKNOWN = 0x0000
FILE_TYPE_DISK = 0x0001
FILE_TYPE_CHAR = 0x0002
FILE_TYPE_PIPE = 0x0003
FILE_TYPE_REMOTE = 0x8000

GENERIC_EXECUTE = 0x20000000
GENERIC_WRITE = 0x40000000
GENERIC_READ = 0x80000000

CREATE_NEW = 1
CREATE_ALWAYS = 2
OPEN_EXISTING = 3
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5

FILE_FLAG_OVERLAPPED = 0x40000000

# source https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx
alias Long = Int32
alias Word = UInt16
alias WChar = UInt16
alias DWord = UInt32
alias Handle = Void*
alias SizeT = UInt64
alias BOOL = Int32 # FIXME maybe it need to be removed because it can be confused with Bool

INVALID_HANDLE_VALUE = Pointer(Void).new((-1).to_u64)
INFINITY = 0xFFFFFFFF_u32

fun _DuplicateHandle = DuplicateHandle(source_process : Handle, source : Handle, target_process : Handle, target : Handle*, desired_access : DWord, inherit_handle : Bool, options : DWord) : Bool

# member names are not original otherwise they would be really confusing
struct OVERLAPPED
status : SizeT
bytes_transfered : SizeT
offset : UInt64
event : Handle
end

struct SECURITY_ATTRIBUTES
nLength : DWord
lpSecurityDescriptor : Void*
bInheritHandle : BOOL
end

fun _GetStdHandle = GetStdHandle(std_handle : DWord) : Handle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused as to why we're adding underscores to symbol names which appear not to be underscored in the first place.

Copy link
Contributor

@ysbaddaden ysbaddaden Aug 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because a fun must follow the same rules than def even under a lib definition, and the identifier must start with a lowercase letter (a-z not A-Z). I'd love if this rule was relaxed, only for fun under lib definitions —it's quite common for C symbols to be "namespaced" with an uppercased prefix: LLVM, SDL2_, ... or to have camelcased functions as per the Win32 API.

Copy link
Contributor

@RX14 RX14 Aug 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find an underscore a really ugly way of solving this, so I think we should be able to define funs with arbitrary (within reason) symbol names. (impl note: top-level exported funs with bodies should support arbitrary symbol names too)

If we can't relax that requirement easily only for fun then we should define a simple reversible transform for mapping windows functions to crystal identifiers, such as FooBar -> foo_bar which is very easy to reverse if you need to look up docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could relax this rule, I have no problem with that, specially for lib functions. For class methods there's also no problem. The only problem is top level methods:

# This is valid in Ruby
def Foo(x)
end

# But this is always a generic instantiation in Crystal
Foo(String)

We still have time to relax this rule everywhere and change generics to use <...>. There's a bit of ambiguity if the parser finds Foo< and it doesn't know if it's a generic instantiation or a comparison, but we could make it so that Foo< (no space between type name and <) is always a generic instantiation, and problem solved.

For now, we can relax the rule for lib funs and relax the rule for method calls...

Copy link
Contributor

@RX14 RX14 Sep 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if if we only relaxed the rule for lib funs myself, I don't think there's a usecase for this outside external symbols, and I don't want to encourage confusing method casing for typical class maethods.

Really though I think we should just rename these methods and not change the compiler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'd prefer snake_case casing, since it feels more Crystal-ish, yet it might involve problems with fun name de/normalization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we allow any case for funs, shouldn't we allow any case for structs inside Lib?
And that will imply allowing that change in the type restrictions for the fun's arguments/return types.
If we aim to simplify binding generations I think that will be needed as well.

I thought there could be a problem with that if in the future we allow bindings to C++, since there will be a need for generic class inside the Lib. But it turns out that fun calls are written as LibA.Method(arg) and types would be LibA::Type(arg) so there is no problem here. As long as we keep not permitting an include LibA. Allowing include LibA would leave us flat in knowing if Foo(1).bar is a fun and method call or a class method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcardiff I was thinking exactly the same thing yesterday. This should be applicable to structs, and maybe even aliases like size_t. But it's true that for restrictions it's a bit tricky, though we could maybe parse restrictions differently inside lib declarations. This will not only make it easier to automatically generate bindings, but also simpler to copy C headers without having to translate size_t to SizeT, etc.

Since all these types have to be prefixed with a lib, like LibC::SizeT, I think we can introduce the rule for LibC::size_t parsing fine. And for restrictions inside lib declarations we can allow fun foo(x : size_t).

This also removes the confusion between Crystal's Int and libc's int, which we could just have as int.

Yesterday I also remembered we have @[External] so that crystal structs can be used in C libraries, so I thought maybe these should also have similar names, but since it's on Crystal's side I think it's fine if we require these to start with uppercase.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that this is a lot of unnecessary complexity just to allow us to use names from C unchanged. Is it really worth it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's almost trivial to implement and will simplify writing bindings. So why not?

fun _GetFileType = GetFileType(file : Handle) : DWord
fun _CreateFileA = CreateFileA(filename : UInt8*, access : DWord, sharemode : DWord, security_attributes : SECURITY_ATTRIBUTES*, creation : DWord, flags : DWord, template : Handle) : Handle
fun _ReadFile = ReadFile(file : Handle, buffer : UInt8*, size : DWord, read : DWord*, overlapped : OVERLAPPED*) : Bool
fun _WriteFile = WriteFile(file : Handle, buffer : UInt8*, size : DWord, written : DWord*, overlapped : OVERLAPPED*) : Bool
fun _CloseHandle = CloseHandle(file : Handle) : Bool

fun _GetCurrentDirectoryA = GetCurrentDirectoryA(size : DWord, buffer : UInt8*) : DWord
fun _SetCurrentDirectoryA = SetCurrentDirectoryA(path : UInt8*) : BOOL
fun _CreateDirectoryA = CreateDirectoryA(path : UInt8*, security_attribute : Void*) : BOOL
fun _RemoveDirectoryA = RemoveDirectoryA(path : UInt8*) : BOOL
fun _GetTempPathA = GetTempPathA(len : DWord, buffer : UInt8*) : DWord

MAX_PATH = 260

struct FILETIME
dwLowDateTime : DWord
dwHighDateTime : DWord
end

struct WIN32_FIND_DATA_A
dwFileAttributes : DWord
ftCreationTime : FILETIME
ftLastAccessTime : FILETIME
ftLastWriteTime : FILETIME
nFileSizeHigh : DWord
nFileSizeLow : DWord
dwReserved0 : DWord
dwReserved1 : DWord
cFileName : StaticArray(UInt8, MAX_PATH)
cAlternateFileName : StaticArray(UInt8, 14)
end

FILE_ATTRIBUTE_ARCHIVE = 32_i32
FILE_ATTRIBUTE_COMPRESSED = 2048_i32
FILE_ATTRIBUTE_NORMAL = 128_i32
FILE_ATTRIBUTE_DIRECTORY = 16_i32
FILE_ATTRIBUTE_HIDDEN = 2_i32
FILE_ATTRIBUTE_READONLY = 1_i32
FILE_ATTRIBUTE_REPARSE_POINT = 1024_i32
FILE_ATTRIBUTE_SYSTEM = 4_i32
FILE_ATTRIBUTE_TEMPORARY = 256_i32
INVALID_FILE_ATTRIBUTES = -1_i32

FILE_BEGIN = 0_i32
FILE_CURRENT = 1_i32
FILE_END = 2_i32
INVALID_SET_FILE_POINTER = (-1).to_u32

fun _FindFirstFileA = FindFirstFileA(fileName : UInt8*, filedata : WIN32_FIND_DATA_A*) : Handle
fun _FindNextFileA = FindNextFileA(file : Handle, filedata : WIN32_FIND_DATA_A*) : BOOL
fun _FindClose = FindClose(file : Handle) : BOOL
fun _GetFileAttributesA = GetFileAttributesA(filename : UInt8*) : DWord
fun _GetFileSize = GetFileSize(file : Handle, fileSizeHigh : DWord*) : DWord
fun _GetFileSizeEx = GetFileSizeEx(file : Handle, size : UInt64*) : BOOL
fun _GetFileTime = GetFileTime(file : Handle, lpCreationTime : FILETIME*, lpLastAccessTime : FILETIME*, lpLastWriteTime : FILETIME*) : BOOL
fun _SetFilePointer = SetFilePointer(file : Handle, lDistanceToMove : Long, lpDistanceToMoveHigh : Long*, dwMoveMethod : DWord) : DWord
fun _SetEndOfFile = SetEndOfFile(file : Handle) : BOOL
fun _DeleteFileA = DeleteFileA(filename : UInt8*) : BOOL
fun _GetFullPathNameA = GetFullPathNameA(filename : UInt8*, buf_len : DWord, lpBuffer : UInt8*, lpFilePart : UInt8**) : DWord
# from Shlwapi.lib
# fun _PathFileExistsA = PathFileExistsA(path : UInt8*) : BOOL
fun _MoveFileA = MoveFileA(lpExistingFileName : UInt8*, lpNewFileName : UInt8*) : BOOL
fun _GetTempFileNameA = GetTempFileNameA(path_name : UInt8*, prefix : UInt8*, unique_num : UInt32, temp_file_name : UInt8*) : UInt32

fun _CreateIoCompletionPort = CreateIoCompletionPort(file : Handle, port : Handle, data : Void*, threads : DWord) : Handle
fun _GetQueuedCompletionStatus = GetQueuedCompletionStatus(port : Handle, bytes_transfered : DWord*, data : Void**, entry : OVERLAPPED**, timeout_millis : DWord) : Bool
fun _PostQueuedCompletionStatus = PostQueuedCompletionStatus(port : Handle, bytes_transfered : DWord, data : Void*, entry : OVERLAPPED*) : Bool
fun _GetCurrentProcess = GetCurrentProcess : Handle
fun _GetCurrentThread = GetCurrentThread : Handle
fun _CreatePipe = CreatePipe(hReadPipe : UInt64*, hWritePipe : UInt64*, lpPipeAttributes : SECURITY_ATTRIBUTES*, nSize : DWord) : BOOL
fun _PeekNamedPipe = PeekNamedPipe(hNamedPipe : Handle, lpBuffer : UInt8*, nBufferSize : DWord, lpBytesRead : DWord*, lpTotalBytesAvail : DWord*, lpBytesLeftThisMessage : DWord*) : BOOL

WAIT_ABANDONED = 0x00000080_u32
WAIT_OBJECT_0 = 0x00000000_u32
WAIT_TIMEOUT = 0x00000102_u32
WAIT_FAILED = 0xFFFFFFFF_u32

fun _WaitForSingleObject = WaitForSingleObject(handle : Handle, timeout_millis : DWord) : DWord
fun _CreateTimerQueueTimer = CreateTimerQueueTimer(timer_handle : Handle*, queue_handle : Handle, callback : (Void*, Bool) ->, data : Void*, due : DWord, period : DWord, flags : SizeT) : Bool
fun _DeleteTimerQueueTimer = DeleteTimerQueueTimer(queue_handle : Handle, timer_handle : Handle, completion_event : Handle) : Bool
fun _GetLastError = GetLastError : DWord
fun _SetLastError = SetLastError(code : DWord) : Void

# STARTUPINFOA.deFlags
STARTF_USESTDHANDLES = 0x00000100_u32

# CreateProcessA.dwCreationFlags
NORMAL_PRIORITY_CLASS = 0x00000020_u32
CREATE_NO_WINDOW = 0x08000000_u32

struct STARTUPINFOA
cb : DWord
lpReserved : UInt8*
lpDesktop : UInt8*
lpTitle : UInt8*
dwX : DWord
dwY : DWord
dwXSize : DWord
dwYSize : DWord
dwXCountChars : DWord
dwYCountChars : DWord
dwFillAttribute : DWord
dwFlags : DWord
wShowWindow : Word
cbReserved2 : Word
lpReserved2 : UInt8*
hStdInput : Handle
hStdOutput : Handle
hStdError : Handle
end

struct PROCESS_INFORMATION
hProcess : Handle
hThread : Handle
dwProcessId : DWord
dwThreadId : DWord
end

fun _CreateProcessA = CreateProcessA(lpApplicationName : UInt8*, lpCommandLine : UInt8*,
lpProcessAttributes : SECURITY_ATTRIBUTES*, lpThreadAttributes : SECURITY_ATTRIBUTES*, bInheritHandles : BOOL,
dwCreationFlags : DWord, lpEnvironment : Void*, lpCurrentDirectory : UInt8*,
lpStartupInfo : STARTUPINFOA*, lpProcessInformation : PROCESS_INFORMATION*) : BOOL
fun _KillProcess = KillProcess(hProcess : Handle, uExitCode : UInt32) : BOOL
fun _GetExitCodeProcess = GetExitCodeProcess(hProcess : Handle, lpExitCode : DWord*) : BOOL

FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100_u32
FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200_u32
FORMAT_MESSAGE_FROM_STRING = 0x00000400_u32
FORMAT_MESSAGE_FROM_HMODULE = 0x00000800_u32
FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000_u32
FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000_u32

fun _FormatMessageA = FormatMessageA(flags : DWord, source : Void*, msg : DWord, lang : DWord, buffer : UInt8*, size : DWord, args : Void*) : DWord

WSASYSNOTREADY = 10091
WSAVERNOTSUPPORTED = 10092
WSAEINPROGRESS = 10036
WSAEPROCLIM = 10067
WSAEFAULT = 10014
WSAEINVAL = 10022

struct WSAData
wVersion : UInt16
wHighVersion : UInt16
szDescription : UInt8[257]
szSystemStatus : UInt8[129]
iMaxSockets : UInt16
iMaxUdpDg : UInt16
lpVendorInfo : UInt8*
end

fun _WSAStartup = WSAStartup(version : Int16, data : WSAData*) : Int32

# source https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx
struct SystemTime
wYear : Word
wMonth : Word
wDayOfWeek : Word
wDay : Word
wHour : Word
wMinute : Word
wSecond : Word
wMilliseconds : Word
end

# source https://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
struct TIME_ZONE_INFORMATION
_Bias : Long
_StandardName : StaticArray(WChar, 32)
_StandardDate : SYSTEMTIME
_StandardBias : Long
_DaylightName : StaticArray(WChar, 32)
_DaylightDate : SYSTEMTIME
_DaylightBias : Long
end

fun _GetTimeZoneInformation = GetTimeZoneInformation(tz_info : TIME_ZONE_INFORMATION*) : DWord
fun _GetSystemTimeAsFileTime = GetSystemTimeAsFileTime(time : FILETIME*)

fun _GetComputerNameA = GetComputerNameA(buffer : UInt8*, size : DWord*) : BOOL

fun _CxxThrowException = _CxxThrowException(exception_object : Void*, throw_info : Void*) : NoReturn
end

module WindowsExt
@[Primitive(:throw_info)]
def self.throw_info : Void*
end
end
Loading