win32-nio 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8de13720111c1452f9e5a1f43fb0e2f8fc471ea7
4
- data.tar.gz: 134e23c2257adf6c650663fde6bf4fd44a1a1c7f
3
+ metadata.gz: 31fe6e337f6c93527d80360c231b9fb5766e2399
4
+ data.tar.gz: bac2e6d821bc01dd2814f8a09b749d476da7d74e
5
5
  SHA512:
6
- metadata.gz: 121d75dfb3d4fff1abbe238f6e6131e306813a38b92b7a36c5fd0f5634ec0c8a8d393861b34d204cfcd0f5771d161f381473e927338e03985a225e32873b8d21
7
- data.tar.gz: a3084ccaa518e24f132760a0e3c235f93b68c7ce077e51b551bd9fa15c846a2360594849be678f180195f99974c53ed34d10a57bee50a31f0d9c2423c97ce52e
6
+ metadata.gz: 2985ccd28d539b4433ebf2a163fe98567b596b965ef0259eccba7df26cdf670e6cf5a7cb102b58d7a4dfd3f3d04a52973f527248aba7d16687cd70efb7fe34af
7
+ data.tar.gz: eeae941454d811133a0d6cf7e84093a855fbfb992e633ce4e0a895140a6780ca75780f940dfda54cc5b82d0de2d7cc16a836a22bb1a029b359fe681811bedb33
data/CHANGES CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.2.0 - 30-Mar-2015
2
+ * Converted to C code for performance.
3
+ * The NIO.readlines method now accepts an optional event object as
4
+ an argument.
5
+ * A negative offset value will now raise an ArgumentError.
6
+ * Some documentation updates.
7
+
1
8
  == 0.1.3 - 21-Oct-2013
2
9
  * Fixed INVALID_HANDLE_VALUE for 64-bit versions of Ruby.
3
10
  * Added Rake as a development dependency.
data/README CHANGED
@@ -9,58 +9,71 @@
9
9
  require 'win32/nio'
10
10
  include Win32
11
11
 
12
+ # Similar to Ruby
12
13
  p NIO.read('some_file.text')
13
-
14
+ p NIO.read('some_file.text', 50)
15
+ p NIO.read('some_file.text', 50, 5)
16
+
14
17
  p NIO.readlines('some_file.txt')
15
-
16
- = Proof of Concept
17
- This code is ALPHA!
18
-
19
- This code is mostly a proof of concept to see how to implement Ruby's
20
- IO functions in pure Windows functions. While it does offer a couple of
21
- features over and above Ruby's IO interface, such as the ability to supply
22
- an event or a block to the NIO.read method, it offers little practical
23
- advantage at the moment.
24
-
25
- In addition, it is currently using pure Ruby (via FFI), which means
26
- it isn't optimized for speed.
18
+ p NIO.readlines('some_file.txt', '')
19
+
20
+ # With events
21
+ event = Win32::Event.new
22
+
23
+ NIO.read('some_big_file.txt', nil, nil, event)
24
+ p event.signaled? # => true
25
+
26
+ # With blocks
27
+ NIO.read('some_big_file.txt'){ puts "Finished" }
28
+
29
+ = Proof of Concept
30
+ This code was originally written to see if using pure Windows functions
31
+ to implement MRI methods would offer any practical advantage. The answer
32
+ is a definite maybe.
27
33
 
34
+ Functionally, these library does offer something the MRI methods do not,
35
+ which is the ability to provide a block or event object that is called
36
+ when a read is finished.
37
+
38
+ In terms of speed, I've found the NIO.read method to be almost twice as
39
+ fast as Ruby 2.2, while the NIO.readlines method was about the same speed.
40
+ In most cases the NIO.readlines method offers no practical advantage, but
41
+ see the documentation for details.
42
+
28
43
  = Benchmarks
29
44
 
30
- Using my HP Windows XP Pro laptop, with a 1.66 core duo Pentium T5500 and
31
- 2gb of RAM, I saw these results, which were typical in repeated runs:
45
+ Using my current laptop running Windows 7 I saw these results, which were
46
+ typical in repeated runs:
47
+
48
+ IO.read(small) 0.000000 0.016000 0.016000 ( 0.020342)
49
+ NIO.read(small) 0.000000 0.000000 0.000000 ( 0.008140)
50
+ IO.read(medium) 0.187000 0.047000 0.234000 ( 0.230579)
51
+ NIO.read(medium) 0.015000 0.093000 0.108000 ( 0.122542)
52
+ IO.read(large) 1.654000 0.593000 2.247000 ( 2.355478)
53
+ NIO.read(large) 0.343000 0.765000 1.108000 ( 1.222567)
54
+ IO.readlines(small) 0.125000 0.000000 0.125000 ( 0.119982)
55
+ NIO.readlines(small) 0.094000 0.015000 0.109000 ( 0.132461)
56
+ IO.readlines(medium) 1.419000 0.234000 1.653000 ( 1.764216)
57
+ NIO.readlines(medium) 1.092000 0.109000 1.201000 ( 1.537593)
58
+ IO.readlines(large) 12.714000 0.874000 13.588000 ( 13.798494)
59
+ NIO.readlines(large) 9.719000 0.468000 10.187000 ( 13.437217)
60
+
61
+ Your results may vary.
62
+
63
+ = JRuby
64
+ As of version 0.2.0 this code was written as a C extension, and
65
+ does not support JRuby. However, JRuby users can continue to use
66
+ the 0.1.x branch.
32
67
 
