Class: Win32::NIO

Inherits:
Object
  • Object
show all
Extended by:
Windows::Functions, Windows::Macros
Includes:
Windows::Constants, Windows::Structs
Defined in:
lib/win32/nio.rb

Overview

The NIO class encapsulates the native IO methods for MS Windows.

Constant Summary collapse

VERSION =

The version of the win32-nio library

'0.1.3'

Class Method Summary collapse

Methods included from Windows::Macros

HasOverlappedIoCompleted

Class Method Details

.read(name, length = nil, offset = 0, options = {}) ⇒ Object

This method is similar to Ruby’s IO.read method except that it uses native function calls.

Examples:

# Read everything Win32::NIO.read(file)

# Read the first 100 bytes Win32::NIO.read(file, 100)

# Read 50 bytes starting at offset 10 Win32::NIO.read(file, 50, 10)

Note that the options that may be passed to this method are limited to :encoding, :mode and :event because we’re no longer using the open function internally. In the case of :mode the only thing that is checked for is the presence of the ‘b’ (binary) mode.

The :event option, if present, must be a Win32::Event object. – In practice the fact that I ignore open_args: is irrelevant since you would never want to open in anything other than GENERIC_READ. I suppose I could change this to as a way to pass flags to CreateFile.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/win32/nio.rb', line 47

def self.read(name, length=nil, offset=0, options={})
  begin
    fname = name + "\0"
    fname.encode!('UTF-16LE')

    flags = FILE_FLAG_SEQUENTIAL_SCAN
    olap  = Overlapped.new
    event = options[:event]

    if event
      raise TypeError unless event.is_a?(Win32::Event)
    end

    olap[:Offset] = offset

    if offset > 0 || event
      flags |= FILE_FLAG_OVERLAPPED
      olap[:hEvent] = event.handle if event
    end

    handle = CreateFileW(
      fname,
      GENERIC_READ,
      FILE_SHARE_READ,
      nil,
      OPEN_EXISTING,
      flags,
      0
    )

    if handle == INVALID_HANDLE_VALUE
      raise SystemCallError.new("CreateFile", FFI.errno)
    end

    length ||= File.size(name)
    buf  = 0.chr * length

    if block_given?
      callback = Proc.new{ |e,b,o| block.call }
      bool = ReadFileEx(handle, buf, buf.size, olap, callback)
    else
      bool = ReadFile(handle, buf, buf.size, nil, olap)
    end

    errno = FFI.errno

    SleepEx(1, true) # Must be in alertable wait state

    unless bool
      if errno == ERROR_IO_PENDING
        bytes = FFI::MemoryPointer.new(:ulong)
        unless GetOverlappedResult(handle, olap, bytes, true)
          raise SystemCallError.new("GetOverlappedResult", FFI.errno)
        end
      else
        raise SystemCallError.new("ReadFile", errno)
      end
    end

    result = buf.delete(0.chr)

    result.encode!(options[:encoding]) if options[:encoding]

    if options[:mode] && options[:mode].include?('t') && ($/ != "\r\n")
      result.gsub!(/\r\n/, $/)
    end

    result
  ensure
    CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
  end
end

.readlines(file, sep = "\r\n") ⇒ Object

Reads the entire file specified by portname as individual lines, and returns those lines in an array. Lines are separated by sep. – The semantics are the same as the MRI version but the implementation is drastically different. We use a scattered IO read.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/win32/nio.rb', line 126

def self.readlines(file, sep = "\r\n")
  fname = file + "\0"
  fname.encode!('UTF-16LE')

  begin
    handle = CreateFileW(
      fname,
      GENERIC_READ,
      FILE_SHARE_READ,
      nil,
      OPEN_EXISTING,
      FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
      0
    )

    if handle == INVALID_HANDLE_VALUE
      raise SystemCallError.new("CreateFileW", FFI.errno)
    end

    sysinfo = SystemInfo.new
    GetSystemInfo(sysinfo)

    file_size = File.size(file)
    page_size = sysinfo[:dwPageSize]
    page_num  = (file_size.to_f / page_size).ceil

    begin
      size = page_size * page_num
      base_address = VirtualAlloc(nil, size, MEM_COMMIT, PAGE_READWRITE)

      if base_address == 0
        raise SystemCallError.new("VirtualAlloc", FFI.errno)
      end

      # Add 1 for null as per the docs
      array = FFI::MemoryPointer.new(FileSegmentElement, page_num + 1)

      for i in 0...page_num
        fse = FileSegmentElement.new(array[i])
        fse[:Alignment] = base_address + page_size * i
      end

      overlapped = Overlapped.new

      bool = ReadFileScatter(handle, array, size, nil, overlapped)

      unless bool
        error = FFI.errno
        if error == ERROR_IO_PENDING
          SleepEx(1, true) while !HasOverlappedIoCompleted(overlapped)
        else
          raise SystemCallError.new("ReadFileScatter", error)
        end
      end

      string = array[0].read_pointer.read_string

      if sep == ""
        array = string.split(/(\r\n){2,}/)
        array.delete("\r\n")
      else
        array = string.split(sep)
      end

      array
    ensure
      VirtualFree(base_address, 0, MEM_RELEASE)
    end
  ensure
    CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
  end
end