win32-nio 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +4 -0
- data/MANIFEST +5 -1
- data/README +4 -5
- data/Rakefile +23 -10
- data/lib/win32/nio.rb +139 -176
- data/lib/win32/windows/constants.rb +72 -0
- data/lib/win32/windows/functions.rb +23 -0
- data/lib/win32/windows/macros.rb +7 -0
- data/lib/win32/windows/structs.rb +39 -0
- data/test/test_win32_nio_read.rb +67 -75
- data/test/test_win32_nio_readlines.rb +55 -50
- data/win32-nio.gemspec +7 -7
- metadata +75 -76
data/CHANGES
CHANGED
data/MANIFEST
CHANGED
@@ -5,5 +5,9 @@
|
|
5
5
|
* win32-nio.gemspec
|
6
6
|
* benchmarks/win32_nio_benchmarks.rb
|
7
7
|
* lib/win32/nio.rb
|
8
|
+
* lib/windows/constants.rb
|
9
|
+
* lib/windows/functions.rb
|
10
|
+
* lib/windows/macros.rb
|
11
|
+
* lib/windows/structs.rb
|
8
12
|
* test/test_win32_nio_read.rb
|
9
|
-
* test/test_win32_nio_readlines.rb
|
13
|
+
* test/test_win32_nio_readlines.rb
|
data/README
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Description
|
2
|
-
|
3
|
-
|
2
|
+
Native IO for Windows. This class matches and extends the current Ruby
|
3
|
+
IO class API, using native Windows functions underneath the hood.
|
4
4
|
|
5
5
|
= Installation
|
6
6
|
gem install win32-nio
|
@@ -22,7 +22,7 @@
|
|
22
22
|
an event or a block to the NIO.read method, it offers little practical
|
23
23
|
advantage at the moment.
|
24
24
|
|
25
|
-
In addition, it is currently using pure Ruby (via
|
25
|
+
In addition, it is currently using pure Ruby (via FFI), which means
|
26
26
|
it isn't optimized for speed.
|
27
27
|
|
28
28
|
= Benchmarks
|
@@ -60,7 +60,7 @@
|
|
60
60
|
Artistic 2.0
|
61
61
|
|
62
62
|
= Copyright
|
63
|
-
(C) 2008-
|
63
|
+
(C) 2008-2012 Daniel J. Berger, All Rights Reserved
|
64
64
|
|
65
65
|
= Warranty
|
66
66
|
This package is provided "as is" and without any express or
|
@@ -70,4 +70,3 @@
|
|
70
70
|
= Author(s)
|
71
71
|
Daniel Berger
|
72
72
|
Park Heesob
|
73
|
-
|
data/Rakefile
CHANGED
@@ -1,14 +1,10 @@
|
|
1
1
|
require 'rake'
|
2
|
+
require 'rake/clean'
|
2
3
|
require 'rake/testtask'
|
3
|
-
require 'rbconfig'
|
4
|
-
include Config
|
5
4
|
|
6
|
-
|
7
|
-
desc 'Remove any .gem files from the project.'
|
8
|
-
task :clean do
|
9
|
-
Dir['*.gem'].each{ |f| File.delete(f) }
|
10
|
-
end
|
5
|
+
CLEAN.include("**/*.gem", "**/*.txt")
|
11
6
|
|
7
|
+
namespace 'gem' do
|
12
8
|
desc 'Create the win32-nio gem'
|
13
9
|
task :create => [:clean] do
|
14
10
|
spec = eval(IO.read('win32-nio.gemspec'))
|
@@ -27,7 +23,24 @@ task :bench do
|
|
27
23
|
sh "ruby -Ilib benchmarks/win32_nio_benchmarks.rb"
|
28
24
|
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
namespace :test do
|
27
|
+
Rake::TestTask.new(:read) do |t|
|
28
|
+
t.verbose = true
|
29
|
+
t.warning = true
|
30
|
+
t.test_files = FileList['test/test_win32_nio_read.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::TestTask.new(:readlines) do |t|
|
34
|
+
t.verbose = true
|
35
|
+
t.warning = true
|
36
|
+
t.test_files = FileList['test/test_win32_nio_readlines.rb']
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::TestTask.new(:all) do |t|
|
40
|
+
t.verbose = true
|
41
|
+
t.warning = true
|
42
|
+
t.test_files = FileList['test/test*.rb']
|
43
|
+
end
|
33
44
|
end
|
45
|
+
|
46
|
+
task :default => 'test:all'
|
data/lib/win32/nio.rb
CHANGED
@@ -1,56 +1,26 @@
|
|
1
|
-
require '
|
2
|
-
require 'windows/handle'
|
3
|
-
require 'windows/error'
|
4
|
-
require 'windows/memory'
|
5
|
-
require 'windows/nio'
|
6
|
-
require 'windows/synchronize'
|
7
|
-
require 'windows/system_info'
|
8
|
-
require 'windows/thread'
|
9
|
-
require 'windows/msvcrt/io'
|
10
|
-
require 'windows/msvcrt/buffer'
|
1
|
+
require 'ffi'
|
11
2
|
require 'win32/event'
|
12
3
|
|
4
|
+
require File.join(File.dirname(__FILE__), 'windows/functions')
|
5
|
+
require File.join(File.dirname(__FILE__), 'windows/constants')
|
6
|
+
require File.join(File.dirname(__FILE__), 'windows/structs')
|
7
|
+
require File.join(File.dirname(__FILE__), 'windows/macros')
|
8
|
+
|
13
9
|
# The Win32 module serves as a namespace only.
|
14
10
|
module Win32
|
15
|
-
|
11
|
+
|
16
12
|
# The NIO class encapsulates the native IO methods for MS Windows.
|
17
13
|
class NIO
|
18
|
-
include Windows::
|
19
|
-
include Windows::
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
include Windows::MSVCRT::Buffer
|
24
|
-
include Windows::SystemInfo
|
25
|
-
include Windows::Memory
|
26
|
-
include Windows::NIO
|
27
|
-
include Windows::Thread
|
28
|
-
|
29
|
-
extend Windows::File
|
30
|
-
extend Windows::Handle
|
31
|
-
extend Windows::Error
|
32
|
-
extend Windows::Synchronize
|
33
|
-
extend Windows::MSVCRT::IO
|
34
|
-
extend Windows::MSVCRT::Buffer
|
35
|
-
extend Windows::SystemInfo
|
36
|
-
extend Windows::Memory
|
37
|
-
extend Windows::NIO
|
38
|
-
extend Windows::Thread
|
39
|
-
|
14
|
+
include Windows::Constants
|
15
|
+
include Windows::Structs
|
16
|
+
extend Windows::Functions
|
17
|
+
extend Windows::Macros
|
18
|
+
|
40
19
|
# The version of the win32-nio library
|
41
|
-
VERSION = '0.0
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
# This method is similar to Ruby's IO.read method except that, in
|
47
|
-
# addition to using native function calls, it accepts an optional +event+
|
48
|
-
# argument for the fourth argument, which must be an instance of
|
49
|
-
# Win32::Event (if provided). The event is automatically set to a
|
50
|
-
# signaled state when the read operation completes.
|
51
|
-
#
|
52
|
-
# If a block is provided, then it is treated as a callback that fires
|
53
|
-
# when the read operation is complete.
|
20
|
+
VERSION = '0.1.0'
|
21
|
+
|
22
|
+
# This method is similar to Ruby's IO.read method except that it uses
|
23
|
+
# native function calls.
|
54
24
|
#
|
55
25
|
# Examples:
|
56
26
|
#
|
@@ -63,94 +33,89 @@ module Win32
|
|
63
33
|
# # Read 50 bytes starting at offset 10
|
64
34
|
# Win32::NIO.read(file, 50, 10)
|
65
35
|
#
|
66
|
-
#
|
67
|
-
#
|
36
|
+
# Note that the +options+ that may be passed to this method are limited
|
37
|
+
# to :encoding, :mode and :event because we're no longer using the open
|
38
|
+
# function internally. In the case of :mode the only thing that is checked
|
39
|
+
# for is the presence of the 'b' (binary) mode.
|
68
40
|
#
|
69
|
-
#
|
70
|
-
|
71
|
-
#
|
72
|
-
#
|
41
|
+
# The :event option, if present, must be a Win32::Event object.
|
42
|
+
#--
|
43
|
+
# In practice the fact that I ignore open_args: is irrelevant since you
|
44
|
+
# would never want to open in anything other than GENERIC_READ. I suppose
|
45
|
+
# I could change this to as a way to pass flags to CreateFile.
|
73
46
|
#
|
74
|
-
def self.read(
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
47
|
+
def self.read(name, length=nil, offset=0, options={})
|
48
|
+
begin
|
49
|
+
fname = name + "\0"
|
50
|
+
fname.encode!('UTF-16LE')
|
79
51
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
52
|
+
flags = FILE_FLAG_SEQUENTIAL_SCAN
|
53
|
+
olap = Overlapped.new
|
54
|
+
event = options[:event]
|
84
55
|
|
85
|
-
|
86
|
-
|
87
|
-
|
56
|
+
if event
|
57
|
+
raise TypeError unless event.is_a?(Win32::Event)
|
58
|
+
end
|
88
59
|
|
89
|
-
|
60
|
+
olap[:Offset] = offset
|
90
61
|
|
91
|
-
|
92
|
-
|
62
|
+
if offset > 0 || event
|
63
|
+
flags |= FILE_FLAG_OVERLAPPED
|
64
|
+
olap[:hEvent] = event.handle if event
|
65
|
+
end
|
93
66
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
67
|
+
handle = CreateFileW(
|
68
|
+
fname,
|
69
|
+
GENERIC_READ,
|
70
|
+
FILE_SHARE_READ,
|
71
|
+
nil,
|
72
|
+
OPEN_EXISTING,
|
73
|
+
flags,
|
74
|
+
0
|
75
|
+
)
|
98
76
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
FILE_SHARE_READ,
|
103
|
-
0,
|
104
|
-
OPEN_EXISTING,
|
105
|
-
flags,
|
106
|
-
0
|
107
|
-
)
|
108
|
-
|
109
|
-
if handle == INVALID_HANDLE_VALUE
|
110
|
-
raise Error, get_last_error
|
111
|
-
end
|
112
|
-
|
113
|
-
# Ruby's File.size is broken, so we implement it here. Also, if an
|
114
|
-
# offset is provided, we can reduce the size to only what we need.
|
115
|
-
if length.nil?
|
116
|
-
size = [0].pack('Q')
|
117
|
-
GetFileSizeEx(handle, size)
|
118
|
-
length = size.unpack('Q').first
|
119
|
-
length -= offset if offset
|
120
|
-
end
|
77
|
+
if handle == INVALID_HANDLE_VALUE
|
78
|
+
raise SystemCallError.new("CreateFile", FFI.errno)
|
79
|
+
end
|
121
80
|
|
122
|
-
|
81
|
+
length ||= File.size(name)
|
82
|
+
buf = 0.chr * length
|
123
83
|
|
124
|
-
begin
|
125
84
|
if block_given?
|
126
|
-
callback =
|
127
|
-
bool = ReadFileEx(handle, buf,
|
85
|
+
callback = Proc.new{ |e,b,o| block.call }
|
86
|
+
bool = ReadFileEx(handle, buf, buf.size, olap, callback)
|
128
87
|
else
|
129
|
-
|
130
|
-
bool = ReadFile(handle, buf, length, bytes, overlapped)
|
88
|
+
bool = ReadFile(handle, buf, buf.size, nil, olap)
|
131
89
|
end
|
132
|
-
|
133
|
-
errno =
|
90
|
+
|
91
|
+
errno = FFI.errno
|
134
92
|
|
135
93
|
SleepEx(1, true) # Must be in alertable wait state
|
136
|
-
|
94
|
+
|
137
95
|
unless bool
|
138
|
-
if errno
|
139
|
-
|
140
|
-
|
96
|
+
if errno == ERROR_IO_PENDING
|
97
|
+
bytes = FFI::MemoryPointer.new(:ulong)
|
98
|
+
unless GetOverlappedResult(handle, olap, bytes, true)
|
99
|
+
raise SystemCallError.new("GetOverlappedResult", FFI.errno)
|
141
100
|
end
|
142
101
|
else
|
143
|
-
raise
|
102
|
+
raise SystemCallError.new("ReadFile", errno)
|
144
103
|
end
|
145
104
|
end
|
146
|
-
|
147
|
-
|
105
|
+
|
106
|
+
result = buf.delete(0.chr)
|
107
|
+
|
108
|
+
result.encode!(options[:encoding]) if options[:encoding]
|
109
|
+
|
110
|
+
if options[:mode] && options[:mode].include?('t') && ($/ != "\r\n")
|
111
|
+
result.gsub!(/\r\n/, $/)
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
148
115
|
ensure
|
149
|
-
CloseHandle(handle)
|
116
|
+
CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
|
150
117
|
end
|
151
|
-
|
152
|
-
buf[0, length]
|
153
|
-
end
|
118
|
+
end # NIO.read
|
154
119
|
|
155
120
|
# Reads the entire file specified by portname as individual lines, and
|
156
121
|
# returns those lines in an array. Lines are separated by +sep+.
|
@@ -159,79 +124,77 @@ module Win32
|
|
159
124
|
# is drastically different. We use a scattered IO read.
|
160
125
|
#
|
161
126
|
def self.readlines(file, sep = "\r\n")
|
162
|
-
|
163
|
-
|
164
|
-
GENERIC_READ,
|
165
|
-
FILE_SHARE_READ,
|
166
|
-
nil,
|
167
|
-
OPEN_EXISTING,
|
168
|
-
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
|
169
|
-
nil
|
170
|
-
)
|
171
|
-
|
172
|
-
if handle == INVALID_HANDLE_VALUE
|
173
|
-
raise Error, get_last_error
|
174
|
-
end
|
175
|
-
|
176
|
-
sysbuf = 0.chr * 40
|
177
|
-
GetSystemInfo(sysbuf)
|
178
|
-
|
179
|
-
file_size = [0].pack('Q')
|
180
|
-
GetFileSizeEx(handle, file_size)
|
181
|
-
file_size = file_size.unpack('Q')[0]
|
127
|
+
fname = file + "\0"
|
128
|
+
fname.encode!('UTF-16LE')
|
182
129
|
|
183
|
-
page_size = sysbuf[4,4].unpack('L')[0] # dwPageSize
|
184
|
-
page_num = (file_size.to_f / page_size).ceil
|
185
|
-
|
186
130
|
begin
|
187
|
-
|
131
|
+
handle = CreateFileW(
|
132
|
+
fname,
|
133
|
+
GENERIC_READ,
|
134
|
+
FILE_SHARE_READ,
|
188
135
|
nil,
|
189
|
-
|
190
|
-
|
191
|
-
|
136
|
+
OPEN_EXISTING,
|
137
|
+
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
|
138
|
+
0
|
192
139
|
)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
for i in 0...page_num
|
197
|
-
buf_list.push(base_address + page_size * i)
|
140
|
+
|
141
|
+
if handle == INVALID_HANDLE_VALUE
|
142
|
+
raise SystemCallError.new("CreateFileW", FFI.errno)
|
198
143
|
end
|
199
|
-
|
200
|
-
seg_array = buf_list.pack('Q*') + 0.chr * 8
|
201
|
-
overlapped = 0.chr * 20
|
202
|
-
|
203
|
-
bool = ReadFileScatter(
|
204
|
-
handle,
|
205
|
-
seg_array,
|
206
|
-
page_size * page_num,
|
207
|
-
nil,
|
208
|
-
overlapped
|
209
|
-
)
|
210
144
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
145
|
+
sysinfo = SystemInfo.new
|
146
|
+
GetSystemInfo(sysinfo)
|
147
|
+
|
148
|
+
file_size = File.size(file)
|
149
|
+
page_size = sysinfo[:dwPageSize]
|
150
|
+
page_num = (file_size.to_f / page_size).ceil
|
151
|
+
|
152
|
+
begin
|
153
|
+
size = page_size * page_num
|
154
|
+
base_address = VirtualAlloc(nil, size, MEM_COMMIT, PAGE_READWRITE)
|
155
|
+
|
156
|
+
if base_address == 0
|
157
|
+
raise SystemCallError.new("VirtualAlloc", FFI.errno)
|
215
158
|
end
|
216
|
-
end
|
217
159
|
|
218
|
-
|
160
|
+
# Add 1 for null as per the docs
|
161
|
+
array = FFI::MemoryPointer.new(FileSegmentElement, page_num + 1)
|
219
162
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
163
|
+
for i in 0...page_num
|
164
|
+
fse = FileSegmentElement.new(array[i])
|
165
|
+
fse[:Alignment] = base_address + page_size * i
|
166
|
+
end
|
167
|
+
|
168
|
+
overlapped = Overlapped.new
|
169
|
+
|
170
|
+
bool = ReadFileScatter(handle, array, size, nil, overlapped)
|
171
|
+
|
172
|
+
unless bool
|
173
|
+
error = FFI.errno
|
174
|
+
if error == ERROR_IO_PENDING
|
175
|
+
SleepEx(1, true) while !HasOverlappedIoCompleted(overlapped)
|
176
|
+
else
|
177
|
+
raise SystemCallError.new("ReadFileScatter", error)
|
178
|
+
end
|
179
|
+
end
|
226
180
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
181
|
+
string = array[0].read_pointer.read_string
|
182
|
+
|
183
|
+
if sep == ""
|
184
|
+
array = string.split(/(\r\n){2,}/)
|
185
|
+
array.delete("\r\n")
|
186
|
+
else
|
187
|
+
array = string.split(sep)
|
188
|
+
end
|
189
|
+
|
190
|
+
array
|
191
|
+
ensure
|
192
|
+
VirtualFree(base_address, 0, MEM_RELEASE)
|
193
|
+
end
|
194
|
+
ensure
|
195
|
+
CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
|
232
196
|
end
|
197
|
+
end # NIO.readlines
|
233
198
|
|
234
|
-
|
235
|
-
|
236
|
-
end
|
237
|
-
end
|
199
|
+
end # NIO
|
200
|
+
end # Win32
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Windows
|
2
|
+
module Constants
|
3
|
+
INVALID_HANDLE_VALUE = 0xFFFFFFFF
|
4
|
+
|
5
|
+
CREATE_NEW = 1
|
6
|
+
CREATE_ALWAYS = 2
|
7
|
+
OPEN_EXISTING = 3
|
8
|
+
OPEN_ALWAYS = 4
|
9
|
+
TRUNCATE_EXISTING = 5
|
10
|
+
|
11
|
+
GENERIC_READ = 0x80000000
|
12
|
+
GENERIC_WRITE = 0x40000000
|
13
|
+
GENERIC_EXECUTE = 0x20000000
|
14
|
+
GENERIC_ALL = 0x10000000
|
15
|
+
|
16
|
+
FILE_SHARE_READ = 0x00000001
|
17
|
+
|
18
|
+
FILE_FLAG_WRITE_THROUGH = 0x80000000
|
19
|
+
FILE_FLAG_OVERLAPPED = 0x40000000
|
20
|
+
FILE_FLAG_NO_BUFFERING = 0x20000000
|
21
|
+
FILE_FLAG_RANDOM_ACCESS = 0x10000000
|
22
|
+
FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
|
23
|
+
FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
|
24
|
+
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
25
|
+
FILE_FLAG_POSIX_SEMANTICS = 0x01000000
|
26
|
+
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
27
|
+
FILE_FLAG_OPEN_NO_RECALL = 0x00100000
|
28
|
+
FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
|
29
|
+
|
30
|
+
MEM_COMMIT = 0x1000
|
31
|
+
MEM_RESERVE = 0x2000
|
32
|
+
MEM_DECOMMIT = 0x4000
|
33
|
+
MEM_RELEASE = 0x8000
|
34
|
+
MEM_FREE = 0x10000
|
35
|
+
MEM_PRIVATE = 0x20000
|
36
|
+
MEM_MAPPED = 0x40000
|
37
|
+
MEM_RESET = 0x80000
|
38
|
+
MEM_TOP_DOWN = 0x100000
|
39
|
+
MEM_WRITE_WATCH = 0x200000
|
40
|
+
MEM_PHYSICAL = 0x400000
|
41
|
+
MEM_LARGE_PAGES = 0x20000000
|
42
|
+
MEM_4MB_PAGES = 0x80000000
|
43
|
+
|
44
|
+
PAGE_NOACCESS = 0x01
|
45
|
+
PAGE_READONLY = 0x02
|
46
|
+
PAGE_READWRITE = 0x04
|
47
|
+
PAGE_WRITECOPY = 0x08
|
48
|
+
PAGE_EXECUTE = 0x10
|
49
|
+
PAGE_EXECUTE_READ = 0x20
|
50
|
+
PAGE_EXECUTE_READWRITE = 0x40
|
51
|
+
PAGE_EXECUTE_WRITECOPY = 0x80
|
52
|
+
PAGE_GUARD = 0x100
|
53
|
+
PAGE_NOCACHE = 0x200
|
54
|
+
PAGE_WRITECOMBINE = 0x400
|
55
|
+
|
56
|
+
INFINITE = 0xFFFFFFFF
|
57
|
+
WAIT_OBJECT_0 = 0
|
58
|
+
WAIT_TIMEOUT = 0x102
|
59
|
+
WAIT_ABANDONED = 128
|
60
|
+
WAIT_ABANDONED_0 = WAIT_ABANDONED
|
61
|
+
WAIT_FAILED = 0xFFFFFFFF
|
62
|
+
|
63
|
+
STATUS_WAIT_0 = 0
|
64
|
+
STATUS_ABANDONED_WAIT_0 = 128
|
65
|
+
STATUS_USER_APC = 192
|
66
|
+
STATUS_TIMEOUT = 258
|
67
|
+
STATUS_PENDING = 259
|
68
|
+
|
69
|
+
ERROR_IO_INCOMPLETE = 996
|
70
|
+
ERROR_IO_PENDING = 997
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Windows
|
4
|
+
module Functions
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib :kernel32
|
7
|
+
ffi_convention :stdcall
|
8
|
+
|
9
|
+
attach_function :CloseHandle, [:long], :bool
|
10
|
+
attach_function :CreateFileA, [:string, :ulong, :ulong, :pointer, :ulong, :ulong, :ulong], :ulong
|
11
|
+
attach_function :CreateFileW, [:buffer_in, :ulong, :ulong, :pointer, :ulong, :ulong, :ulong], :ulong
|
12
|
+
attach_function :GetOverlappedResult, [:ulong, :pointer, :pointer, :bool], :bool
|
13
|
+
attach_function :GetSystemInfo, [:pointer], :void
|
14
|
+
attach_function :ReadFile, [:ulong, :buffer_out, :ulong, :pointer, :pointer], :bool
|
15
|
+
attach_function :ReadFileScatter, [:ulong, :pointer, :ulong, :pointer, :pointer], :bool
|
16
|
+
attach_function :SleepEx, [:ulong, :bool], :ulong
|
17
|
+
attach_function :VirtualAlloc, [:pointer, :size_t, :ulong, :ulong], :ulong
|
18
|
+
attach_function :VirtualFree, [:ulong, :size_t, :ulong], :bool
|
19
|
+
|
20
|
+
callback :completion_function, [:ulong, :ulong, :pointer], :void
|
21
|
+
attach_function :ReadFileEx, [:ulong, :buffer_out, :ulong, :pointer, :completion_function], :bool
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Windows
|
4
|
+
module Structs
|
5
|
+
extend FFI::Library
|
6
|
+
|
7
|
+
# I'm assuming the anonymous struct for the internal union here.
|
8
|
+
class Overlapped < FFI::Struct
|
9
|
+
layout(
|
10
|
+
:Internal, :ulong,
|
11
|
+
:InternalHigh, :ulong,
|
12
|
+
:Offset, :ulong,
|
13
|
+
:OffsetHigh, :ulong,
|
14
|
+
:hEvent, :ulong
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# dwOemId is deprecated. Just assume the nested struct.
|
19
|
+
class SystemInfo < FFI::Struct
|
20
|
+
layout(
|
21
|
+
:wProcessorArchitecture, :ushort,
|
22
|
+
:wReserved, :ushort,
|
23
|
+
:dwPageSize, :ulong,
|
24
|
+
:lpMinimumApplicationAddress, :pointer,
|
25
|
+
:lpMaximumApplicationAddress, :pointer,
|
26
|
+
:dwActiveProcessorMask, :pointer,
|
27
|
+
:dwNumberOfProcessors, :ulong,
|
28
|
+
:dwProcessorType, :ulong,
|
29
|
+
:dwAllocationGranularity, :ulong,
|
30
|
+
:wProcessorLevel, :ushort,
|
31
|
+
:wProcessorRevision, :ushort
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
class FileSegmentElement < FFI::Union
|
36
|
+
layout(:Buffer, :pointer, :Alignment, :uint64)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/test_win32_nio_read.rb
CHANGED
@@ -1,90 +1,82 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
########################################################################
|
2
|
+
# test_win32_nio_read.rb
|
3
|
+
#
|
4
|
+
# Tests for the NIO.read method.
|
5
|
+
########################################################################
|
6
|
+
require 'test-unit'
|
4
7
|
require 'win32/nio'
|
5
8
|
require 'test/unit'
|
6
9
|
include Win32
|
7
10
|
|
8
11
|
class TC_Win32_NIO_Read < Test::Unit::TestCase
|
9
|
-
|
10
|
-
|
12
|
+
def self.startup
|
13
|
+
Dir.chdir(File.expand_path(File.dirname(__FILE__)))
|
14
|
+
|
15
|
+
@@file = 'read_test.txt'
|
16
|
+
@@text = "The quick brown fox jumped over the lazy dog's back"
|
17
|
+
|
18
|
+
File.open(@@file, 'w'){ |fh|
|
19
|
+
100.times{ |n| fh.puts @@text + ": #{n}" }
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup
|
24
|
+
@size = File.size(@@file)
|
25
|
+
end
|
26
|
+
|
27
|
+
test "version number is set to expected value" do
|
28
|
+
assert_equal('0.1.0', Win32::NIO::VERSION)
|
29
|
+
end
|
30
|
+
|
31
|
+
test "read method basic functionality" do
|
32
|
+
assert_respond_to(NIO, :read)
|
33
|
+
assert_nothing_raised{ NIO.read(@@file) }
|
34
|
+
end
|
11
35
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
}
|
19
|
-
}
|
20
|
-
end
|
36
|
+
test "read method accepts a file name and returns a string of the expected size" do
|
37
|
+
assert_kind_of(String, NIO.read(@@file))
|
38
|
+
p @size
|
39
|
+
p NIO.read(@@file).size
|
40
|
+
assert_true(NIO.read(@@file).size == @size)
|
41
|
+
end
|
21
42
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def test_nio_version
|
28
|
-
assert_equal('0.0.2', Win32::NIO::VERSION)
|
29
|
-
end
|
43
|
+
test "read method accepts a length argument and returns a string of that length" do
|
44
|
+
assert_nothing_raised{ NIO.read(@@file, 19) }
|
45
|
+
assert_equal('The quick brown fox', NIO.read(@@file, 19))
|
46
|
+
assert_equal('', NIO.read(@@file, 0))
|
47
|
+
end
|
30
48
|
|
31
|
-
|
32
|
-
|
33
|
-
|
49
|
+
test "read method accepts an offset and returns a string between offset and length" do
|
50
|
+
assert_nothing_raised{ NIO.read(@@file, 19, 4) }
|
51
|
+
assert_equal('quick brown fox', NIO.read(@@file, 15, 4))
|
52
|
+
assert_equal("lazy dog's back: 99\r\n", NIO.read(@@file, nil, @size-21))
|
53
|
+
end
|
34
54
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
assert_true(NIO.read(@@file).size == @size)
|
39
|
-
end
|
55
|
+
test "read method requires at least one argument" do
|
56
|
+
assert_raise(ArgumentError){ NIO.read }
|
57
|
+
end
|
40
58
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
59
|
+
test "length parameter must be a positive number" do
|
60
|
+
assert_raise(ArgumentError){ NIO.read(@@file, -1) }
|
61
|
+
assert_raise(TypeError){ NIO.read(@@file, 'foo') }
|
62
|
+
end
|
46
63
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
def test_nio_read_with_event
|
54
|
-
assert_false(@event.signaled?)
|
55
|
-
assert_nothing_raised{ NIO.read(@@file, 9, 0, @event) }
|
56
|
-
assert_true(@event.signaled?)
|
57
|
-
end
|
64
|
+
test "offset parameter must be a positive number" do
|
65
|
+
assert_raise(Errno::EINVAL, Errno::ENAMETOOLONG){ NIO.read(@@file, 1, -1) }
|
66
|
+
assert_raise(TypeError){ NIO.read(@@file, 1, 'foo') }
|
67
|
+
end
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
assert_raise(TypeError){ NIO.read(@@file, 'foo') }
|
63
|
-
assert_raise(ArgumentError){ NIO.read(@@file, 1, -1) }
|
64
|
-
assert_raise(TypeError){ NIO.read(@@file, 1, 'foo') }
|
65
|
-
assert_raise(TypeError){ NIO.read(@@file, 1, 1, 'foo') }
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_readlines_basic
|
69
|
-
assert_respond_to(NIO, :readlines)
|
70
|
-
assert_nothing_raised{ NIO.readlines(@@file) }
|
71
|
-
assert_kind_of(Array, NIO.readlines(@@file))
|
72
|
-
end
|
73
|
-
|
74
|
-
def test_readlines
|
75
|
-
assert_equal("#{@@text}: 0", NIO.readlines(@@file).first)
|
76
|
-
assert_equal("#{@@text}: 99", NIO.readlines(@@file).last)
|
77
|
-
end
|
69
|
+
test "options parameter must be a hash" do
|
70
|
+
assert_raise(TypeError){ NIO.read(@@file, 1, 1, 'foo') }
|
71
|
+
end
|
78
72
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
@event = nil
|
83
|
-
end
|
73
|
+
def teardown
|
74
|
+
@size = nil
|
75
|
+
end
|
84
76
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
77
|
+
def self.shutdown
|
78
|
+
File.delete(@@file) if File.exists?(@@file)
|
79
|
+
@@file = nil
|
80
|
+
@@text = nil
|
81
|
+
end
|
90
82
|
end
|
@@ -3,59 +3,64 @@
|
|
3
3
|
#
|
4
4
|
# Test case for the Win32::NIO.readlines method.
|
5
5
|
#######################################################################
|
6
|
-
require '
|
7
|
-
gem 'test-unit'
|
8
|
-
require 'test/unit'
|
6
|
+
require 'test-unit'
|
9
7
|
require 'win32/nio'
|
10
8
|
include Win32
|
11
9
|
|
12
10
|
class TC_Win32_NIO_Readlines < Test::Unit::TestCase
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
def self.startup
|
12
|
+
@@line = "The quick brown fox jumped over the lazy dog's back"
|
13
|
+
@@file = "readlines_test.txt"
|
14
|
+
@@size = 10
|
15
|
+
|
16
|
+
File.open(@@file, 'w'){ |fh|
|
17
|
+
1.upto(@@size){ |n|
|
18
|
+
fh.puts @@line + ": #{n}"
|
19
|
+
fh.puts if n % 3 == 0
|
22
20
|
}
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@array = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
test "readlines method basic functionality" do
|
29
|
+
assert_respond_to(NIO, :readlines)
|
30
|
+
assert_nothing_raised{ NIO.readlines(@@file) }
|
31
|
+
end
|
32
|
+
|
33
|
+
test "readlines returns an array" do
|
34
|
+
assert_kind_of(Array, NIO.readlines(@@file))
|
35
|
+
end
|
36
|
+
|
37
|
+
test "readlines returns an array of the expected size" do
|
38
|
+
assert_equal(@@size + 3, NIO.readlines(@@file).size)
|
39
|
+
assert_equal(@@line + ': 1', NIO.readlines(@@file).first)
|
40
|
+
end
|
41
|
+
|
42
|
+
test "readlines treats an empty second argument as a paragraph separator" do
|
43
|
+
assert_nothing_raised{ NIO.readlines(@@file, '') }
|
44
|
+
assert_kind_of(Array, NIO.readlines(@@file, ''))
|
45
|
+
assert_equal(4, NIO.readlines(@@file, '').size)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "readlines expects at least one argument" do
|
49
|
+
assert_raise(ArgumentError){ NIO.readlines }
|
50
|
+
end
|
51
|
+
|
52
|
+
test "readlines accepts a maximum of two arguments" do
|
53
|
+
assert_raise(ArgumentError){ NIO.readlines(@@file, '', true) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def teardown
|
57
|
+
@array = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.shutdown
|
61
|
+
File.delete(@@file) if File.exists?(@@file)
|
62
|
+
@@file = nil
|
63
|
+
@@size = nil
|
64
|
+
@@line = nil
|
65
|
+
end
|
61
66
|
end
|
data/win32-nio.gemspec
CHANGED
@@ -2,18 +2,22 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'win32-nio'
|
5
|
-
spec.version = '0.0
|
5
|
+
spec.version = '0.1.0'
|
6
6
|
spec.author = 'Daniel J. Berger'
|
7
7
|
spec.license = 'Artistic 2.0'
|
8
8
|
spec.email = 'djberg96@gmail.com'
|
9
9
|
spec.homepage = 'http://www.rubyforge.org/projects/win32utils'
|
10
|
-
spec.platform = Gem::Platform::RUBY
|
11
10
|
spec.summary = 'Native IO for MS Windows'
|
12
|
-
spec.has_rdoc = true
|
13
11
|
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
14
12
|
|
15
13
|
spec.rubyforge_project = 'Win32Utils'
|
16
14
|
spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
|
15
|
+
spec.required_ruby_version = '> 1.9.0'
|
16
|
+
|
17
|
+
spec.add_dependency('ffi')
|
18
|
+
spec.add_dependency('win32-event', '>= 0.6.0')
|
19
|
+
|
20
|
+
spec.add_development_dependency('test-unit')
|
17
21
|
|
18
22
|
spec.description = <<-EOF
|
19
23
|
The win32-nio library implements certain IO methods using native
|
@@ -21,8 +25,4 @@ Gem::Specification.new do |spec|
|
|
21
25
|
layer that MRI typically uses. In addition, some methods provide
|
22
26
|
additional event handling capability.
|
23
27
|
EOF
|
24
|
-
|
25
|
-
spec.add_dependency('windows-pr', '>= 0.9.5')
|
26
|
-
spec.add_dependency('win32-event', '>= 0.5.0')
|
27
|
-
spec.add_development_dependency('test-unit', '>= 2.0.3')
|
28
28
|
end
|
metadata
CHANGED
@@ -1,113 +1,112 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: win32-nio
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 3
|
9
|
-
version: 0.0.3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
7
|
+
authors:
|
12
8
|
- Daniel J. Berger
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
segments:
|
28
|
-
- 0
|
29
|
-
- 9
|
30
|
-
- 5
|
31
|
-
version: 0.9.5
|
12
|
+
date: 2012-07-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
32
22
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: win32-event
|
36
23
|
prerelease: false
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: win32-event
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.6.0
|
46
38
|
type: :runtime
|
47
|
-
version_requirements: *id002
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: test-unit
|
50
39
|
prerelease: false
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.6.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: test-unit
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
60
54
|
type: :development
|
61
|
-
|
62
|
-
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! " The win32-nio library implements certain IO methods using native\n
|
63
|
+
\ Windows function calls rather than using the POSIX compatibility\n layer
|
64
|
+
that MRI typically uses. In addition, some methods provide\n additional event
|
65
|
+
handling capability.\n"
|
63
66
|
email: djberg96@gmail.com
|
64
67
|
executables: []
|
65
|
-
|
66
68
|
extensions: []
|
67
|
-
|
68
|
-
extra_rdoc_files:
|
69
|
+
extra_rdoc_files:
|
69
70
|
- README
|
70
71
|
- CHANGES
|
71
72
|
- MANIFEST
|
72
|
-
files:
|
73
|
+
files:
|
73
74
|
- benchmarks/win32_nio_benchmarks.rb
|
74
75
|
- CHANGES
|
75
76
|
- lib/win32/nio.rb
|
77
|
+
- lib/win32/windows/constants.rb
|
78
|
+
- lib/win32/windows/functions.rb
|
79
|
+
- lib/win32/windows/macros.rb
|
80
|
+
- lib/win32/windows/structs.rb
|
76
81
|
- MANIFEST
|
77
82
|
- Rakefile
|
78
83
|
- README
|
79
84
|
- test/test_win32_nio_read.rb
|
80
85
|
- test/test_win32_nio_readlines.rb
|
81
86
|
- win32-nio.gemspec
|
82
|
-
has_rdoc: true
|
83
87
|
homepage: http://www.rubyforge.org/projects/win32utils
|
84
|
-
licenses:
|
88
|
+
licenses:
|
85
89
|
- Artistic 2.0
|
86
90
|
post_install_message:
|
87
91
|
rdoc_options: []
|
88
|
-
|
89
|
-
require_paths:
|
92
|
+
require_paths:
|
90
93
|
- lib
|
91
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
requirements:
|
100
|
-
- -
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
|
103
|
-
- 0
|
104
|
-
version: "0"
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>'
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.9.0
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
105
106
|
requirements: []
|
106
|
-
|
107
107
|
rubyforge_project: Win32Utils
|
108
|
-
rubygems_version: 1.
|
108
|
+
rubygems_version: 1.8.24
|
109
109
|
signing_key:
|
110
110
|
specification_version: 3
|
111
111
|
summary: Native IO for MS Windows
|
112
112
|
test_files: []
|
113
|
-
|