33
- user system total real
34
- IO.read(small) 0.016000 0.047000 0.063000 ( 0.063000)
35
- NIO.read(small) 0.109000 0.000000 0.109000 ( 0.109000)
36
- IO.read(medium) 0.422000 0.094000 0.516000 ( 0.532000)
37
- NIO.read(medium) 0.937000 0.062000 0.999000 ( 1.047000)
38
- IO.read(large) 3.282000 1.063000 4.345000 ( 4.468000)
39
- NIO.read(large) 8.187000 1.062000 9.249000 ( 9.454000)
40
- IO.readlines(small) 0.156000 0.063000 0.219000 ( 0.234000)
41
- NIO.readlines(small) 0.141000 0.000000 0.141000 ( 0.797000)
42
- IO.readlines(medium) 2.984000 0.250000 3.234000 ( 3.625000)
43
- NIO.readlines(medium) 1.172000 0.062000 1.234000 ( 3.406000)
44
- IO.readlines(large) 18.625000 2.281000 20.906000 ( 21.532000)
45
- NIO.readlines(large) 12.406000 0.563000 12.969000 (122.643000)
46
-
47
- Note that, in most cases, the user and system time has decreased, but
48
- the real time has increased.
49
-
50
68
  = Known Bugs
51
69
  None that I know of. Please log any other bug reports on the RubyForge
52
70
  project page at https://github.com/djberg96/win32-nio.
53
71
 
54
- = Future Plans
55
- The pure Ruby code is really only meant for prototyping. The eventual
56
- plan is to convert the Ruby code to equivalent C code in order to improve
57
- performance.
58
-
59
72
  = License
60
73
  Artistic 2.0
61
74
 
62
75
  = Copyright
63
- (C) 2008-2013 Daniel J. Berger, All Rights Reserved
76
+ (C) 2008-2015 Daniel J. Berger, All Rights Reserved
64
77
 
65
78
  = Warranty
66
79
  This package is provided "as is" and without any express or
data/Rakefile CHANGED
@@ -1,19 +1,44 @@
1
1
  require 'rake'
2
2
  require 'rake/clean'
3
3
  require 'rake/testtask'
4
+ include RbConfig
4
5
 
5
- CLEAN.include("**/*.gem", "**/*.txt")
6
+ CLEAN.include(
7
+ '**/*.gem', # Gem files
8
+ '**/*.rbc', # Rubinius
9
+ '**/*.o', # C object file
10
+ '**/*.log', # Ruby extension build log
11
+ '**/Makefile', # C Makefile
12
+ '**/*.def', # Definition files
13
+ '**/*.exp',
14
+ '**/*.lib',
15
+ '**/*.pdb',
16
+ '**/*.obj',
17
+ '**/*.stackdump', # Junk that can happen on Windows
18
+ "**/*.#{CONFIG['DLEXT']}" # C shared object
19
+ )
20
+
21
+ desc "Build the win32-nio library"
22
+ task :build => [:clean] do
23
+ if RbConfig::CONFIG['host_os'] =~ /mingw|cygwn/i
24
+ require 'devkit'
25
+ make_cmd = "make"
26
+ else
27
+ make_cmd = "nmake"
28
+ end
29
+ Dir.chdir('ext') do
30
+ ruby "extconf.rb"
31
+ sh make_cmd
32
+ cp 'nio.so', 'win32' # For testing
33
+ end
34
+ end
6
35
 
7
36
  namespace 'gem' do
8
37
  desc 'Create the win32-nio gem'
9
38
  task :create => [:clean] do
39
+ require 'rubygems/package'
10
40
  spec = eval(IO.read('win32-nio.gemspec'))
11
- if Gem::VERSION < "2.0.0"
12
- Gem::Builder.new(spec).build
13
- else
14
- require 'rubygems/package'
15
- Gem::Package.build(spec)
16
- end
41
+ Gem::Package.build(spec)
17
42
  end
18
43
 
19
44
  desc 'Install the win32-nio gem'
@@ -24,24 +49,30 @@ namespace 'gem' do
24
49
  end
25
50
 
26
51
  desc 'Run the benchmark suite'
27
- task :bench do
28
- sh "ruby -Ilib benchmarks/win32_nio_benchmarks.rb"
52
+ task :bench => [:build] do
53
+ sh "ruby -Iext benchmarks/win32_nio_benchmarks.rb"
29
54
  end
30
55
 
31
56
  namespace :test do
32
57
  Rake::TestTask.new(:read) do |t|
58
+ task :read => [:build]
59
+ t.libs << 'ext'
33
60
  t.verbose = true
34
61
  t.warning = true
35
62
  t.test_files = FileList['test/test_win32_nio_read.rb']
36
63
  end
37
64
 
38
65
  Rake::TestTask.new(:readlines) do |t|
