win32-pipe 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +34 -0
- data/MANIFEST +15 -0
- data/README +56 -0
- data/Rakefile +65 -0
- data/examples/test_client.rb +30 -0
- data/examples/test_client_async.rb +82 -0
- data/examples/test_server.rb +33 -0
- data/examples/test_server_async.rb +100 -0
- data/lib/win32/pipe.rb +253 -0
- data/lib/win32/pipe/client.rb +49 -0
- data/lib/win32/pipe/server.rb +96 -0
- data/test/tc_pipe.rb +122 -0
- data/test/tc_pipe_client.rb +28 -0
- data/test/tc_pipe_server.rb +34 -0
- data/win32-pipe.gemspec +26 -0
- metadata +68 -0
data/CHANGES
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
== 0.2.0 - 28-May-2008
|
2
|
+
* Now pure Ruby.
|
3
|
+
* Major interface change. Pipe::Server.new and Pipe::Client.new replace the
|
4
|
+
Pipe.new_server and Pipe.new_client methods, respectively.
|
5
|
+
* An optional 3rd argument, the open mode, is now accepted which allows finer
|
6
|
+
control over how pipes are created.
|
7
|
+
* Several pipe mode and open mode constants were added.
|
8
|
+
* The asynchronous pipe server actually works now.
|
9
|
+
* Added the Pipe#name method.
|
10
|
+
* Added the Pipe#asynchronous? method.
|
11
|
+
* Added the Pipe#size method as an alias for Pipe#length.
|
12
|
+
* Added a Rakefile with tasks for testing and installation.
|
13
|
+
* Added a gemspec and uploaded a gem file to RubyForge.
|
14
|
+
* Merged the doc files into the README and/or replaced them with inlined
|
15
|
+
comments that are RDoc friendly.
|
16
|
+
|
17
|
+
== 0.1.2 - 1-Mar-2005
|
18
|
+
* Moved the 'examples' directory to the toplevel directory.
|
19
|
+
* Made the CHANGES and README files rdoc friendly.
|
20
|
+
|
21
|
+
== 0.1.1 - 25-Aug-2004
|
22
|
+
* Added many more tests to the test suite.
|
23
|
+
* Moved the example programs to doc/examples.
|
24
|
+
* Fixed minor bugs in the asynchronous client and server test programs.
|
25
|
+
* Removed the pipe.html file. You can generate your own html documentation
|
26
|
+
using rd2 on the pipe.rd file.
|
27
|
+
|
28
|
+
== 0.1.0 - 13-Feb-2004
|
29
|
+
* Asynchronous support added (thanks Park Heesob)
|
30
|
+
* Sample test programs added. See files under 'test'.
|
31
|
+
* Documentation updates.
|
32
|
+
|
33
|
+
== 0.0.1 - 20-Nov-2003
|
34
|
+
* Initial release
|
data/MANIFEST
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
* MANIFEST
|
2
|
+
* README
|
3
|
+
* CHANGES
|
4
|
+
* Rakefile
|
5
|
+
* win32-pipe.gemspec
|
6
|
+
* examples/test_server.rb
|
7
|
+
* examples/test_server_async.rb
|
8
|
+
* examples/test_client.rb
|
9
|
+
* examples/test_client_async.rb
|
10
|
+
* lib/win32/pipe.rb
|
11
|
+
* lib/win32/pipe/client.rb
|
12
|
+
* lib/win32/pipe/server.rb
|
13
|
+
* test/tc_pipe.rb
|
14
|
+
* test/tc_pipe_client.rb
|
15
|
+
* test/tc_pipe_server.rb
|
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= Description
|
2
|
+
A Ruby interface for named pipes on Windows.
|
3
|
+
|
4
|
+
= Prerequisites
|
5
|
+
* windows-pr 0.8.5 or later
|
6
|
+
|
7
|
+
= Installation
|
8
|
+
== Local
|
9
|
+
rake install (non-gem) or rake install_gem (gem)
|
10
|
+
|
11
|
+
== Remote
|
12
|
+
gem install win32-pipe
|
13
|
+
|
14
|
+
= Synopsis
|
15
|
+
require 'win32/pipe'
|
16
|
+
include Win32
|
17
|
+
|
18
|
+
# In server.rb
|
19
|
+
pipe_server = Pipe::Server.new("foo_pipe")
|
20
|
+
pipe_server.connect
|
21
|
+
data = pipe_server.read
|
22
|
+
puts "Got #{data} from client"
|
23
|
+
pipe_server.close
|
24
|
+
|
25
|
+
# In client.rb (run from a different shell)
|
26
|
+
pipe_client = Pipe::Client.new("foo_pipe")
|
27
|
+
pipe_client.write("Hello World")
|
28
|
+
pipe_client.close
|
29
|
+
|
30
|
+
= What's a named pipe?
|
31
|
+
A pipe with a name - literally. In practice, it will feel more like a cross
|
32
|
+
between a socket and a pipe. At least, it does to me.
|
33
|
+
|
34
|
+
= What good is it?
|
35
|
+
My hope is that it can be used in certain circumstances where a fork might
|
36
|
+
be desirable, but which is not possible on Windows. It could also be handy
|
37
|
+
for the traditional "piping data to a server" usage. And if you come up
|
38
|
+
with anything cool, please let us all know!
|
39
|
+
|
40
|
+
= Future Plans
|
41
|
+
* Add transactions
|
42
|
+
|
43
|
+
= License
|
44
|
+
Ruby's
|
45
|
+
|
46
|
+
= Warranty
|
47
|
+
This package is provided "as is" and without any express or
|
48
|
+
implied warranties, including, without limitation, the implied
|
49
|
+
warranties of merchantability and fitness for a particular purpose.
|
50
|
+
|
51
|
+
= Copyright
|
52
|
+
(C) 2003-2008, Daniel J. Berger, All Rights Reserved.
|
53
|
+
|
54
|
+
= Authors
|
55
|
+
Daniel Berger
|
56
|
+
Park Heesob
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rbconfig'
|
5
|
+
include Config
|
6
|
+
|
7
|
+
desc 'Install the win32-pipe library (non-gem)'
|
8
|
+
task :install do
|
9
|
+
sitelibdir = CONFIG['sitelibdir']
|
10
|
+
|
11
|
+
pipe_installdir = File.join(sitelibdir, 'win32')
|
12
|
+
sub_installdir = File.join(sitelibdir, 'win32', 'pipe')
|
13
|
+
|
14
|
+
pipe_file = File.join('lib', 'win32', 'pipe.rb')
|
15
|
+
client_file = File.join('lib', 'win32', 'pipe', 'client.rb')
|
16
|
+
server_file = File.join('lib', 'win32', 'pipe', 'server.rb')
|
17
|
+
|
18
|
+
FileUtils.mkdir_p(sub_installdir)
|
19
|
+
|
20
|
+
FileUtils.cp(pipe_file, pipe_installdir, :verbose => true)
|
21
|
+
FileUtils.cp(client_file, sub_installdir, :verbose => true)
|
22
|
+
FileUtils.cp(server_file, sub_installdir, :verbose => true)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Install the win32-pipe library'
|
26
|
+
task :install_c => [:build] do
|
27
|
+
Dir.chdir('ext'){
|
28
|
+
sh 'nmake install'
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Clean any build files for win32-pipe"
|
33
|
+
task :clean do
|
34
|
+
Dir.chdir('ext') do
|
35
|
+
if File.exists?('pipe.so') ||
|
36
|
+
File.exists?('win32/pipe.so')
|
37
|
+
then
|
38
|
+
sh 'nmake distclean'
|
39
|
+
rm 'win32/pipe.so' if File.exists?('win32/pipe.so')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "Build win32-pipe (but don't install it)"
|
45
|
+
task :build => [:clean] do
|
46
|
+
Dir.chdir('ext') do
|
47
|
+
ruby 'extconf.rb'
|
48
|
+
sh 'nmake'
|
49
|
+
mv 'pipe.so', 'win32' # For the test suite
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Run the sample program"
|
54
|
+
task :example do |t|
|
55
|
+
Dir.chdir('examples'){
|
56
|
+
sh 'ruby test.rb'
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
Rake::TestTask.new('test') do |test|
|
61
|
+
test.libs << 'lib/win32'
|
62
|
+
test.libs << 'lib/win32/pipe'
|
63
|
+
test.test_files = FileList['test/tc*']
|
64
|
+
test.warning = true
|
65
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#########################################################################
|
2
|
+
# test_client.rb
|
3
|
+
#
|
4
|
+
# Simple client test. Be sure to start the server first in a separate
|
5
|
+
# terminal. You can run this example via the 'rake example_client' task.
|
6
|
+
#
|
7
|
+
# Modify this code as you see fit.
|
8
|
+
#########################################################################
|
9
|
+
require 'win32/pipe'
|
10
|
+
include Win32
|
11
|
+
|
12
|
+
Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
|
13
|
+
|
14
|
+
puts "VERSION: " + Pipe::VERSION
|
15
|
+
|
16
|
+
# Block form
|
17
|
+
Pipe::Client.new('foo') do |pipe|
|
18
|
+
puts "Connected..."
|
19
|
+
pipe.write("Ruby rocks!")
|
20
|
+
data = pipe.read
|
21
|
+
puts "Got [#{data}] back from server"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Non-block form
|
25
|
+
#pclient = Pipe::Client.new('foo')
|
26
|
+
#puts "Connected..."
|
27
|
+
#pclient.write("Ruby rocks!")
|
28
|
+
#data = pclient.read
|
29
|
+
#puts "Got [#{data}] back from server"
|
30
|
+
#pclient.close
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#########################################################################
|
2
|
+
# test_client_async.rb
|
3
|
+
#
|
4
|
+
# Simple client test. Be sure to start the server first in a separate
|
5
|
+
# terminal. You can run this example via the 'rake example_async_client'
|
6
|
+
# task.
|
7
|
+
#########################################################################
|
8
|
+
require 'win32/pipe'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
puts "VERSION: " + Pipe::VERSION
|
12
|
+
|
13
|
+
Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
|
14
|
+
|
15
|
+
CONNECTING_STATE = 0
|
16
|
+
READING_STATE = 1
|
17
|
+
WRITING_STATE = 2
|
18
|
+
|
19
|
+
class MyPipe < Pipe::Client
|
20
|
+
def read_complete
|
21
|
+
puts "read_complete"
|
22
|
+
puts "Got [#{buffer}] back from server"
|
23
|
+
@state = WRITING_STATE
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_complete
|
27
|
+
puts "write_complete"
|
28
|
+
@state = READING_STATE
|
29
|
+
end
|
30
|
+
|
31
|
+
def mainloop
|
32
|
+
@state = WRITING_STATE
|
33
|
+
while true
|
34
|
+
if wait(1) # wait for 1 second
|
35
|
+
if pending? # IO is pending
|
36
|
+
case @state
|
37
|
+
when READING_STATE
|
38
|
+
if transferred == 0
|
39
|
+
reconnect
|
40
|
+
break
|
41
|
+
end
|
42
|
+
read_complete
|
43
|
+
break
|
44
|
+
when WRITING_STATE
|
45
|
+
if transferred != length
|
46
|
+
reconnect
|
47
|
+
break
|
48
|
+
end
|
49
|
+
write_complete
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
case @state
|
54
|
+
when READING_STATE
|
55
|
+
if read
|
56
|
+
if not pending?
|
57
|
+
read_complete
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
when WRITING_STATE
|
62
|
+
if write("Ruby rocks!")
|
63
|
+
if not pending?
|
64
|
+
write_complete
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
sleep(1)
|
71
|
+
puts "pipe client is running"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
flags = Pipe::DEFAULT_OPEN_MODE | Pipe::OVERLAPPED
|
77
|
+
|
78
|
+
MyPipe.new('foo', nil, flags) do |client|
|
79
|
+
puts "Connected..."
|
80
|
+
client.mainloop
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#########################################################################
|
2
|
+
# test_server.rb
|
3
|
+
#
|
4
|
+
# A simple named pipe server. Start this up in its own terminal window.
|
5
|
+
# You may have to use the task manager to kill it if you don't connect
|
6
|
+
# with the test client program.
|
7
|
+
#
|
8
|
+
# You can start this server with the 'rake example_server' task. Modify
|
9
|
+
# this code as you see fit.
|
10
|
+
#########################################################################
|
11
|
+
require 'win32/pipe'
|
12
|
+
include Win32
|
13
|
+
|
14
|
+
Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
|
15
|
+
|
16
|
+
puts "VERSION: " + Pipe::VERSION
|
17
|
+
|
18
|
+
# Block form
|
19
|
+
Pipe::Server.new('foo') do |pipe|
|
20
|
+
pipe.connect
|
21
|
+
data = pipe.read
|
22
|
+
puts "Got [#{data}]"
|
23
|
+
pipe.write "Thanks for the data!"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Non-block form
|
27
|
+
#pserver = Pipe::Server.new('foo')
|
28
|
+
#pserver.connect # put server in wait connect
|
29
|
+
#data = pserver.read
|
30
|
+
#puts "Got [#{data}]"
|
31
|
+
#pserver.write("Thanks for the data!")
|
32
|
+
#pserver.disconnect
|
33
|
+
#pserver.close
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#######################################################################
|
2
|
+
# test_server_async.rb
|
3
|
+
#
|
4
|
+
# A simple, asynchronous named pipe server. Start this up in its own
|
5
|
+
# terminal window. You can run this program via the
|
6
|
+
# 'rake example_async_server' task.
|
7
|
+
#######################################################################
|
8
|
+
require 'win32/pipe'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
puts "VERSION: " + Pipe::VERSION
|
12
|
+
|
13
|
+
Thread.new { loop { sleep 0.01 } } # Allow Ctrl-C
|
14
|
+
|
15
|
+
CONNECTING_STATE = 0
|
16
|
+
READING_STATE = 1
|
17
|
+
WRITING_STATE = 2
|
18
|
+
|
19
|
+
class MyPipe < Pipe::Server
|
20
|
+
def connected
|
21
|
+
puts "connected"
|
22
|
+
@state = READING_STATE
|
23
|
+
end
|
24
|
+
|
25
|
+
def read_complete
|
26
|
+
puts "read_complete"
|
27
|
+
puts "Got [#{buffer}]"
|
28
|
+
@state = WRITING_STATE
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_complete
|
32
|
+
puts "write_complete"
|
33
|
+
disconnect
|
34
|
+
@state = CONNECTING_STATE
|
35
|
+
end
|
36
|
+
|
37
|
+
def reconnect
|
38
|
+
disconnect
|
39
|
+
mainloop
|
40
|
+
end
|
41
|
+
|
42
|
+
def mainloop
|
43
|
+
@state = CONNECTING_STATE
|
44
|
+
while true
|
45
|
+
if wait(1) # wait for 1 second
|
46
|
+
if pending? # IO is pending
|
47
|
+
case @state
|
48
|
+
when CONNECTING_STATE
|
49
|
+
connected
|
50
|
+
when READING_STATE
|
51
|
+
if transferred == 0
|
52
|
+
reconnect
|
53
|
+
break
|
54
|
+
end
|
55
|
+
read_complete
|
56
|
+
when WRITING_STATE
|
57
|
+
if transferred != length
|
58
|
+
reconnect
|
59
|
+
break
|
60
|
+
end
|
61
|
+
write_complete
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
case @state
|
66
|
+
when CONNECTING_STATE
|
67
|
+
if connect
|
68
|
+
connected
|
69
|
+
end
|
70
|
+
when READING_STATE
|
71
|
+
if read
|
72
|
+
if !pending?
|
73
|
+
read_complete
|
74
|
+
end
|
75
|
+
else
|
76
|
+
reconnect
|
77
|
+
end
|
78
|
+
when WRITING_STATE
|
79
|
+
if write("Thanks for the data!")
|
80
|
+
if not pending?
|
81
|
+
write_complete
|
82
|
+
end
|
83
|
+
else
|
84
|
+
reconnect
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
sleep(1)
|
91
|
+
puts "pipe server is running"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
flags = Pipe::ACCESS_DUPLEX | Pipe::OVERLAPPED
|
97
|
+
|
98
|
+
MyPipe.new('foo', 0, flags) do |pipe|
|
99
|
+
pipe.mainloop
|
100
|
+
end
|
data/lib/win32/pipe.rb
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'windows/pipe'
|
2
|
+
require 'windows/synchronize'
|
3
|
+
require 'windows/handle'
|
4
|
+
require 'windows/file'
|
5
|
+
require 'windows/error'
|
6
|
+
|
7
|
+
# The Win32 module serves as a namespace only.
|
8
|
+
module Win32
|
9
|
+
# The Pipe class is an abstract base class for the Pipe::Server and
|
10
|
+
# Pipe::Client classes. Do not use this directly.
|
11
|
+
#
|
12
|
+
class Pipe
|
13
|
+
include Windows::Pipe
|
14
|
+
include Windows::Synchronize
|
15
|
+
include Windows::Handle
|
16
|
+
include Windows::File
|
17
|
+
include Windows::Error
|
18
|
+
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
# The version of this library
|
22
|
+
VERSION = '0.2.0'
|
23
|
+
|
24
|
+
PIPE_BUFFER_SIZE = 512 #:nodoc:
|
25
|
+
PIPE_TIMEOUT = 5000 #:nodoc:
|
26
|
+
|
27
|
+
# Blocking mode is enabled
|
28
|
+
WAIT = PIPE_WAIT
|
29
|
+
|
30
|
+
# Nonblocking mode is enabled
|
31
|
+
NOWAIT = PIPE_NOWAIT
|
32
|
+
|
33
|
+
# The pipe is bi-directional. Both server and client processes can read
|
34
|
+
# from and write to the pipe.
|
35
|
+
ACCESS_DUPLEX = PIPE_ACCESS_DUPLEX
|
36
|
+
|
37
|
+
# The flow of data in the pipe goes from client to server only.
|
38
|
+
ACCESS_INBOUND = PIPE_ACCESS_INBOUND
|
39
|
+
|
40
|
+
# The flow of data in the pipe goes from server to client only.
|
41
|
+
ACCESS_OUTBOUND = PIPE_ACCESS_OUTBOUND
|
42
|
+
|
43
|
+
# Data is written to the pipe as a stream of bytes.
|
44
|
+
TYPE_BYTE = PIPE_TYPE_BYTE
|
45
|
+
|
46
|
+
# Data is written to the pipe as a stream of messages.
|
47
|
+
TYPE_MESSAGE = PIPE_TYPE_MESSAGE
|
48
|
+
|
49
|
+
# Data is read from the pipe as a stream of bytes.
|
50
|
+
READMODE_BYTE = PIPE_READMODE_BYTE
|
51
|
+
|
52
|
+
# Data is read from the pipe as a stream of messages.
|
53
|
+
READMODE_MESSAGE = PIPE_READMODE_MESSAGE
|
54
|
+
|
55
|
+
# All instances beyond the first will fail with access denied errors.
|
56
|
+
FIRST_PIPE_INSTANCE = FILE_FLAG_FIRST_PIPE_INSTANCE
|
57
|
+
|
58
|
+
# Functions do not return until the data is written across the network.
|
59
|
+
WRITE_THROUGH = FILE_FLAG_WRITE_THROUGH
|
60
|
+
|
61
|
+
# Overlapped mode enables asynchronous communication.
|
62
|
+
OVERLAPPED = FILE_FLAG_OVERLAPPED
|
63
|
+
|
64
|
+
# The default pipe mode
|
65
|
+
DEFAULT_PIPE_MODE = NOWAIT
|
66
|
+
|
67
|
+
# The default open mode
|
68
|
+
DEFAULT_OPEN_MODE = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH
|
69
|
+
|
70
|
+
# The data still in the pipe's buffer
|
71
|
+
attr_reader :buffer
|
72
|
+
|
73
|
+
# The number of bytes to be written to the pipe.
|
74
|
+
attr_reader :size
|
75
|
+
|
76
|
+
# The number of characters that are actually transferred over the pipe.
|
77
|
+
attr_reader :transferred
|
78
|
+
|
79
|
+
# The full name of the pipe, e.g. "\\\\.\\pipe\\my_pipe"
|
80
|
+
attr_reader :name
|
81
|
+
|
82
|
+
# The pipe mode of the pipe.
|
83
|
+
attr_reader :open_mode
|
84
|
+
|
85
|
+
# The open mode of the pipe.
|
86
|
+
attr_reader :pipe_mode
|
87
|
+
|
88
|
+
# Abstract initializer for base class. This handles automatic prepending
|
89
|
+
# of '\\.\pipe\' to each named pipe so that you don't have to. Don't
|
90
|
+
# use this directly. Add the full implementation in subclasses.
|
91
|
+
#
|
92
|
+
# The default pipe mode is PIPE_WAIT.
|
93
|
+
#
|
94
|
+
# The default open mode is FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH.
|
95
|
+
#
|
96
|
+
def initialize(name, pipe_mode = DEFAULT_PIPE_MODE, open_mode = DEFAULT_OPEN_MODE)
|
97
|
+
@name = "\\\\.\\pipe\\" + name
|
98
|
+
|
99
|
+
@pipe_mode = pipe_mode.nil? ? DEFAULT_PIPE_MODE : pipe_mode
|
100
|
+
@open_mode = open_mode.nil? ? DEFAULT_OPEN_MODE : open_mode
|
101
|
+
|
102
|
+
@pipe = nil
|
103
|
+
@pending_io = false
|
104
|
+
@buffer = 0.chr * PIPE_BUFFER_SIZE
|
105
|
+
@size = 0
|
106
|
+
@overlapped = 0.chr * 20 # sizeof(OVERLAPPED)
|
107
|
+
@transferred = 0
|
108
|
+
@asynchronous = false
|
109
|
+
|
110
|
+
if open_mode & FILE_FLAG_OVERLAPPED > 0
|
111
|
+
@asynchronous = true
|
112
|
+
end
|
113
|
+
|
114
|
+
if @asynchronous
|
115
|
+
@event = CreateEvent(nil, true, true, nil)
|
116
|
+
@overlapped[16, 4] = [@event].pack('L')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Disconnects the pipe.
|
121
|
+
def disconnect
|
122
|
+
DisconnectNamedPipe(@pipe)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Closes the pipe.
|
126
|
+
#
|
127
|
+
def close
|
128
|
+
CloseHandle(@pipe)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns whether or not there is a pending IO operation on the pipe.
|
132
|
+
#
|
133
|
+
def pending?
|
134
|
+
@pending_io
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns whether or not the pipe is asynchronous.
|
138
|
+
#
|
139
|
+
def asynchronous?
|
140
|
+
@asynchronous
|
141
|
+
end
|
142
|
+
|
143
|
+
# Reads data from the pipe. You can read data from either end of a named
|
144
|
+
# pipe.
|
145
|
+
#
|
146
|
+
def read
|
147
|
+
bytes = [0].pack('L')
|
148
|
+
@buffer = 0.chr * PIPE_BUFFER_SIZE
|
149
|
+
|
150
|
+
if @asynchronous
|
151
|
+
bool = ReadFile(@pipe, @buffer, @buffer.size, bytes, @overlapped)
|
152
|
+
|
153
|
+
bytes_read = bytes.unpack('L').first
|
154
|
+
|
155
|
+
if bool && bytes_read > 0
|
156
|
+
@pending_io = false
|
157
|
+
@buffer = @buffer[0, bytes_read]
|
158
|
+
return true
|
159
|
+
end
|
160
|
+
|
161
|
+
error = GetLastError()
|
162
|
+
if !bool && error == ERROR_IO_PENDING
|
163
|
+
@pending_io = true
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
|
167
|
+
return false
|
168
|
+
else
|
169
|
+
unless ReadFile(@pipe, @buffer, @buffer.size, bytes, nil)
|
170
|
+
raise Error, get_last_error
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
@buffer.unpack("A*")
|
175
|
+
end
|
176
|
+
|
177
|
+
# Writes 'data' to the pipe. You can write data to either end of a
|
178
|
+
# named pipe.
|
179
|
+
#
|
180
|
+
def write(data)
|
181
|
+
@buffer = data
|
182
|
+
@size = data.size
|
183
|
+
bytes = [0].pack('L')
|
184
|
+
|
185
|
+
if @asynchronous
|
186
|
+
bool = WriteFile(@pipe, @buffer, @buffer.size, bytes, @overlapped)
|
187
|
+
|
188
|
+
bytes_written = bytes.unpack('L').first
|
189
|
+
|
190
|
+
if bool && bytes_written > 0
|
191
|
+
@pending_io = false
|
192
|
+
return true
|
193
|
+
end
|
194
|
+
|
195
|
+
error = GetLastError()
|
196
|
+
|
197
|
+
if !bool && error == ERROR_IO_PENDING
|
198
|
+
@pending_io = true
|
199
|
+
return true
|
200
|
+
end
|
201
|
+
|
202
|
+
return false
|
203
|
+
else
|
204
|
+
unless WriteFile(@pipe, @buffer, @buffer.size, bytes, 0)
|
205
|
+
raise Error, get_last_error
|
206
|
+
end
|
207
|
+
|
208
|
+
return true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the pipe object if an event (such as a client connection)
|
213
|
+
# occurs within the +max_time+ specified (in seconds). Otherwise, it
|
214
|
+
# returns false.
|
215
|
+
#
|
216
|
+
def wait(max_time = nil)
|
217
|
+
unless @asynchronous
|
218
|
+
raise Error, 'cannot wait in synchronous (blocking) mode'
|
219
|
+
end
|
220
|
+
|
221
|
+
max_time = max_time ? max_time * 1000 : INFINITE
|
222
|
+
|
223
|
+
wait = WaitForSingleObject(@event, max_time)
|
224
|
+
|
225
|
+
if wait == WAIT_TIMEOUT
|
226
|
+
return false
|
227
|
+
else
|
228
|
+
if wait != WAIT_OBJECT_0
|
229
|
+
raise Error, get_last_error
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
if @pending_io
|
234
|
+
transferred = [0].pack('L')
|
235
|
+
bool = GetOverlappedResult(@pipe, @overlapped, transferred, false)
|
236
|
+
|
237
|
+
unless bool
|
238
|
+
raise Error, get_last_error
|
239
|
+
end
|
240
|
+
|
241
|
+
@transferred = transferred.unpack('L')[0]
|
242
|
+
@buffer = @buffer[0, @transferred]
|
243
|
+
end
|
244
|
+
|
245
|
+
self
|
246
|
+
end
|
247
|
+
|
248
|
+
alias length size
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
require 'win32/pipe/server'
|
253
|
+
require 'win32/pipe/client'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# The Win32 module serves as a namespace only
|
2
|
+
module Win32
|
3
|
+
# The Pipe::Client class encapsulates the client side of a named pipe
|
4
|
+
# connection.
|
5
|
+
#
|
6
|
+
class Pipe::Client < Pipe
|
7
|
+
# Create and return a new Pipe::Client instance.
|
8
|
+
#
|
9
|
+
# The default pipe mode is PIPE_WAIT.
|
10
|
+
#
|
11
|
+
# The default open mode is FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH.
|
12
|
+
#--
|
13
|
+
# 2147483776 is FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH
|
14
|
+
def initialize(name, pipe_mode = DEFAULT_PIPE_MODE, open_mode = DEFAULT_OPEN_MODE)
|
15
|
+
super(name, pipe_mode, open_mode)
|
16
|
+
|
17
|
+
@pipe = CreateFile(
|
18
|
+
@name,
|
19
|
+
GENERIC_READ | GENERIC_WRITE,
|
20
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
21
|
+
nil,
|
22
|
+
OPEN_EXISTING,
|
23
|
+
@open_mode,
|
24
|
+
nil
|
25
|
+
)
|
26
|
+
|
27
|
+
error = GetLastError()
|
28
|
+
|
29
|
+
if error == ERROR_PIPE_BUSY
|
30
|
+
unless WaitNamedPipe(@name, NMPWAIT_WAIT_FOREVER)
|
31
|
+
raise Error, get_last_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if @pipe == INVALID_HANDLE_VALUE
|
36
|
+
raise Error, get_last_error
|
37
|
+
end
|
38
|
+
|
39
|
+
if block_given?
|
40
|
+
begin
|
41
|
+
yield self
|
42
|
+
ensure
|
43
|
+
disconnect
|
44
|
+
close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# The Win32 module serves as a namespace only.
|
2
|
+
module Win32
|
3
|
+
# The Pipe::Server class encapsulates the server side of a named pipe
|
4
|
+
# connection.
|
5
|
+
class Pipe::Server < Pipe
|
6
|
+
|
7
|
+
# Creates and returns a new Pipe::Server instance, using +name+ as the
|
8
|
+
# name for the pipe. Note that this does not actually connect the pipe.
|
9
|
+
# Use Pipe::Server#connect for that.
|
10
|
+
#
|
11
|
+
# The default pipe_mode is PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT.
|
12
|
+
#
|
13
|
+
# The default open_mode is Pipe::ACCESS_DUPLEX.
|
14
|
+
#--
|
15
|
+
# The default pipe_mode also happens to be 0.
|
16
|
+
#
|
17
|
+
def initialize(name, pipe_mode = 0, open_mode = Pipe::ACCESS_DUPLEX)
|
18
|
+
super(name, pipe_mode, open_mode)
|
19
|
+
|
20
|
+
@pipe = CreateNamedPipe(
|
21
|
+
@name,
|
22
|
+
@open_mode,
|
23
|
+
@pipe_mode,
|
24
|
+
PIPE_UNLIMITED_INSTANCES,
|
25
|
+
PIPE_BUFFER_SIZE,
|
26
|
+
PIPE_BUFFER_SIZE,
|
27
|
+
PIPE_TIMEOUT,
|
28
|
+
0
|
29
|
+
)
|
30
|
+
|
31
|
+
if @pipe == INVALID_HANDLE_VALUE
|
32
|
+
raise Error, get_last_error
|
33
|
+
end
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
begin
|
37
|
+
yield self
|
38
|
+
ensure
|
39
|
+
close
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enables the named pipe server process to wait for a client process
|
45
|
+
# to connect to an instance of a named pipe. In other words, it puts
|
46
|
+
# the server in 'connection wait' status.
|
47
|
+
#
|
48
|
+
# In synchronous mode always returns true on success. In asynchronous
|
49
|
+
# mode returns true if there is pending IO, or false otherwise.
|
50
|
+
#
|
51
|
+
def connect
|
52
|
+
if @asynchronous
|
53
|
+
# An overlapped ConnectNamedPipe should return 0
|
54
|
+
if ConnectNamedPipe(@pipe, @overlapped)
|
55
|
+
raise Error, get_last_error
|
56
|
+
end
|
57
|
+
|
58
|
+
error = GetLastError()
|
59
|
+
|
60
|
+
case error
|
61
|
+
when ERROR_IO_PENDING
|
62
|
+
@pending_io = true
|
63
|
+
when ERROR_PIPE_CONNECTED
|
64
|
+
unless SetEvent(@event)
|
65
|
+
raise Error, get_last_error(error)
|
66
|
+
end
|
67
|
+
when ERROR_PIPE_LISTENING
|
68
|
+
# Do nothing
|
69
|
+
else
|
70
|
+
raise Error, get_last_error(error)
|
71
|
+
end
|
72
|
+
|
73
|
+
if @pending_io
|
74
|
+
return false
|
75
|
+
else
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
else
|
79
|
+
unless ConnectNamedPipe(@pipe, nil)
|
80
|
+
raise Error, get_last_error
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
return true
|
85
|
+
end
|
86
|
+
|
87
|
+
# Close the server. This will flush file buffers, disconnect the
|
88
|
+
# pipe, and close the pipe handle.
|
89
|
+
#
|
90
|
+
def close
|
91
|
+
FlushFileBuffers(@pipe)
|
92
|
+
DisconnectNamedPipe(@pipe)
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/test/tc_pipe.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
##########################################################################
|
2
|
+
# tc_pipe.rb
|
3
|
+
#
|
4
|
+
# Test suite for the win32-pipe library. This test suite should be run
|
5
|
+
# via the 'rake test' task.
|
6
|
+
##########################################################################
|
7
|
+
require 'test/unit'
|
8
|
+
require 'win32/pipe'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
class TC_Win32_Pipe < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@pipe = Pipe.new('foo')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_version
|
17
|
+
assert_equal('0.2.0', Pipe::VERSION)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_name
|
21
|
+
assert_respond_to(@pipe, :name)
|
22
|
+
assert_nothing_raised{ @pipe.name }
|
23
|
+
assert_equal("\\\\.\\pipe\\foo", @pipe.name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_pipe_mode
|
27
|
+
assert_respond_to(@pipe, :pipe_mode)
|
28
|
+
assert_nothing_raised{ @pipe.pipe_mode }
|
29
|
+
assert_equal(Pipe::DEFAULT_PIPE_MODE, @pipe.pipe_mode)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_open_mode
|
33
|
+
assert_respond_to(@pipe, :open_mode)
|
34
|
+
assert_nothing_raised{ @pipe.open_mode }
|
35
|
+
assert_equal(Pipe::DEFAULT_OPEN_MODE, @pipe.open_mode)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_buffer
|
39
|
+
assert_respond_to(@pipe, :buffer)
|
40
|
+
assert_nothing_raised{ @pipe.buffer }
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_size
|
44
|
+
assert_respond_to(@pipe, :size)
|
45
|
+
assert_nothing_raised{ @pipe.size }
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_length_alias
|
49
|
+
assert_respond_to(@pipe, :length)
|
50
|
+
assert_equal(true, @pipe.method(:length) == @pipe.method(:size))
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_pending
|
54
|
+
assert_respond_to(@pipe, :pending?)
|
55
|
+
assert_nothing_raised{ @pipe.pending? }
|
56
|
+
assert_equal(false, @pipe.pending?)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_asynchronous
|
60
|
+
assert_respond_to(@pipe, :asynchronous?)
|
61
|
+
assert_nothing_raised{ @pipe.asynchronous? }
|
62
|
+
assert_equal(false, @pipe.asynchronous?)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_read
|
66
|
+
assert_respond_to(@pipe, :read)
|
67
|
+
assert_raises(Pipe::Error){ @pipe.read } # Nothing to read
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_transferred
|
71
|
+
assert_respond_to(@pipe, :transferred)
|
72
|
+
assert_nothing_raised{ @pipe.transferred }
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_wait
|
76
|
+
assert_respond_to(@pipe, :wait)
|
77
|
+
assert_raises(Pipe::Error){ @pipe.wait } # Can't wait in blocking mode
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_write
|
81
|
+
assert_respond_to(@pipe, :write)
|
82
|
+
assert_raises(ArgumentError){ @pipe.write } # Must have 1 argument
|
83
|
+
assert_raises(Pipe::Error){ @pipe.write("foo") } # Nothing to write to
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_disconnect
|
87
|
+
assert_respond_to(@pipe, :disconnect)
|
88
|
+
assert_nothing_raised{ @pipe.disconnect }
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_close
|
92
|
+
assert_respond_to(@pipe, :close)
|
93
|
+
assert_nothing_raised{ @pipe.close }
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_pipe_mode_constants
|
97
|
+
assert_not_nil(Pipe::WAIT)
|
98
|
+
assert_not_nil(Pipe::NOWAIT)
|
99
|
+
assert_not_nil(Pipe::TYPE_BYTE)
|
100
|
+
assert_not_nil(Pipe::TYPE_MESSAGE)
|
101
|
+
assert_not_nil(Pipe::READMODE_BYTE)
|
102
|
+
assert_not_nil(Pipe::READMODE_MESSAGE)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_open_mode_constants
|
106
|
+
assert_not_nil(Pipe::ACCESS_DUPLEX)
|
107
|
+
assert_not_nil(Pipe::ACCESS_INBOUND)
|
108
|
+
assert_not_nil(Pipe::ACCESS_OUTBOUND)
|
109
|
+
assert_not_nil(Pipe::FIRST_PIPE_INSTANCE)
|
110
|
+
assert_not_nil(Pipe::WRITE_THROUGH)
|
111
|
+
assert_not_nil(Pipe::OVERLAPPED)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_other_constants
|
115
|
+
assert_not_nil(Pipe::INFINITE)
|
116
|
+
end
|
117
|
+
|
118
|
+
def teardown
|
119
|
+
@pipe.close
|
120
|
+
@pipe = nil
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
##########################################################################
|
2
|
+
# tc_pipe_client.rb
|
3
|
+
#
|
4
|
+
# Test suite for the Pipe::Client class. This test suite should be run
|
5
|
+
# as part of the 'rake test' task.
|
6
|
+
##########################################################################
|
7
|
+
require 'test/unit'
|
8
|
+
require 'win32/pipe'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
class TC_Win32_Pipe_Client < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@pipe = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_constructor_basic
|
17
|
+
assert_respond_to(Pipe::Client, :new)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_constructor_expected_errors
|
21
|
+
assert_raise(ArgumentError){ Pipe::Client.new }
|
22
|
+
assert_raise(TypeError){ Pipe::Client.new(1) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def teardown
|
26
|
+
@pipe = nil
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
##########################################################################
|
2
|
+
# tc_pipe_server.rb
|
3
|
+
#
|
4
|
+
# Test suite for the Pipe::Server class. This test suite should be run
|
5
|
+
# as part of the 'rake test' task.
|
6
|
+
##########################################################################
|
7
|
+
require 'test/unit'
|
8
|
+
require 'win32/pipe'
|
9
|
+
include Win32
|
10
|
+
|
11
|
+
class TC_Win32_Pipe_Server < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@pipe = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_constructor_basic
|
17
|
+
assert_respond_to(Pipe::Server, :new)
|
18
|
+
assert_nothing_raised{ Pipe::Server.new('foo') }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_connect
|
22
|
+
assert_nothing_raised{ @pipe = Pipe::Server.new('foo') }
|
23
|
+
assert_respond_to(@pipe, :connect)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_constructor_expected_errors
|
27
|
+
assert_raise(ArgumentError){ Pipe::Server.new }
|
28
|
+
assert_raise(TypeError){ Pipe::Server.new(1) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def teardown
|
32
|
+
@pipe = nil
|
33
|
+
end
|
34
|
+
end
|
data/win32-pipe.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |gem|
|
4
|
+
gem.name = "win32-pipe"
|
5
|
+
gem.version = "0.2.0"
|
6
|
+
gem.author = "Daniel J. Berger"
|
7
|
+
gem.email = "djberg96@gmail.com"
|
8
|
+
gem.homepage = "http://www.rubyforge.org/projects/win32utils"
|
9
|
+
gem.platform = Gem::Platform::RUBY
|
10
|
+
gem.summary = "An interface for named pipes on MS Windows"
|
11
|
+
gem.description = "An interface for named pipes on MS Windows"
|
12
|
+
gem.test_files = Dir["test/tc_*.rb"]
|
13
|
+
gem.has_rdoc = true
|
14
|
+
gem.extra_rdoc_files = ['CHANGES', 'README', 'MANIFEST']
|
15
|
+
gem.rubyforge_project = "win32utils"
|
16
|
+
|
17
|
+
files = Dir["doc/*"] + Dir["examples/*"] + Dir["lib/win32/**/*.rb"]
|
18
|
+
files += Dir["test/*"] + Dir["[A-Z]*"]
|
19
|
+
files.delete_if{ |item| item.include?("CVS") }
|
20
|
+
gem.files = files
|
21
|
+
end
|
22
|
+
|
23
|
+
if $0 == __FILE__
|
24
|
+
Gem.manage_gems
|
25
|
+
Gem::Builder.new(spec).build
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: win32-pipe
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2008-05-28 00:00:00 -06:00
|
8
|
+
summary: An interface for named pipes on MS Windows
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: djberg96@gmail.com
|
12
|
+
homepage: http://www.rubyforge.org/projects/win32utils
|
13
|
+
rubyforge_project: win32utils
|
14
|
+
description: An interface for named pipes on MS Windows
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Daniel J. Berger
|
31
|
+
files:
|
32
|
+
- examples/test_client.rb
|
33
|
+
- examples/test_client_async.rb
|
34
|
+
- examples/test_server.rb
|
35
|
+
- examples/test_server_async.rb
|
36
|
+
- lib/win32/pipe/client.rb
|
37
|
+
- lib/win32/pipe/server.rb
|
38
|
+
- lib/win32/pipe.rb
|
39
|
+
- test/tc_pipe.rb
|
40
|
+
- test/tc_pipe_client.rb
|
41
|
+
- test/tc_pipe_server.rb
|
42
|
+
- CHANGES
|
43
|
+
- examples
|
44
|
+
- ext
|
45
|
+
- lib
|
46
|
+
- MANIFEST
|
47
|
+
- Rakefile
|
48
|
+
- README
|
49
|
+
- test
|
50
|
+
- win32-pipe.gemspec
|
51
|
+
test_files:
|
52
|
+
- test/tc_pipe.rb
|
53
|
+
- test/tc_pipe_client.rb
|
54
|
+
- test/tc_pipe_server.rb
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
extra_rdoc_files:
|
58
|
+
- CHANGES
|
59
|
+
- README
|
60
|
+
- MANIFEST
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
dependencies: []
|
68
|
+
|