win32-nio 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
|