win32-nio 0.1.3 → 0.2.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.
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