win32-nio 0.2.1 → 0.2.2

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.
@@ -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
+ }