66
+ task :readlines => [:build]
67
+ t.libs << 'ext'
39
68
  t.verbose = true
40
69
  t.warning = true
41
70
  t.test_files = FileList['test/test_win32_nio_readlines.rb']
42
71
  end
43
72
 
44
73
  Rake::TestTask.new(:all) do |t|
74
+ task :all => [:build]
75
+ t.libs << 'ext'
45
76
  t.verbose = true
46
77
  t.warning = true
47
78
  t.test_files = FileList['test/test*.rb']
data/ext/extconf.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('win32/nio', 'win32')
data/ext/win32/nio.c ADDED
@@ -0,0 +1,347 @@
1
+ #include <ruby.h>
2
+ #include <ruby/encoding.h>
3
+ #include <windows.h>
4
+ #include <math.h>
5
+
6
+ // Callback used when a block is provided to read method. It simply calls the block.
7
+ void CALLBACK read_complete(DWORD dwErrorCode, DWORD dwBytes, LPOVERLAPPED olap){
8
+ VALUE p = rb_block_proc();
9
+ rb_funcall(p, rb_intern("call"), 0);
10
+ }
11
+
12
+ // Helper function to raise system errors the way I would like
13
+ void rb_raise_syserr(const char* msg, int errnum){
14
+ VALUE v_sys = rb_funcall(rb_eSystemCallError, rb_intern("new"), 2, rb_str_new2(msg), INT2FIX(errnum));
15
+ rb_funcall(rb_mKernel, rb_intern("raise"), 1, v_sys);
16
+ }
17
+
18
+ /*
19
+ * This method is similar to Ruby's IO.read method except that it uses
20
+ * native function calls.
21
+ *
22
+ * Examples:
23
+ *
24
+ * # Read everything
25
+ * Win32::NIO.read(file)
26
+ *
27
+ * # Read the first 100 bytes
28
+ * Win32::NIO.read(file, 100)
29
+ *
30
+ * # Read 50 bytes starting at offset 10
31
+ * Win32::NIO.read(file, 50, 10)
32
+ *
33
+ * Note that the + options + that may be passed to this method are limited
34
+ * to :encoding, : mode and : event because we're no longer using the open
35
+ * function internally.In the case of:mode the only thing that is checked
36
+ * for is the presence of the 'b' (binary)mode.
37
+ *
38
+ * The :event option, if present, must be a Win32::Event object.
39
+ */
40
+ static VALUE rb_nio_read(int argc, VALUE* argv, VALUE self){
41
+ OVERLAPPED olap;
42
+ HANDLE h;
43
+ DWORD bytes_read;
44
+ LARGE_INTEGER lsize;
45
+ BOOL b;
46
+ VALUE v_file, v_length, v_offset, v_options;
47
+ VALUE v_event, v_mode, v_encoding, v_result;
48
+ size_t length;
49
+ int flags, error, size;
50
+ wchar_t* file = NULL;
51
+ char* buffer = NULL;
52
+
53
+ memset(&olap, 0, sizeof(olap));
54
+
55
+ // Paranoid initialization
56
+ v_length = Qnil;
57
+ v_offset = Qnil;
58
+ v_options = Qnil;
59
+
60
+ rb_scan_args(argc, argv, "13", &v_file, &v_length, &v_offset, &v_options);
61
+
62
+ // Allow path-y objects.
63
+ if (rb_respond_to(v_file, rb_intern("to_path")))
64
+ v_file = rb_funcall(v_file, rb_intern("to_path"), 0, NULL);
65
+
66
+ SafeStringValue(v_file);
67
+
68
+ // If a length is provided it cannot be negative.
69
+ if (!NIL_P(v_length)){
70
+ length = NUM2SIZET(v_length);
71
+ if ((int)length < 0)
72
+ rb_raise(rb_eArgError, "negative length %i given", (int)length);
73
+ }
74
+ else{
75
+ length = 0; // Mostly to make gcc stfu. We'll set this later.
76
+ }
77
+
78
+ // Unlike MRI, don't wait for an internal C function to fail on an invalid argument.
79
+ if (!NIL_P(v_offset)){
80
+ olap.Offset = NUM2ULONG(v_offset);
81
+ if ((int)olap.Offset < 0)
82
+ rb_raise(rb_eArgError, "negative offset %i given", (int)olap.Offset);
83
+ }
84
+
85
+ // Gotta do this for wide character function support.
86
+ size = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
87
+ file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
88
+
89
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, size)){
90
+ ruby_xfree(file);
91
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
92
+ }
93
+
94
+ flags = FILE_FLAG_SEQUENTIAL_SCAN;
95
+
96
+ // Possible options are :event, :mode and :encoding
97
+ if (!NIL_P(v_options)){
98
+ Check_Type(v_options, T_HASH);
99
+
100
+ v_event = rb_hash_aref(v_options, ID2SYM(rb_intern("event")));
101
+ v_encoding = rb_hash_aref(v_options, ID2SYM(rb_intern("encoding")));
102
+ v_mode = rb_hash_aref(v_options, ID2SYM(rb_intern("mode")));
103
+
104
+ if (!NIL_P(v_event)){
105
+ flags |= FILE_FLAG_OVERLAPPED;
106
+ olap.hEvent = (HANDLE)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
107
+ }
108
+ }
109
+ else{
110
+ v_event = Qnil;
111
+ v_encoding = Qnil;
112
+ v_mode = Qnil;
113
+ }
114
+
115
+ h = CreateFileW(
116
+ file,
117
+ GENERIC_READ,
118
+ FILE_SHARE_READ,
119
+ NULL,
120
+ OPEN_EXISTING,
121
+ flags,
122
+ NULL
123
+ );
124
+
125
+ if (h == INVALID_HANDLE_VALUE)
126
+ rb_raise_syserr("CreateFile", GetLastError());
127
+
128
+ // Get the file size. We may use this later to limit read length.
129
+ if (!GetFileSizeEx(h, &lsize)){
130
+ error = GetLastError();
131
+ CloseHandle(h);
132
+ rb_raise_syserr("GetFileSizeEx", error);
133
+ }
134
+
135
+ // If no length is specified, read the entire file
136
+ if (NIL_P(v_length))
137
+ length = (size_t)lsize.QuadPart;
138
+
139
+ // Don't read past the end of the file
140
+ if (olap.Offset + length > (size_t)lsize.QuadPart)
141
+ length = (size_t)lsize.QuadPart - olap.Offset;
142
+
143
+ buffer = (char*)ruby_xmalloc(length * sizeof(char));
144
+
145
+ // If a block is given then treat it as a callback
146
+ if (rb_block_given_p()){
147
+ flags |= FILE_FLAG_OVERLAPPED;
148
+ b = ReadFileEx(h, buffer, length, &olap, read_complete);
149
+ }
150
+ else{
151
+ b = ReadFile(h, buffer, length, &bytes_read, &olap);
152
+ }
153
+
154
+ error = GetLastError();
155
+
156
+ // Put in alertable wait state if overlapped IO
157
+ if (flags & FILE_FLAG_OVERLAPPED)
158
+ SleepEx(1, TRUE);
159
+
160
+ if (!b){
161
+ if(error == ERROR_IO_PENDING){
162
+ DWORD bytes;
163
+ if (!GetOverlappedResult(h, &olap, &bytes, TRUE)){
164
+ ruby_xfree(buffer);
165
+ CloseHandle(h);
166
+ rb_raise_syserr("GetOverlappedResult", error);
167
+ }
168
+ }
169
+ else{
170
+ ruby_xfree(buffer);
171
+ CloseHandle(h);
172
+ rb_raise_syserr("ReadFile", error);
173
+ }
174
+ }
175
+
176
+ CloseHandle(h);
177
+
178
+ v_result = rb_str_new(buffer, length);
179
+ ruby_xfree(buffer);
180
+
181
+ // Convert CRLF to LF if text mode
182
+ if (!NIL_P(v_mode) && strstr(RSTRING_PTR(v_mode), "t"))
183
+ rb_funcall(v_result, rb_intern("gsub!"), 2, rb_str_new2("\r\n"), rb_gv_get("$/"));
184
+
185
+ if (!NIL_P(v_encoding))
186
+ rb_funcall(v_result, rb_intern("encode!"), 1, v_encoding);
187
+
188
+ return v_result;
189
+ }
190
+
191
+ /*
192
+ * Reads the entire file specified by portname as individual lines, and
193
+ * returns those lines in an array. Lines are separated by +sep+.
194
+ *
195
+ * Examples:
196
+ *
197
+ * # Standard call
198
+ * Win32::NIO.readlines('file.txt') # => ['line 1', 'line 2', 'line 3', 'line 4']
199
+ *
200
+ * # Paragraph mode
201
+ * Win32::NIO.readlines('file.txt', '') # => ['line 1\r\nline 2', 'line 3\r\nline 4']
202
+ *
203
+ * # With event
204
+ * event = Win32::Event.new
205
+ * Win32::NIO.readlines('file.txt', nil, event)
206
+ * p event.signaled? # => true
207
+ *
208
+ * Superficially this method acts the same as the Ruby IO.readlines call, except that
209
+ * it does not transform line endings and accepts an optional event object. However,
210
+ * internally this method is using a scattered read to accomplish its goal. In practice
211
+ * this is only relevant in specific situations. Using it outside of those situations
212
+ * is unlikely to provide any practical benefit, and may even result in slower performance.
213
+ *
214
+ * See information on vectored IO for more details.
215
+ */
216
+ static VALUE rb_nio_readlines(int argc, VALUE* argv, VALUE self){
217
+ HANDLE h;
218
+ SYSTEM_INFO info;
219
+ LARGE_INTEGER file_size;
220
+ size_t length, page_size;
221
+ double size;
222
+ void* base_address;
223
+ int error, page_num;
224
+ wchar_t* file;
225
+ VALUE v_file, v_sep, v_event, v_result;
226
+
227
+ rb_scan_args(argc, argv, "12", &v_file, &v_sep, &v_event);
228
+
229
+ SafeStringValue(v_file);
230
+
231
+ if (NIL_P(v_sep))
232
+ v_sep = rb_str_new2("\r\n");
233
+ else
234
+ SafeStringValue(v_sep);
235
+
236
+ v_result = Qnil;
237
+
238
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
239
+ file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
240
+
241
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, length)){
242
+ ruby_xfree(file);
243
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
244
+ }
245
+
246
+ h = CreateFileW(
247
+ file,
248
+ GENERIC_READ,
249
+ FILE_SHARE_READ,
250
+ NULL,
251
+ OPEN_EXISTING,
252
+ FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
253
+ NULL
254
+ );
255
+
256
+ if (h == INVALID_HANDLE_VALUE)
257
+ rb_raise_syserr("CreateFile", GetLastError());
258
+
259
+ if (!GetFileSizeEx(h, &file_size)){
260
+ error = GetLastError();
261
+ CloseHandle(h);
262
+ rb_raise_syserr("GetFileSizeEx", error);
263
+ }
264
+
265
+ length = (size_t)file_size.QuadPart;
266
+
267
+ GetSystemInfo(&info);
268
+ page_size = info.dwPageSize;
269
+
270
+ page_num = (int)ceil((double)length / page_size);
271
+
272
+ size = page_num * page_size;
273
+
274
+ base_address = VirtualAlloc(NULL, (size_t)size, MEM_COMMIT, PAGE_READWRITE);
275
+
276
+ if (!base_address){
277
+ error = GetLastError();
278
+ CloseHandle(h);
279
+ rb_raise_syserr("VirtualAlloc", error);
280
+ }
281
+ else{
282
+ int i;
283
+ OVERLAPPED olap;
284
+ BOOL rv;
285
+ FILE_SEGMENT_ELEMENT* fse;
286
+
287
+ olap.Offset = 0;
288
+ olap.OffsetHigh = 0;
289
+
290
+ if (NIL_P(v_event))
291
+ olap.hEvent = NULL;
292
+ else
293
+ olap.hEvent = (HANDLE)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
294
+
295
+ fse = (FILE_SEGMENT_ELEMENT*)malloc(sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
296
+ memset(fse, 0, sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
297
+ v_result = Qnil;
298
+
299
+ for (i = 0; i < page_num; i++)
300
+ fse[i].Alignment = (ULONGLONG)base_address + (page_size * i);
301
+
302
+ rv = ReadFileScatter(h, fse, (DWORD)size, NULL, &olap);
303
+
304
+ if (!rv){
305
+ error = GetLastError();
306
+
307
+ if (error == ERROR_IO_PENDING){
308
+ while (!HasOverlappedIoCompleted(&olap))
309
+ SleepEx(1, TRUE);
310
+ }
311
+ else{
312
+ VirtualFree(base_address, 0, MEM_RELEASE);
313
+ CloseHandle(h);
314
+ rb_raise_syserr("ReadFileScatter", error);
315
+ }
316
+ }
317
+
318
+ // Explicitly handle paragraph mode
319
+ if (rb_equal(v_sep, rb_str_new2(""))){
320
+ VALUE v_args[1];
321
+ v_args[0] = rb_str_new2("(\r\n){2,}");
322
+ v_sep = rb_class_new_instance(1, v_args, rb_cRegexp);
323
+ v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
324
+ rb_funcall(v_result, rb_intern("delete"), 1, rb_str_new2("\r\n"));
325
+ }
326
+ else{
327
+ v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
328
+ }
329
+
330
+ VirtualFree(base_address, 0, MEM_RELEASE);
331
+ }
332
+
333
+ CloseHandle(h);
334
+
335
+ return v_result;
336
+ }
337
+
338
+ void Init_nio(){
339
+ VALUE mWin32 = rb_define_module("Win32");
340
+ VALUE cNio = rb_define_class_under(mWin32, "NIO", rb_cObject);
341
+
342
+ rb_define_singleton_method(cNio, "read", rb_nio_read, -1);
343
+ rb_define_singleton_method(cNio, "readlines", rb_nio_readlines, -1);
344
+
345
+ /* 0.2.0: The version of the win32-nio library */
346
+ rb_define_const(cNio, "VERSION", rb_str_new2("0.2.0"));
347
+ }
@@ -24,7 +24,7 @@ class TC_Win32_NIO_Read < Test::Unit::TestCase
24
24
  end
25
25
 
26
26
  test "version number is set to expected value" do
27
- assert_equal('0.1.3', Win32::NIO::VERSION)
27
+ assert_equal('0.2.0', Win32::NIO::VERSION)
28
28
  end
29
29
 
30
30
  test "read method basic functionality" do
@@ -61,7 +61,7 @@ class TC_Win32_NIO_Read < Test::Unit::TestCase
61
61
  end
62
62
 
63
63
  test "offset parameter must be a positive number" do
64
- assert_raise(Errno::EINVAL, Errno::ENAMETOOLONG){ NIO.read(@@file, 1, -1) }
64
+ assert_raise(ArgumentError){ NIO.read(@@file, 1, -1) }
65
65
  assert_raise(TypeError){ NIO.read(@@file, 1, 'foo') }
66
66
  end
67
67
 
@@ -74,7 +74,7 @@ class TC_Win32_NIO_Read < Test::Unit::TestCase
74
74
  end
75
75
 
76
76
  def self.shutdown
77
- File.delete(@@file) if File.exists?(@@file)
77
+ File.delete(@@file) if File.exist?(@@file)
78
78
  @@file = nil
79
79
  @@text = nil
80
80
  end
@@ -5,6 +5,7 @@
5
5
  #######################################################################
6
6
  require 'test-unit'
7
7
  require 'win32/nio'
8
+ require 'win32/event'
8
9
  include Win32
9
10
 
10
11
  class TC_Win32_NIO_Readlines < Test::Unit::TestCase
@@ -23,6 +24,7 @@ class TC_Win32_NIO_Readlines < Test::Unit::TestCase
23
24
 
24
25
  def setup
25
26
  @array = nil
27
+ @event = Win32::Event.new
26
28
  end
27
29
 
28
30
  test "readlines method basic functionality" do
@@ -45,20 +47,27 @@ class TC_Win32_NIO_Readlines < Test::Unit::TestCase
45
47
  assert_equal(4, NIO.readlines(@@file, '').size)
46
48
  end
47
49
 
50
+ test "readlines accepts an event object" do
51
+ assert_false(@event.signaled?)
52
+ assert_nothing_raised{ NIO.readlines(@@file, nil, @event) }
53
+ assert_true(@event.signaled?)
54
+ end
55
+
48
56
  test "readlines expects at least one argument" do
49
57
  assert_raise(ArgumentError){ NIO.readlines }
50
58
  end
51
59
 
52
- test "readlines accepts a maximum of two arguments" do
53
- assert_raise(ArgumentError){ NIO.readlines(@@file, '', true) }
60
+ test "readlines accepts a maximum of three arguments" do
61
+ assert_raise(ArgumentError){ NIO.readlines(@@file, '', @event, true) }
54
62
  end
55
63
 
56
64
  def teardown
57
65
  @array = nil
66
+ @event = nil
58
67
  end
59
68
 
60
69
  def self.shutdown
61
- File.delete(@@file) if File.exists?(@@file)
70
+ File.delete(@@file) if File.exist?(@@file)
62
71
  @@file = nil
63
72
  @@size = nil
64
73
  @@line = nil
data/win32-nio.gemspec CHANGED
@@ -1,20 +1,20 @@
1
1
  require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.name = 'win32-nio'
5
- spec.version = '0.1.3'
6
- spec.author = 'Daniel J. Berger'
7
- spec.license = 'Artistic 2.0'
8
- spec.email = 'djberg96@gmail.com'
9
- spec.homepage = 'https://github.com/djberg96/win32-nio'
10
- spec.summary = 'Native IO for MS Windows'
11
- spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
4
+ spec.name = 'win32-nio'
5
+ spec.version = '0.2.0'
6
+ spec.author = 'Daniel J. Berger'
7
+ spec.license = 'Artistic 2.0'
8
+ spec.email = 'djberg96@gmail.com'
9
+ spec.homepage = 'https://github.com/djberg96/win32-nio'
10
+ spec.summary = 'Native IO for MS Windows'
11
+ spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
12
+ spec.extensions = ['ext/extconf.rb']
12
13
 
13
14
  spec.rubyforge_project = 'Win32Utils'
14
15
  spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
15
16
  spec.required_ruby_version = '> 1.9.0'
16
17
 
17
- spec.add_dependency('ffi')
18
18
  spec.add_dependency('win32-event', '>= 0.6.0')
19
19
 
20
20
  spec.add_development_dependency('rake')
metadata CHANGED
@@ -1,69 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-nio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel J. Berger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-21 00:00:00.000000000 Z
11
+ date: 2015-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: ffi
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '>='
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: win32-event
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - '>='
17
+ - - ">="
32
18
  - !ruby/object:Gem::Version
33
19
  version: 0.6.0
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - '>='
24
+ - - ">="
39
25
  - !ruby/object:Gem::Version
40
26
  version: 0.6.0
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
- - - '>='
31
+ - - ">="
46
32
  - !ruby/object:Gem::Version
47
33
  version: '0'
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
- - - '>='
38
+ - - ">="
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: test-unit
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - '>='
45
+ - - ">="
60
46
  - !ruby/object:Gem::Version
61
47
  version: '0'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - '>='
52
+ - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '0'
69
55
  description: |2
@@ -73,22 +59,20 @@ description: |2
73
59
  additional event handling capability.
74
60
  email: djberg96@gmail.com
75
61
  executables: []
76
- extensions: []
62
+ extensions:
63
+ - ext/extconf.rb
77
64
  extra_rdoc_files:
78
65
  - README
79
66
  - CHANGES
80
67
  - MANIFEST
81
68
  files:
82
- - benchmarks/win32_nio_benchmarks.rb
83
69
  - CHANGES
84
- - lib/win32/nio.rb
85
- - lib/win32/windows/constants.rb
86
- - lib/win32/windows/functions.rb
87
- - lib/win32/windows/macros.rb
88
- - lib/win32/windows/structs.rb
89
70
  - MANIFEST
90
- - Rakefile
91
71
  - README
72
+ - Rakefile
73
+ - benchmarks/win32_nio_benchmarks.rb
74
+ - ext/extconf.rb
75
+ - ext/win32/nio.c
92
76
  - test/test_win32_nio_read.rb
93
77
  - test/test_win32_nio_readlines.rb
94
78
  - win32-nio.gemspec
@@ -102,17 +86,17 @@ require_paths:
102
86
  - lib
103
87
  required_ruby_version: !ruby/object:Gem::Requirement
104
88
  requirements:
105
- - - '>'
89
+ - - ">"
106
90
  - !ruby/object:Gem::Version
107
91
  version: 1.9.0
108
92
  required_rubygems_version: !ruby/object:Gem::Requirement
109
93
  requirements:
110
- - - '>='
94
+ - - ">="
111
95
  - !ruby/object:Gem::Version
112
96
  version: '0'
113
97
  requirements: []
114
98
  rubyforge_project: Win32Utils
115
- rubygems_version: 2.1.9
99
+ rubygems_version: 2.4.5
116
100
  signing_key:
117
101
  specification_version: 4
118
102
  summary: Native IO for MS Windows
data/lib/win32/nio.rb DELETED
@@ -1,200 +0,0 @@
1
- require 'ffi'
2
- require 'win32/event'
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
-
9
- # The Win32 module serves as a namespace only.
10
- module Win32
11
-
12
- # The NIO class encapsulates the native IO methods for MS Windows.
13
- class NIO
14
- include Windows::Constants
15
- include Windows::Structs
16
- extend Windows::Functions
17
- extend Windows::Macros
18
-
19
- # The version of the win32-nio library
20
- VERSION = '0.1.3'
21
-
22
- # This method is similar to Ruby's IO.read method except that it uses
23
- # native function calls.
24
- #
25
- # Examples:
26
- #
27
- # # Read everything
28
- # Win32::NIO.read(file)
29
- #
30
- # # Read the first 100 bytes
31
- # Win32::NIO.read(file, 100)
32
- #
33
- # # Read 50 bytes starting at offset 10
34
- # Win32::NIO.read(file, 50, 10)
35
- #
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.
40
- #
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.
46
- #
47
- def self.read(name, length=nil, offset=0, options={})
48
- begin
49
- fname = name + "\0"
50
- fname.encode!('UTF-16LE')
51
-
52
- flags = FILE_FLAG_SEQUENTIAL_SCAN
53
- olap = Overlapped.new
54
- event = options[:event]
55
-
56
- if event
57
- raise TypeError unless event.is_a?(Win32::Event)
58
- end
59
-
60
- olap[:Offset] = offset
61
-
62
- if offset > 0 || event
63
- flags |= FILE_FLAG_OVERLAPPED
64
- olap[:hEvent] = event.handle if event
65
- end
66
-
67
- handle = CreateFileW(
68
- fname,
69
- GENERIC_READ,
70
- FILE_SHARE_READ,
71
- nil,
72
- OPEN_EXISTING,
73
- flags,
74
- 0
75
- )
76
-
77
- if handle == INVALID_HANDLE_VALUE
78
- raise SystemCallError.new("CreateFile", FFI.errno)
79
- end
80
-
81
- length ||= File.size(name)
82
- buf = 0.chr * length
83
-
84
- if block_given?
85
- callback = Proc.new{ |e,b,o| block.call }
86
- bool = ReadFileEx(handle, buf, buf.size, olap, callback)
87
- else
88
- bool = ReadFile(handle, buf, buf.size, nil, olap)
89
- end
90
-
91
- errno = FFI.errno
92
-
93
- SleepEx(1, true) # Must be in alertable wait state
94
-
95
- unless bool
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)
100
- end
101
- else
102
- raise SystemCallError.new("ReadFile", errno)
103
- end
104
- end
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
115
- ensure
116
- CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE
117
- end
118
- end # NIO.read
119
-
120
- # Reads the entire file specified by portname as individual lines, and
121
- # returns those lines in an array. Lines are separated by +sep+.
122
- #--
123
- # The semantics are the same as the MRI version but the implementation
124
- # is drastically different. We use a scattered IO read.
125
- #
126
- def self.readlines(file, sep = "\r\n")
127
- fname = file + "\0"
128
- fname.encode!('UTF-16LE')
129
-
130
- begin
131
- handle = CreateFileW(
132
- fname,
133
- GENERIC_READ,
134
- FILE_SHARE_READ,
135
- nil,
136
- OPEN_EXISTING,
137
- FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
138
- 0
139
- )
140
-
141
- if handle == INVALID_HANDLE_VALUE
142
- raise SystemCallError.new("CreateFileW", FFI.errno)
143
- end
144
-
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)
158
- end
159
-
160
- # Add 1 for null as per the docs
161
- array = FFI::MemoryPointer.new(FileSegmentElement, page_num + 1)
162
-
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
180
-
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
196
- end
197
- end # NIO.readlines
198
-
199
- end # NIO
200
- end # Win32
@@ -1,77 +0,0 @@
1
- require 'ffi'
2
- module Windows
3
- module Constants
4
- include FFI::Library
5
-
6
- private
7
-
8
- INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address
9
-
10
- CREATE_NEW = 1
11
- CREATE_ALWAYS = 2
12
- OPEN_EXISTING = 3
13
- OPEN_ALWAYS = 4
14
- TRUNCATE_EXISTING = 5
15
-
16
- GENERIC_READ = 0x80000000
17
- GENERIC_WRITE = 0x40000000
18
- GENERIC_EXECUTE = 0x20000000
19
- GENERIC_ALL = 0x10000000
20
-
21
- FILE_SHARE_READ = 0x00000001
22
-
23
- FILE_FLAG_WRITE_THROUGH = 0x80000000
24
- FILE_FLAG_OVERLAPPED = 0x40000000
25
- FILE_FLAG_NO_BUFFERING = 0x20000000
26
- FILE_FLAG_RANDOM_ACCESS = 0x10000000
27
- FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
28
- FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
29
- FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
30
- FILE_FLAG_POSIX_SEMANTICS = 0x01000000
31
- FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
32
- FILE_FLAG_OPEN_NO_RECALL = 0x00100000
33
- FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000
34
-
35
- MEM_COMMIT = 0x1000
36
- MEM_RESERVE = 0x2000
37
- MEM_DECOMMIT = 0x4000
38
- MEM_RELEASE = 0x8000
39
- MEM_FREE = 0x10000
40
- MEM_PRIVATE = 0x20000
41
- MEM_MAPPED = 0x40000
42
- MEM_RESET = 0x80000
43
- MEM_TOP_DOWN = 0x100000
44
- MEM_WRITE_WATCH = 0x200000
45
- MEM_PHYSICAL = 0x400000
46
- MEM_LARGE_PAGES = 0x20000000
47
- MEM_4MB_PAGES = 0x80000000
48
-
49
- PAGE_NOACCESS = 0x01
50
- PAGE_READONLY = 0x02
51
- PAGE_READWRITE = 0x04
52
- PAGE_WRITECOPY = 0x08
53
- PAGE_EXECUTE = 0x10
54
- PAGE_EXECUTE_READ = 0x20
55
- PAGE_EXECUTE_READWRITE = 0x40
56
- PAGE_EXECUTE_WRITECOPY = 0x80
57
- PAGE_GUARD = 0x100
58
- PAGE_NOCACHE = 0x200
59
- PAGE_WRITECOMBINE = 0x400
60
-
61
- INFINITE = 0xFFFFFFFF
62
- WAIT_OBJECT_0 = 0
63
- WAIT_TIMEOUT = 0x102
64
- WAIT_ABANDONED = 128
65
- WAIT_ABANDONED_0 = WAIT_ABANDONED
66
- WAIT_FAILED = 0xFFFFFFFF
67
-
68
- STATUS_WAIT_0 = 0
69
- STATUS_ABANDONED_WAIT_0 = 128
70
- STATUS_USER_APC = 192
71
- STATUS_TIMEOUT = 258
72
- STATUS_PENDING = 259
73
-
74
- ERROR_IO_INCOMPLETE = 996
75
- ERROR_IO_PENDING = 997
76
- end
77
- end
@@ -1,27 +0,0 @@
1
- require 'ffi'
2
-
3
- module Windows
4
- module Functions
5
- extend FFI::Library
6
- typedef :ulong, :dword
7
- typedef :uintptr_t, :handle
8
- typedef :pointer, :ptr
9
-
10
- ffi_lib :kernel32
11
- ffi_convention :stdcall
12
-
13
- attach_function :CloseHandle, [:handle], :bool
14
- attach_function :CreateFileA, [:string, :dword, :dword, :ptr, :dword, :dword, :handle], :handle
15
- attach_function :CreateFileW, [:buffer_in, :dword, :dword, :ptr, :dword, :dword, :handle], :handle
16
- attach_function :GetOverlappedResult, [:handle, :ptr, :ptr, :bool], :bool
17
- attach_function :GetSystemInfo, [:ptr], :void
18
- attach_function :ReadFile, [:handle, :buffer_out, :dword, :ptr, :ptr], :bool
19
- attach_function :ReadFileScatter, [:handle, :ptr, :dword, :ptr, :ptr], :bool
20
- attach_function :SleepEx, [:dword, :bool], :dword
21
- attach_function :VirtualAlloc, [:ptr, :size_t, :dword, :dword], :dword
22
- attach_function :VirtualFree, [:dword, :size_t, :dword], :bool
23
-
24
- callback :completion_function, [:dword, :dword, :ptr], :void
25
- attach_function :ReadFileEx, [:handle, :buffer_out, :dword, :ptr, :completion_function], :bool
26
- end
27
- end
@@ -1,7 +0,0 @@
1
- module Windows
2
- module Macros
3
- def HasOverlappedIoCompleted(overlapped)
4
- overlapped[:Internal] != 259 # STATUS_PENDING
5
- end
6
- end
7
- end
@@ -1,39 +0,0 @@
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, :uintptr_t,
11
- :InternalHigh, :uintptr_t,
12
- :Offset, :ulong,
13
- :OffsetHigh, :ulong,
14
- :hEvent, :uintptr_t
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