win32-nio 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ version: '{build}'
2
+ branches:
3
+ only:
4
+ - C
5
+ skip_tags: true
6
+ clone_depth: 10
7
+ environment:
8
+ matrix:
9
+ - ruby_version: 200
10
+ ruby_dir: 2.0.0
11
+ - ruby_version: 200-x64
12
+ ruby_dir: 2.0.0
13
+ - ruby_version: 21
14
+ ruby_dir: 2.1.0
15
+ - ruby_version: 21-x64
16
+ ruby_dir: 2.1.0
17
+ - ruby_version: 22
18
+ ruby_dir: 2.2.0
19
+ - ruby_version: 22-x64
20
+ ruby_dir: 2.2.0
21
+ install:
22
+ - ps: >-
23
+ $env:path = "C:\Ruby" + $env:ruby_version + "\bin;" + $env:path
24
+
25
+ $tpath = "C:\Ruby" + $env:ruby_version + "\lib\ruby\" + $env:ruby_dir + "\test"
26
+
27
+ if ((test-path $tpath) -eq $True){ rm -recurse -force $tpath }
28
+
29
+ gem update --system > $null
30
+
31
+ if ((gem query -i win32-event) -eq $False){ gem install win32-event --no-document }
32
+
33
+ if ((gem query -i test-unit -v ">= 3.0") -eq $False){ gem install test-unit --no-document }
34
+
35
+ C:\Ruby21\DevKit\devkitvars.bat
36
+ cache:
37
+ - C:\Ruby200\lib\ruby\gems\2.0.0
38
+ - C:\Ruby200-x64\lib\ruby\gems\2.0.0
39
+ - C:\Ruby21\lib\ruby\gems\2.1.0
40
+ - C:\Ruby21-x64\lib\ruby\gems\2.1.0
41
+ - C:\Ruby22\lib\ruby\gems\2.2.0
42
+ - C:\Ruby22-x64\lib\ruby\gems\2.2.0
43
+ build: off
44
+ test_script:
45
+ - cmd: rake
@@ -1,113 +1,113 @@
1
- ########################################################################
2
- # win32_nio_benchmarks.rb
3
- #
4
- # Run this via the 'rake bench' task to compare win32-nio with Ruby's
5
- # own IO methods. This benchmark will take a bit of time, since it
6
- # generates some test files on the fly.
7
- ########################################################################
8
- require 'benchmark'
9
- require 'win32/nio'
10
- include Win32
11
-
12
- MAX = (ARGV[0] || '10').chomp.to_i # Default to 10 iterations
13
- PHRASE = "The quick brown fox jumped over the lazy dog's back"
14
-
15
- SMALL_FILE = "small_nio_test.txt" # 588k
16
- MEDIUM_FILE = "medium_nio_test.txt" # 6mb
17
- LARGE_FILE = "large_nio_test.txt" # 60mb
18
- HUGE_FILE = "huge_nio_test.txt" # 618mb
19
-
20
- unless File.exists?(SMALL_FILE)
21
- File.open(SMALL_FILE, 'w'){ |fh|
22
- 10000.times{ |n| fh.puts PHRASE + ": #{n}" }
23
- }
24
-
25
- puts "Small file created"
26
- end
27
-
28
- unless File.exists?(MEDIUM_FILE)
29
- File.open(MEDIUM_FILE, 'w'){ |fh|
30
- 110000.times{ |n| fh.puts PHRASE + ": #{n}" }
31
- }
32
-
33
- puts "Medium file created"
34
- end
35
-
36
- unless File.exists?(LARGE_FILE)
37
- File.open(LARGE_FILE, 'w'){ |fh|
38
- 1000000.times{ |n| fh.puts PHRASE + ": #{n}" }
39
- }
40
-
41
- puts "Large file created"
42
- end
43
-
44
- unless File.exists?(HUGE_FILE)
45
- #File.open(HUGE_FILE, 'w'){ |fh|
46
- # 10000000.times{ |n| fh.puts PHRASE + ": #{n}" }
47
- #}
48
-
49
- #puts "Huge file created"
50
- end
51
-
52
- Benchmark.bm(20) do |x|
53
- x.report('IO.read(small)'){
54
- MAX.times{ IO.read(SMALL_FILE) }
55
- }
56
-
57
- x.report('NIO.read(small)'){
58
- MAX.times{ NIO.read(SMALL_FILE) }
59
- }
60
-
61
- x.report('IO.read(medium)'){
62
- MAX.times{ IO.read(MEDIUM_FILE) }
63
- }
64
-
65
- x.report('NIO.read(medium)'){
66
- MAX.times{ NIO.read(MEDIUM_FILE) }
67
- }
68
-
69
- x.report('IO.read(large)'){
70
- MAX.times{ IO.read(LARGE_FILE) }
71
- }
72
-
73
- x.report('NIO.read(large)'){
74
- MAX.times{ NIO.read(LARGE_FILE) }
75
- }
76
-
77
- #x.report('IO.read(huge)'){
78
- # MAX.times{ IO.read(HUGE_FILE) }
79
- #}
80
-
81
- #x.report('NIO.read(huge)'){
82
- # MAX.times{ NIO.read(HUGE_FILE) }
83
- #}
84
-
85
- x.report('IO.readlines(small)'){
86
- MAX.times{ IO.readlines(SMALL_FILE) }
87
- }
88
-
89
- x.report('NIO.readlines(small)'){
90
- MAX.times{ NIO.readlines(SMALL_FILE) }
91
- }
92
-
93
- x.report('IO.readlines(medium)'){
94
- MAX.times{ IO.readlines(MEDIUM_FILE) }
95
- }
96
-
97
- x.report('NIO.readlines(medium)'){
98
- MAX.times{ NIO.readlines(MEDIUM_FILE) }
99
- }
100
-
101
- x.report('IO.readlines(large)'){
102
- MAX.times{ IO.readlines(LARGE_FILE) }
103
- }
104
-
105
- x.report('NIO.readlines(large)'){
106
- MAX.times{ NIO.readlines(LARGE_FILE) }
107
- }
108
- end
109
-
110
- File.delete(SMALL_FILE) if File.exists?(SMALL_FILE)
111
- File.delete(MEDIUM_FILE) if File.exists?(MEDIUM_FILE)
112
- File.delete(LARGE_FILE) if File.exists?(LARGE_FILE)
113
- File.delete(HUGE_FILE) if File.exists?(HUGE_FILE)
1
+ ########################################################################
2
+ # win32_nio_benchmarks.rb
3
+ #
4
+ # Run this via the 'rake bench' task to compare win32-nio with Ruby's
5
+ # own IO methods. This benchmark will take a bit of time, since it
6
+ # generates some test files on the fly.
7
+ ########################################################################
8
+ require 'benchmark'
9
+ require 'win32/nio'
10
+ include Win32
11
+
12
+ MAX = (ARGV[0] || '10').chomp.to_i # Default to 10 iterations
13
+ PHRASE = "The quick brown fox jumped over the lazy dog's back"
14
+
15
+ SMALL_FILE = "small_nio_test.txt" # 588k
16
+ MEDIUM_FILE = "medium_nio_test.txt" # 6mb
17
+ LARGE_FILE = "large_nio_test.txt" # 60mb
18
+ HUGE_FILE = "huge_nio_test.txt" # 618mb
19
+
20
+ unless File.exists?(SMALL_FILE)
21
+ File.open(SMALL_FILE, 'w'){ |fh|
22
+ 10000.times{ |n| fh.puts PHRASE + ": #{n}" }
23
+ }
24
+
25
+ puts "Small file created"
26
+ end
27
+
28
+ unless File.exists?(MEDIUM_FILE)
29
+ File.open(MEDIUM_FILE, 'w'){ |fh|
30
+ 110000.times{ |n| fh.puts PHRASE + ": #{n}" }
31
+ }
32
+
33
+ puts "Medium file created"
34
+ end
35
+
36
+ unless File.exists?(LARGE_FILE)
37
+ File.open(LARGE_FILE, 'w'){ |fh|
38
+ 1000000.times{ |n| fh.puts PHRASE + ": #{n}" }
39
+ }
40
+
41
+ puts "Large file created"
42
+ end
43
+
44
+ unless File.exists?(HUGE_FILE)
45
+ #File.open(HUGE_FILE, 'w'){ |fh|
46
+ # 10000000.times{ |n| fh.puts PHRASE + ": #{n}" }
47
+ #}
48
+
49
+ #puts "Huge file created"
50
+ end
51
+
52
+ Benchmark.bm(20) do |x|
53
+ x.report('IO.read(small)'){
54
+ MAX.times{ IO.read(SMALL_FILE) }
55
+ }
56
+
57
+ x.report('NIO.read(small)'){
58
+ MAX.times{ NIO.read(SMALL_FILE) }
59
+ }
60
+
61
+ x.report('IO.read(medium)'){
62
+ MAX.times{ IO.read(MEDIUM_FILE) }
63
+ }
64
+
65
+ x.report('NIO.read(medium)'){
66
+ MAX.times{ NIO.read(MEDIUM_FILE) }
67
+ }
68
+
69
+ x.report('IO.read(large)'){
70
+ MAX.times{ IO.read(LARGE_FILE) }
71
+ }
72
+
73
+ x.report('NIO.read(large)'){
74
+ MAX.times{ NIO.read(LARGE_FILE) }
75
+ }
76
+
77
+ #x.report('IO.read(huge)'){
78
+ # MAX.times{ IO.read(HUGE_FILE) }
79
+ #}
80
+
81
+ #x.report('NIO.read(huge)'){
82
+ # MAX.times{ NIO.read(HUGE_FILE) }
83
+ #}
84
+
85
+ x.report('IO.readlines(small)'){
86
+ MAX.times{ IO.readlines(SMALL_FILE) }
87
+ }
88
+
89
+ x.report('NIO.readlines(small)'){
90
+ MAX.times{ NIO.readlines(SMALL_FILE) }
91
+ }
92
+
93
+ x.report('IO.readlines(medium)'){
94
+ MAX.times{ IO.readlines(MEDIUM_FILE) }
95
+ }
96
+
97
+ x.report('NIO.readlines(medium)'){
98
+ MAX.times{ NIO.readlines(MEDIUM_FILE) }
99
+ }
100
+
101
+ x.report('IO.readlines(large)'){
102
+ MAX.times{ IO.readlines(LARGE_FILE) }
103
+ }
104
+
105
+ x.report('NIO.readlines(large)'){
106
+ MAX.times{ NIO.readlines(LARGE_FILE) }
107
+ }
108
+ end
109
+
110
+ File.delete(SMALL_FILE) if File.exists?(SMALL_FILE)
111
+ File.delete(MEDIUM_FILE) if File.exists?(MEDIUM_FILE)
112
+ File.delete(LARGE_FILE) if File.exists?(LARGE_FILE)
113
+ File.delete(HUGE_FILE) if File.exists?(HUGE_FILE)
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
+ Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
+ S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
+ gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
+ FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
+ zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
+ DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
+ nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
+ bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
+ ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
+ tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
+ /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
+ wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
+ EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
+ tGSHgAmcLlkdGgan182qsE/4kKM=
21
+ -----END CERTIFICATE-----
@@ -1,2 +1,2 @@
1
- require 'mkmf'
2
- create_makefile('win32/nio', 'win32')
1
+ require 'mkmf'
2
+ create_makefile('win32/nio', 'win32')
@@ -1,355 +1,355 @@
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
- * NIO.read(file, length=nil, offset=0, options=nil){ # optional block }
20
- *
21
- * This method is similar to Ruby's IO.read method except that it uses
22
- * native function calls. It also optionally accepts a Win32::Event object,
23
- * signalling it upon completion. If provided it calls a block upon completion
24
- * of the read.
25
- *
26
- * Examples:
27
- *
28
- * # Read everything
29
- * Win32::NIO.read(file)
30
- *
31
- * # Read the first 100 bytes
32
- * Win32::NIO.read(file, 100)
33
- *
34
- * # Read 50 bytes starting at offset 10
35
- * Win32::NIO.read(file, 50, 10)
36
- *
37
- * Note that the +options+ that may be passed to this method are limited
38
- * to :encoding, :mode and :event because we're no longer using the open
39
- * function internally. In the case of :mode the only thing that is checked
40
- * for is the presence of the 'b' (binary) mode.
41
- *
42
- * The :event option, if present, must be a Win32::Event object.
43
- */
44
- static VALUE rb_nio_read(int argc, VALUE* argv, VALUE self){
45
- OVERLAPPED olap;
46
- HANDLE h;
47
- DWORD bytes_read;
48
- LARGE_INTEGER lsize;
49
- BOOL b;
50
- VALUE v_file, v_length, v_offset, v_options;
51
- VALUE v_event, v_mode, v_encoding, v_result;
52
- size_t length;
53
- int flags, error, size;
54
- wchar_t* file = NULL;
55
- char* buffer = NULL;
56
-
57
- memset(&olap, 0, sizeof(olap));
58
-
59
- // Paranoid initialization
60
- v_length = Qnil;
61
- v_offset = Qnil;
62
- v_options = Qnil;
63
-
64
- rb_scan_args(argc, argv, "13", &v_file, &v_length, &v_offset, &v_options);
65
-
66
- // Allow path-y objects.
67
- if (rb_respond_to(v_file, rb_intern("to_path")))
68
- v_file = rb_funcall(v_file, rb_intern("to_path"), 0, NULL);
69
-
70
- SafeStringValue(v_file);
71
-
72
- // If a length is provided it cannot be negative.
73
- if (!NIL_P(v_length)){
74
- length = NUM2SIZET(v_length);
75
- if ((int)length < 0)
76
- rb_raise(rb_eArgError, "negative length %i given", (int)length);
77
- }
78
- else{
79
- length = 0; // Mostly to make gcc stfu. We'll set this later.
80
- }
81
-
82
- // Unlike MRI, don't wait for an internal C function to fail on an invalid argument.
83
- if (!NIL_P(v_offset)){
84
- olap.Offset = NUM2ULONG(v_offset);
85
- if ((int)olap.Offset < 0)
86
- rb_raise(rb_eArgError, "negative offset %i given", (int)olap.Offset);
87
- }
88
-
89
- // Gotta do this for wide character function support.
90
- size = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
91
- file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
92
-
93
- if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, size)){
94
- ruby_xfree(file);
95
- rb_raise_syserr("MultibyteToWideChar", GetLastError());
96
- }
97
-
98
- flags = FILE_FLAG_SEQUENTIAL_SCAN;
99
-
100
- // Possible options are :event, :mode and :encoding
101
- if (!NIL_P(v_options)){
102
- Check_Type(v_options, T_HASH);
103
-
104
- v_event = rb_hash_aref(v_options, ID2SYM(rb_intern("event")));
105
- v_encoding = rb_hash_aref(v_options, ID2SYM(rb_intern("encoding")));
106
- v_mode = rb_hash_aref(v_options, ID2SYM(rb_intern("mode")));
107
-
108
- if (!NIL_P(v_event)){
109
- flags |= FILE_FLAG_OVERLAPPED;
110
- olap.hEvent = (HANDLE)(uintptr_t)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
111
- }
112
- }
113
- else{
114
- v_event = Qnil;
115
- v_encoding = Qnil;
116
- v_mode = Qnil;
117
- }
118
-
119
- h = CreateFileW(
120
- file,
121
- GENERIC_READ,
122
- FILE_SHARE_READ,
123
- NULL,
124
- OPEN_EXISTING,
125
- flags,
126
- NULL
127
- );
128
-
129
- if (h == INVALID_HANDLE_VALUE)
130
- rb_raise_syserr("CreateFile", GetLastError());
131
-
132
- // Get the file size. We may use this later to limit read length.
133
- if (!GetFileSizeEx(h, &lsize)){
134
- error = GetLastError();
135
- CloseHandle(h);
136
- rb_raise_syserr("GetFileSizeEx", error);
137
- }
138
-
139
- // If no length is specified, read the entire file
140
- if (NIL_P(v_length))
141
- length = (size_t)lsize.QuadPart;
142
-
143
- // Don't read past the end of the file
144
- if (olap.Offset + length > (size_t)lsize.QuadPart)
145
- length = (size_t)lsize.QuadPart - olap.Offset;
146
-
147
- buffer = (char*)ruby_xmalloc(length * sizeof(char));
148
-
149
- // If a block is given then treat it as a callback
150
- if (rb_block_given_p()){
151
- flags |= FILE_FLAG_OVERLAPPED;
152
- b = ReadFileEx(h, buffer, length, &olap, read_complete);
153
- }
154
- else{
155
- b = ReadFile(h, buffer, length, &bytes_read, &olap);
156
- }
157
-
158
- error = GetLastError();
159
-
160
- // Put in alertable wait state if overlapped IO
161
- if (flags & FILE_FLAG_OVERLAPPED)
162
- SleepEx(1, TRUE);
163
-
164
- if (!b){
165
- if(error == ERROR_IO_PENDING){
166
- DWORD bytes;
167
- if (!GetOverlappedResult(h, &olap, &bytes, TRUE)){
168
- ruby_xfree(buffer);
169
- CloseHandle(h);
170
- rb_raise_syserr("GetOverlappedResult", error);
171
- }
172
- }
173
- else{
174
- ruby_xfree(buffer);
175
- CloseHandle(h);
176
- rb_raise_syserr("ReadFile", error);
177
- }
178
- }
179
-
180
- CloseHandle(h);
181
-
182
- v_result = rb_str_new(buffer, length);
183
- ruby_xfree(buffer);
184
-
185
- // Convert CRLF to LF if text mode
186
- if (!NIL_P(v_mode) && strstr(RSTRING_PTR(v_mode), "t"))
187
- rb_funcall(v_result, rb_intern("gsub!"), 2, rb_str_new2("\r\n"), rb_gv_get("$/"));
188
-
189
- if (!NIL_P(v_encoding))
190
- rb_funcall(v_result, rb_intern("encode!"), 1, v_encoding);
191
-
192
- return v_result;
193
- }
194
-
195
- /*
196
- * NIO.readlines(file, sep="\r\n", event=nil)
197
- *
198
- * Reads the entire file specified by portname as individual lines, and
199
- * returns those lines in an array. Lines are separated by +sep+. The +event+
200
- * argument must be a Win32::Event if present, and it is signalled upon
201
- * completion.
202
- *
203
- * Examples:
204
- *
205
- * # Standard call
206
- * Win32::NIO.readlines('file.txt') # => ['line 1', 'line 2', 'line 3', 'line 4']
207
- *
208
- * # Paragraph mode
209
- * Win32::NIO.readlines('file.txt', '') # => ['line 1\r\nline 2', 'line 3\r\nline 4']
210
- *
211
- * # With event
212
- * event = Win32::Event.new
213
- * Win32::NIO.readlines('file.txt', nil, event)
214
- * p event.signaled? # => true
215
- *
216
- * Superficially this method acts the same as the Ruby IO.readlines call, except that
217
- * it does not transform line endings and accepts an optional event object. However,
218
- * internally this method is using a scattered read to accomplish its goal. In practice
219
- * this is only relevant in specific situations. Using it outside of those situations
220
- * is unlikely to provide any practical benefit, and may even result in slower performance.
221
- *
222
- * See information on vectored IO for more details.
223
- */
224
- static VALUE rb_nio_readlines(int argc, VALUE* argv, VALUE self){
225
- HANDLE h;
226
- SYSTEM_INFO info;
227
- LARGE_INTEGER file_size;
228
- size_t length, page_size;
229
- double size;
230
- void* base_address;
231
- int error, page_num;
232
- wchar_t* file;
233
- VALUE v_file, v_sep, v_event, v_result;
234
-
235
- rb_scan_args(argc, argv, "12", &v_file, &v_sep, &v_event);
236
-
237
- SafeStringValue(v_file);
238
-
239
- if (NIL_P(v_sep))
240
- v_sep = rb_str_new2("\r\n");
241
- else
242
- SafeStringValue(v_sep);
243
-
244
- v_result = Qnil;
245
-
246
- length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
247
- file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
248
-
249
- if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, length)){
250
- ruby_xfree(file);
251
- rb_raise_syserr("MultibyteToWideChar", GetLastError());
252
- }
253
-
254
- h = CreateFileW(
255
- file,
256
- GENERIC_READ,
257
- FILE_SHARE_READ,
258
- NULL,
259
- OPEN_EXISTING,
260
- FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
261
- NULL
262
- );
263
-
264
- if (h == INVALID_HANDLE_VALUE)
265
- rb_raise_syserr("CreateFile", GetLastError());
266
-
267
- if (!GetFileSizeEx(h, &file_size)){
268
- error = GetLastError();
269
- CloseHandle(h);
270
- rb_raise_syserr("GetFileSizeEx", error);
271
- }
272
-
273
- length = (size_t)file_size.QuadPart;
274
-
275
- GetSystemInfo(&info);
276
- page_size = info.dwPageSize;
277
-
278
- page_num = (int)ceil((double)length / page_size);
279
-
280
- size = page_num * page_size;
281
-
282
- base_address = VirtualAlloc(NULL, (size_t)size, MEM_COMMIT, PAGE_READWRITE);
283
-
284
- if (!base_address){
285
- error = GetLastError();
286
- CloseHandle(h);
287
- rb_raise_syserr("VirtualAlloc", error);
288
- }
289
- else{
290
- int i;
291
- OVERLAPPED olap;
292
- BOOL rv;
293
- FILE_SEGMENT_ELEMENT* fse;
294
-
295
- olap.Offset = 0;
296
- olap.OffsetHigh = 0;
297
-
298
- if (NIL_P(v_event))
299
- olap.hEvent = NULL;
300
- else
301
- olap.hEvent = (HANDLE)(uintptr_t)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
302
-
303
- fse = (FILE_SEGMENT_ELEMENT*)malloc(sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
304
- memset(fse, 0, sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
305
- v_result = Qnil;
306
-
307
- for (i = 0; i < page_num; i++)
308
- fse[i].Alignment = (ULONGLONG)(uintptr_t)base_address + (page_size * i);
309
-
310
- rv = ReadFileScatter(h, fse, (DWORD)size, NULL, &olap);
311
-
312
- if (!rv){
313
- error = GetLastError();
314
-
315
- if (error == ERROR_IO_PENDING){
316
- while (!HasOverlappedIoCompleted(&olap))
317
- SleepEx(1, TRUE);
318
- }
319
- else{
320
- VirtualFree(base_address, 0, MEM_RELEASE);
321
- CloseHandle(h);
322
- rb_raise_syserr("ReadFileScatter", error);
323
- }
324
- }
325
-
326
- // Explicitly handle paragraph mode
327
- if (rb_equal(v_sep, rb_str_new2(""))){
328
- VALUE v_args[1];
329
- v_args[0] = rb_str_new2("(\r\n){2,}");
330
- v_sep = rb_class_new_instance(1, v_args, rb_cRegexp);
331
- v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
332
- rb_funcall(v_result, rb_intern("delete"), 1, rb_str_new2("\r\n"));
333
- }
334
- else{
335
- v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
336
- }
337
-
338
- VirtualFree(base_address, 0, MEM_RELEASE);
339
- }
340
-
341
- CloseHandle(h);
342
-
343
- return v_result;
344
- }
345
-
346
- void Init_nio(){
347
- VALUE mWin32 = rb_define_module("Win32");
348
- VALUE cNio = rb_define_class_under(mWin32, "NIO", rb_cObject);
349
-
350
- rb_define_singleton_method(cNio, "read", rb_nio_read, -1);
351
- rb_define_singleton_method(cNio, "readlines", rb_nio_readlines, -1);
352
-
353
- /* 0.2.1: The version of the win32-nio library */
354
- rb_define_const(cNio, "VERSION", rb_str_new2("0.2.1"));
355
- }
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
+ * NIO.read(file, length=nil, offset=0, options=nil){ # optional block }
20
+ *
21
+ * This method is similar to Ruby's IO.read method except that it uses
22
+ * native function calls. It also optionally accepts a Win32::Event object,
23
+ * signalling it upon completion. If provided it calls a block upon completion
24
+ * of the read.
25
+ *
26
+ * Examples:
27
+ *
28
+ * # Read everything
29
+ * Win32::NIO.read(file)
30
+ *
31
+ * # Read the first 100 bytes
32
+ * Win32::NIO.read(file, 100)
33
+ *
34
+ * # Read 50 bytes starting at offset 10
35
+ * Win32::NIO.read(file, 50, 10)
36
+ *
37
+ * Note that the +options+ that may be passed to this method are limited
38
+ * to :encoding, :mode and :event because we're no longer using the open
39
+ * function internally. In the case of :mode the only thing that is checked
40
+ * for is the presence of the 'b' (binary) mode.
41
+ *
42
+ * The :event option, if present, must be a Win32::Event object.
43
+ */
44
+ static VALUE rb_nio_read(int argc, VALUE* argv, VALUE self){
45
+ OVERLAPPED olap;
46
+ HANDLE h;
47
+ DWORD bytes_read;
48
+ LARGE_INTEGER lsize;
49
+ BOOL b;
50
+ VALUE v_file, v_length, v_offset, v_options;
51
+ VALUE v_event, v_mode, v_encoding, v_result;
52
+ size_t length;
53
+ int flags, error, size;
54
+ wchar_t* file = NULL;
55
+ char* buffer = NULL;
56
+
57
+ memset(&olap, 0, sizeof(olap));
58
+
59
+ // Paranoid initialization
60
+ v_length = Qnil;
61
+ v_offset = Qnil;
62
+ v_options = Qnil;
63
+
64
+ rb_scan_args(argc, argv, "13", &v_file, &v_length, &v_offset, &v_options);
65
+
66
+ // Allow path-y objects.
67
+ if (rb_respond_to(v_file, rb_intern("to_path")))
68
+ v_file = rb_funcall(v_file, rb_intern("to_path"), 0, NULL);
69
+
70
+ SafeStringValue(v_file);
71
+
72
+ // If a length is provided it cannot be negative.
73
+ if (!NIL_P(v_length)){
74
+ length = NUM2SIZET(v_length);
75
+ if ((int)length < 0)
76
+ rb_raise(rb_eArgError, "negative length %i given", (int)length);
77
+ }
78
+ else{
79
+ length = 0; // Mostly to make gcc stfu. We'll set this later.
80
+ }
81
+
82
+ // Unlike MRI, don't wait for an internal C function to fail on an invalid argument.
83
+ if (!NIL_P(v_offset)){
84
+ olap.Offset = NUM2ULONG(v_offset);
85
+ if ((int)olap.Offset < 0)
86
+ rb_raise(rb_eArgError, "negative offset %i given", (int)olap.Offset);
87
+ }
88
+
89
+ // Gotta do this for wide character function support.
90
+ size = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
91
+ file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
92
+
93
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, size)){
94
+ ruby_xfree(file);
95
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
96
+ }
97
+
98
+ flags = FILE_FLAG_SEQUENTIAL_SCAN;
99
+
100
+ // Possible options are :event, :mode and :encoding
101
+ if (!NIL_P(v_options)){
102
+ Check_Type(v_options, T_HASH);
103
+
104
+ v_event = rb_hash_aref(v_options, ID2SYM(rb_intern("event")));
105
+ v_encoding = rb_hash_aref(v_options, ID2SYM(rb_intern("encoding")));
106
+ v_mode = rb_hash_aref(v_options, ID2SYM(rb_intern("mode")));
107
+
108
+ if (!NIL_P(v_event)){
109
+ flags |= FILE_FLAG_OVERLAPPED;
110
+ olap.hEvent = (HANDLE)(uintptr_t)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
111
+ }
112
+ }
113
+ else{
114
+ v_event = Qnil;
115
+ v_encoding = Qnil;
116
+ v_mode = Qnil;
117
+ }
118
+
119
+ h = CreateFileW(
120
+ file,
121
+ GENERIC_READ,
122
+ FILE_SHARE_READ,
123
+ NULL,
124
+ OPEN_EXISTING,
125
+ flags,
126
+ NULL
127
+ );
128
+
129
+ if (h == INVALID_HANDLE_VALUE)
130
+ rb_raise_syserr("CreateFile", GetLastError());
131
+
132
+ // Get the file size. We may use this later to limit read length.
133
+ if (!GetFileSizeEx(h, &lsize)){
134
+ error = GetLastError();
135
+ CloseHandle(h);
136
+ rb_raise_syserr("GetFileSizeEx", error);
137
+ }
138
+
139
+ // If no length is specified, read the entire file
140
+ if (NIL_P(v_length))
141
+ length = (size_t)lsize.QuadPart;
142
+
143
+ // Don't read past the end of the file
144
+ if (olap.Offset + length > (size_t)lsize.QuadPart)
145
+ length = (size_t)lsize.QuadPart - olap.Offset;
146
+
147
+ buffer = (char*)ruby_xmalloc(length * sizeof(char));
148
+
149
+ // If a block is given then treat it as a callback
150
+ if (rb_block_given_p()){
151
+ flags |= FILE_FLAG_OVERLAPPED;
152
+ b = ReadFileEx(h, buffer, length, &olap, read_complete);
153
+ }
154
+ else{
155
+ b = ReadFile(h, buffer, length, &bytes_read, &olap);
156
+ }
157
+
158
+ error = GetLastError();
159
+
160
+ // Put in alertable wait state if overlapped IO
161
+ if (flags & FILE_FLAG_OVERLAPPED)
162
+ SleepEx(1, TRUE);
163
+
164
+ if (!b){
165
+ if(error == ERROR_IO_PENDING){
166
+ DWORD bytes;
167
+ if (!GetOverlappedResult(h, &olap, &bytes, TRUE)){
168
+ ruby_xfree(buffer);
169
+ CloseHandle(h);
170
+ rb_raise_syserr("GetOverlappedResult", error);
171
+ }
172
+ }
173
+ else{
174
+ ruby_xfree(buffer);
175
+ CloseHandle(h);
176
+ rb_raise_syserr("ReadFile", error);
177
+ }
178
+ }
179
+
180
+ CloseHandle(h);
181
+
182
+ v_result = rb_str_new(buffer, length);
183
+ ruby_xfree(buffer);
184
+
185
+ // Convert CRLF to LF if text mode
186
+ if (!NIL_P(v_mode) && strstr(RSTRING_PTR(v_mode), "t"))
187
+ rb_funcall(v_result, rb_intern("gsub!"), 2, rb_str_new2("\r\n"), rb_gv_get("$/"));
188
+
189
+ if (!NIL_P(v_encoding))
190
+ rb_funcall(v_result, rb_intern("encode!"), 1, v_encoding);
191
+
192
+ return v_result;
193
+ }
194
+
195
+ /*
196
+ * NIO.readlines(file, sep="\r\n", event=nil)
197
+ *
198
+ * Reads the entire file specified by portname as individual lines, and
199
+ * returns those lines in an array. Lines are separated by +sep+. The +event+
200
+ * argument must be a Win32::Event if present, and it is signalled upon
201
+ * completion.
202
+ *
203
+ * Examples:
204
+ *
205
+ * # Standard call
206
+ * Win32::NIO.readlines('file.txt') # => ['line 1', 'line 2', 'line 3', 'line 4']
207
+ *
208
+ * # Paragraph mode
209
+ * Win32::NIO.readlines('file.txt', '') # => ['line 1\r\nline 2', 'line 3\r\nline 4']
210
+ *
211
+ * # With event
212
+ * event = Win32::Event.new
213
+ * Win32::NIO.readlines('file.txt', nil, event)
214
+ * p event.signaled? # => true
215
+ *
216
+ * Superficially this method acts the same as the Ruby IO.readlines call, except that
217
+ * it does not transform line endings and accepts an optional event object. However,
218
+ * internally this method is using a scattered read to accomplish its goal. In practice
219
+ * this is only relevant in specific situations. Using it outside of those situations
220
+ * is unlikely to provide any practical benefit, and may even result in slower performance.
221
+ *
222
+ * See information on vectored IO for more details.
223
+ */
224
+ static VALUE rb_nio_readlines(int argc, VALUE* argv, VALUE self){
225
+ HANDLE h;
226
+ SYSTEM_INFO info;
227
+ LARGE_INTEGER file_size;
228
+ size_t length, page_size;
229
+ double size;
230
+ void* base_address;
231
+ int error, page_num;
232
+ wchar_t* file;
233
+ VALUE v_file, v_sep, v_event, v_result;
234
+
235
+ rb_scan_args(argc, argv, "12", &v_file, &v_sep, &v_event);
236
+
237
+ SafeStringValue(v_file);
238
+
239
+ if (NIL_P(v_sep))
240
+ v_sep = rb_str_new2("\r\n");
241
+ else
242
+ SafeStringValue(v_sep);
243
+
244
+ v_result = Qnil;
245
+
246
+ length = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, NULL, 0);
247
+ file = (wchar_t*)ruby_xmalloc(MAX_PATH * sizeof(wchar_t));
248
+
249
+ if (!MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(v_file), -1, file, length)){
250
+ ruby_xfree(file);
251
+ rb_raise_syserr("MultibyteToWideChar", GetLastError());
252
+ }
253
+
254
+ h = CreateFileW(
255
+ file,
256
+ GENERIC_READ,
257
+ FILE_SHARE_READ,
258
+ NULL,
259
+ OPEN_EXISTING,
260
+ FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
261
+ NULL
262
+ );
263
+
264
+ if (h == INVALID_HANDLE_VALUE)
265
+ rb_raise_syserr("CreateFile", GetLastError());
266
+
267
+ if (!GetFileSizeEx(h, &file_size)){
268
+ error = GetLastError();
269
+ CloseHandle(h);
270
+ rb_raise_syserr("GetFileSizeEx", error);
271
+ }
272
+
273
+ length = (size_t)file_size.QuadPart;
274
+
275
+ GetSystemInfo(&info);
276
+ page_size = info.dwPageSize;
277
+
278
+ page_num = (int)ceil((double)length / page_size);
279
+
280
+ size = page_num * page_size;
281
+
282
+ base_address = VirtualAlloc(NULL, (size_t)size, MEM_COMMIT, PAGE_READWRITE);
283
+
284
+ if (!base_address){
285
+ error = GetLastError();
286
+ CloseHandle(h);
287
+ rb_raise_syserr("VirtualAlloc", error);
288
+ }
289
+ else{
290
+ int i;
291
+ OVERLAPPED olap;
292
+ BOOL rv;
293
+ FILE_SEGMENT_ELEMENT* fse;
294
+
295
+ olap.Offset = 0;
296
+ olap.OffsetHigh = 0;
297
+
298
+ if (NIL_P(v_event))
299
+ olap.hEvent = NULL;
300
+ else
301
+ olap.hEvent = (HANDLE)(uintptr_t)NUM2OFFT(rb_funcall(v_event, rb_intern("handle"), 0, 0));
302
+
303
+ fse = (FILE_SEGMENT_ELEMENT*)malloc(sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
304
+ memset(fse, 0, sizeof(FILE_SEGMENT_ELEMENT) * (page_num + 1));
305
+ v_result = Qnil;
306
+
307
+ for (i = 0; i < page_num; i++)
308
+ fse[i].Alignment = (ULONGLONG)(uintptr_t)base_address + (page_size * i);
309
+
310
+ rv = ReadFileScatter(h, fse, (DWORD)size, NULL, &olap);
311
+
312
+ if (!rv){
313
+ error = GetLastError();
314
+
315
+ if (error == ERROR_IO_PENDING){
316
+ while (!HasOverlappedIoCompleted(&olap))
317
+ SleepEx(1, TRUE);
318
+ }
319
+ else{
320
+ VirtualFree(base_address, 0, MEM_RELEASE);
321
+ CloseHandle(h);
322
+ rb_raise_syserr("ReadFileScatter", error);
323
+ }
324
+ }
325
+
326
+ // Explicitly handle paragraph mode
327
+ if (rb_equal(v_sep, rb_str_new2(""))){
328
+ VALUE v_args[1];
329
+ v_args[0] = rb_str_new2("(\r\n){2,}");
330
+ v_sep = rb_class_new_instance(1, v_args, rb_cRegexp);
331
+ v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
332
+ rb_funcall(v_result, rb_intern("delete"), 1, rb_str_new2("\r\n"));
333
+ }
334
+ else{
335
+ v_result = rb_funcall(rb_str_new2(fse[0].Buffer), rb_intern("split"), 1, v_sep);
336
+ }
337
+
338
+ VirtualFree(base_address, 0, MEM_RELEASE);
339
+ }
340
+
341
+ CloseHandle(h);
342
+
343
+ return v_result;
344
+ }
345
+
346
+ void Init_nio(){
347
+ VALUE mWin32 = rb_define_module("Win32");
348
+ VALUE cNio = rb_define_class_under(mWin32, "NIO", rb_cObject);
349
+
350
+ rb_define_singleton_method(cNio, "read", rb_nio_read, -1);
351
+ rb_define_singleton_method(cNio, "readlines", rb_nio_readlines, -1);
352
+
353
+ /* 0.2.2: The version of the win32-nio library */
354
+ rb_define_const(cNio, "VERSION", rb_str_new2("0.2.2"));
355
+ }