dde 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/bin/dde_main +112 -0
- data/dde.gemspec +94 -0
- data/doc/XLTable_format.doc +0 -0
- data/doc/dde_formats.doc +0 -0
- data/doc/ddeml.d.txt +374 -0
- data/doc/types.txt +159 -0
- data/doc/~$Table_format.doc +0 -0
- data/doc/~$e_formats.doc +0 -0
- data/features/dde.feature +9 -0
- data/features/step_definitions/dde_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/lib/dde/app.rb +85 -0
- data/lib/dde/client.rb +53 -0
- data/lib/dde/dde_string.rb +33 -0
- data/lib/dde/monitor.rb +26 -0
- data/lib/dde/server.rb +41 -0
- data/lib/dde/xl_server.rb +32 -0
- data/lib/dde/xl_table.rb +172 -0
- data/lib/dde.rb +8 -0
- data/spec/dde/app_spec.rb +85 -0
- data/spec/dde/client_spec.rb +176 -0
- data/spec/dde/dde_string_spec.rb +40 -0
- data/spec/dde/monitor_spec.rb +81 -0
- data/spec/dde/server_shared.rb +103 -0
- data/spec/dde/server_spec.rb +21 -0
- data/spec/dde/xl_server_spec.rb +51 -0
- data/spec/dde/xl_table_spec.rb +12 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +49 -0
- metadata +127 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module DDE
|
2
|
+
|
3
|
+
# Class encapsulates DDE string. In addition to normal string behavior,
|
4
|
+
# it also has *handle* that can be passed to dde functions
|
5
|
+
class DdeString < String
|
6
|
+
include Win::DDE
|
7
|
+
|
8
|
+
attr_accessor :handle, # string handle passable to DDEML functions
|
9
|
+
:instance_id, # instance id of DDE app that created this DdeString
|
10
|
+
:code_page, # Windows code page for this string (CP_WINANSI or CP_WINUNICODE)
|
11
|
+
:name # ORIGINAL string used to create this DdeString
|
12
|
+
|
13
|
+
# Given the DDE application instance_id, you cane create DdeStrings
|
14
|
+
# either from regular string or from known DdeString handle
|
15
|
+
def initialize(instance_id, string_or_handle, code_page=CP_WINANSI)
|
16
|
+
@instance_id = instance_id
|
17
|
+
@code_page = code_page
|
18
|
+
|
19
|
+
begin
|
20
|
+
if string_or_handle.is_a? String
|
21
|
+
@name = string_or_handle
|
22
|
+
error unless @handle = dde_create_string_handle(@instance_id, @name, @code_page)
|
23
|
+
else
|
24
|
+
@handle = string_or_handle
|
25
|
+
error unless @name = dde_query_string(@instance_id, @handle, @code_page)
|
26
|
+
end
|
27
|
+
rescue => e
|
28
|
+
end
|
29
|
+
raise DDE::Errors::StringError, "Failed to initialize DDE string: #{e}" unless @handle && @name && !e
|
30
|
+
super @name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/dde/monitor.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module DDE
|
2
|
+
|
3
|
+
# Class encapsulates DDE Monitor that prints all DDE transactions to console
|
4
|
+
class Monitor < App
|
5
|
+
|
6
|
+
# Creates new DDE monitor instance
|
7
|
+
def initialize(init_flags=nil, &callback)
|
8
|
+
init_flags ||=
|
9
|
+
APPCLASS_MONITOR | # this is monitor
|
10
|
+
MF_CALLBACKS | # monitor callback functions
|
11
|
+
MF_CONV | # monitor conversation data
|
12
|
+
MF_ERRORS | # monitor DDEML errors
|
13
|
+
MF_HSZ_INFO | # monitor data handle activity
|
14
|
+
MF_LINKS | # monitor advise loops
|
15
|
+
MF_POSTMSGS | # monitor posted DDE messages
|
16
|
+
MF_SENDMSGS # monitor sent DDE messages
|
17
|
+
|
18
|
+
callback ||= lambda do |*args|
|
19
|
+
p args.unshift(Win::DDE::TYPES[args.shift]).push(Win::DDE::FLAGS[args.pop])
|
20
|
+
1
|
21
|
+
end
|
22
|
+
|
23
|
+
super init_flags, &callback
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/dde/server.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module DDE
|
2
|
+
|
3
|
+
# Class encapsulates DDE Server with basic functionality (starting/stopping named service)
|
4
|
+
class Server < App
|
5
|
+
|
6
|
+
attr_reader :service # service(s) that this Server supports
|
7
|
+
|
8
|
+
def start_service( name, init_flags=nil, &dde_callback )
|
9
|
+
try "Starting service #{name}", DDE::Errors::ServiceError do
|
10
|
+
# Trying to start DDE if it was inactive
|
11
|
+
error unless dde_active? || start_dde( init_flags, &dde_callback )
|
12
|
+
|
13
|
+
# Create DDE string for name (this creates handle that can be passed to DDEML functions)
|
14
|
+
@service = DDE::DdeString.new(@id, name)
|
15
|
+
|
16
|
+
# Register new DDE service, returns true/false success code
|
17
|
+
error unless dde_name_service(@id, @service.handle, DNS_REGISTER)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop_service
|
22
|
+
try "Stopping active service", DDE::Errors::ServiceError do
|
23
|
+
error "Either DDE or service not initialized" unless dde_active? && service_active?
|
24
|
+
|
25
|
+
# Unregister DDE service, returns true/false success code
|
26
|
+
error unless dde_name_service(@id, @service.handle, DNS_UNREGISTER);
|
27
|
+
|
28
|
+
# Free string handle for service name
|
29
|
+
error unless dde_free_string_handle(@id, @service.handle)
|
30
|
+
|
31
|
+
# Clear handle if service successfuly stopped
|
32
|
+
@service = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def service_active?
|
37
|
+
!!@service
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'dde/xl_table'
|
2
|
+
|
3
|
+
module DDE
|
4
|
+
|
5
|
+
# Class encapsulates DDE Server mimicking Excel. It is used to create DDE server with specific service name
|
6
|
+
# (default name 'excel') and store data received by the server via DDE
|
7
|
+
class XlServer < Server
|
8
|
+
|
9
|
+
attr_reader :format, # data format(s) (registered clipboard formats) that server supports
|
10
|
+
:table # data table for data storage
|
11
|
+
|
12
|
+
# Creates new Xl Server instance
|
13
|
+
def initialize(init_flags = nil, &dde_callback )
|
14
|
+
|
15
|
+
@table = DDE::XlTable.new
|
16
|
+
|
17
|
+
# Trying to register or retrieve existing format XlTable
|
18
|
+
try 'Registering format XlTable', DDE::Errors::FormatError do
|
19
|
+
@format = register_clipboard_format("XlTable")
|
20
|
+
end
|
21
|
+
|
22
|
+
super init_flags, &dde_callback
|
23
|
+
end
|
24
|
+
|
25
|
+
# Make 'excel' the default name for named service
|
26
|
+
alias_method :__start_service, :start_service
|
27
|
+
def start_service( name='excel', init_flags=nil, &dde_callback )
|
28
|
+
__start_service( name, init_flags, &dde_callback )
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/dde/xl_table.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
module DDE
|
2
|
+
extend FFI::Library # todo: < Array ?
|
3
|
+
class Conv < FFI::Union
|
4
|
+
layout( :w, :ushort, # word should be 2 bytes, not 8
|
5
|
+
:d, :double, # it is 8 bytes
|
6
|
+
:b, [:char, 8]) # it is 8 bytes
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# XLTable class represents a single chunk of DDE data formatted as an Excel table
|
11
|
+
class XlTable
|
12
|
+
include Win::DDE
|
13
|
+
|
14
|
+
# Received data types
|
15
|
+
TDT_FLOAT = 1
|
16
|
+
TDT_STRING = 2
|
17
|
+
TDT_BOOL = 3
|
18
|
+
TDT_ERROR = 4
|
19
|
+
TDT_BLANK = 5
|
20
|
+
TDT_INT = 6
|
21
|
+
TDT_SKIP = 7
|
22
|
+
TDT_TABLE = 16
|
23
|
+
|
24
|
+
TDT_TYPES = {
|
25
|
+
TDT_FLOAT => 'TDT_FLOAT',
|
26
|
+
TDT_STRING =>'TDT_STRING',
|
27
|
+
TDT_BOOL => 'TDT_BOOL',
|
28
|
+
TDT_ERROR => 'TDT_ERROR',
|
29
|
+
TDT_BLANK => 'TDT_BLANK',
|
30
|
+
TDT_INT => 'TDT_INT',
|
31
|
+
TDT_SKIP => 'TDT_SKIP',
|
32
|
+
TDT_TABLE => 'TDT_TABLE'
|
33
|
+
}
|
34
|
+
|
35
|
+
attr_accessor :topic_item # topic_item
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@table_data = [] # Array contains Arrays of Strings
|
39
|
+
@col = 0
|
40
|
+
@row = 0
|
41
|
+
# omitting separators for now
|
42
|
+
end
|
43
|
+
|
44
|
+
# tests if table data is empty or contains data in inconsistent state
|
45
|
+
def empty?
|
46
|
+
@table_data.empty? ||
|
47
|
+
@row == 0 || @col == 0 ||
|
48
|
+
@row != @table_data.size ||
|
49
|
+
@col != @table_data.first.size # assumes first element is also an Array
|
50
|
+
end
|
51
|
+
|
52
|
+
def data?;
|
53
|
+
!empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def draw
|
57
|
+
return false if empty?
|
58
|
+
|
59
|
+
# omitting separator gymnastics for now
|
60
|
+
cout "-----\n"
|
61
|
+
@table_data.each{|row| row.each {|col| cout col, " "}; cout "\n"}
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_data(dde_handle)
|
65
|
+
# conv = DDE::Conv.new # Union for data conversion
|
66
|
+
|
67
|
+
# Copy DDE data from dde_handle (FFI::MemoryPointer is returned)
|
68
|
+
return nil unless data = dde_get_data(dde_handle) # raise 'DDE data not extracted'
|
69
|
+
offset = 0
|
70
|
+
|
71
|
+
# Make sure that the first block is tdtTable
|
72
|
+
return nil unless data.get_int16(offset) == TDT_TABLE # raise 'DDE data not TDT_TABLE'
|
73
|
+
offset += 2
|
74
|
+
|
75
|
+
# Make sure cb == 4
|
76
|
+
return nil unless data.get_int16(offset) == 4 # raise 'TDT_TABLE data length wrong'
|
77
|
+
offset += 2
|
78
|
+
|
79
|
+
@row = data.get_int16(offset)
|
80
|
+
@col = data.get_int16(offset+2)
|
81
|
+
offset += 4
|
82
|
+
#p "row, col", @row, @col
|
83
|
+
|
84
|
+
# Make sure nonzero row and col
|
85
|
+
return nil if @row == 0 || @col == 0 # raise 'col or row zero in TDT_TABLE'
|
86
|
+
|
87
|
+
@table_data = Array.new(@row){||Array.new}
|
88
|
+
|
89
|
+
r = 0
|
90
|
+
c = 0
|
91
|
+
while offset < data.size
|
92
|
+
type = data.get_int16(offset) # Next data field(s) type
|
93
|
+
cb = data.get_int16(offset+2) # Next data field(s) length in bytes
|
94
|
+
offset += 4
|
95
|
+
|
96
|
+
#p "type #{TDT_TYPES[type]}, cb #{cb}, row #{r}, col #{c}"
|
97
|
+
case type
|
98
|
+
when TDT_FLOAT # Float, 8 bytes per field
|
99
|
+
(cb/8).times do
|
100
|
+
@table_data[r][c] = data.get_float64(offset)
|
101
|
+
offset += 8
|
102
|
+
c += 1
|
103
|
+
if c == @col # end of row
|
104
|
+
c = 0
|
105
|
+
r += 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
when TDT_STRING
|
109
|
+
end_field = offset + cb
|
110
|
+
while offset < end_field do
|
111
|
+
length = data.get_int8(offset)
|
112
|
+
offset += 1
|
113
|
+
@table_data[r][c] = data.get_bytes(offset, length)
|
114
|
+
offset += length
|
115
|
+
c += 1
|
116
|
+
if c == @col # end of row
|
117
|
+
c = 0
|
118
|
+
r += 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
when TDT_BOOL # Bool, 2 bytes per field
|
122
|
+
(cb/2).times do
|
123
|
+
@table_data[r][c] = data.get_int16(offset) == 0
|
124
|
+
offset += 2
|
125
|
+
c += 1
|
126
|
+
if c == @col # end of row
|
127
|
+
c = 0
|
128
|
+
r += 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
when TDT_ERROR # Error enum, 2 bytes per field
|
132
|
+
(cb/2).times do
|
133
|
+
@table_data[r][c] = "Error:#{data.get_int16(offset)}"
|
134
|
+
offset += 2
|
135
|
+
c += 1
|
136
|
+
if c == @col # end of row
|
137
|
+
c = 0
|
138
|
+
r += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
when TDT_BLANK # Number of blank cells, 2 bytes per field
|
142
|
+
(cb/2).times do
|
143
|
+
blanks = data.get_int16(offset)
|
144
|
+
offset += 2
|
145
|
+
blanks.times do
|
146
|
+
@table_data[r][c] = ""
|
147
|
+
c += 1
|
148
|
+
if c == @col # end of row
|
149
|
+
c = 0
|
150
|
+
r += 1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
when TDT_INT # Int, 2 bytes per field
|
155
|
+
(cb/2).times do
|
156
|
+
@table_data[r][c] = data.get_int16(offset) == 0
|
157
|
+
offset += 2
|
158
|
+
c += 1
|
159
|
+
if c == @col # end of row
|
160
|
+
c = 0
|
161
|
+
r += 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
else
|
165
|
+
return nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
#TODO: free FFI::Pointer ? delete []data; // Free memory
|
169
|
+
true # Data acquisition successful
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
data/lib/dde.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
module DDETest
|
4
|
+
describe DDE::App do
|
5
|
+
before(:each ){ @app = DDE::App.new }
|
6
|
+
|
7
|
+
it 'starts with nil id and flags if no arguments given' do
|
8
|
+
@app.id.should == nil
|
9
|
+
@app.init_flags.should == nil
|
10
|
+
@app.dde_active?.should == false
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
|
14
|
+
app = DDE::App.new {|*args|}
|
15
|
+
app.id.should be_an Integer
|
16
|
+
app.id.should_not == 0
|
17
|
+
app.init_flags.should == APPCLASS_STANDARD
|
18
|
+
app.dde_active?.should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#start_dde' do
|
22
|
+
it 'starts DDE with callback and default init_flags' do
|
23
|
+
res = @app.start_dde {|*args|}
|
24
|
+
res.should be_true
|
25
|
+
@app.id.should be_an Integer
|
26
|
+
@app.id.should_not == 0
|
27
|
+
@app.init_flags.should == APPCLASS_STANDARD
|
28
|
+
@app.dde_active?.should == true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns self if success (allows method chain)' do
|
32
|
+
@app.start_dde {|*args|}.should == @app
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'starts DDE with callback and given init_flags' do
|
36
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
|
37
|
+
res.should be_true
|
38
|
+
@app.id.should be_an Integer
|
39
|
+
@app.id.should_not == 0
|
40
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
41
|
+
@app.dde_active?.should == true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises InitError if no callback was given' do
|
45
|
+
lambda{ @app.start_dde}.should raise_error DDE::Errors::InitError
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'reinitializes with new flags and callback if it was already initialized' do
|
49
|
+
@app.start_dde {|*args| 1}
|
50
|
+
old_id = @app.id
|
51
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
|
52
|
+
res.should be_true
|
53
|
+
@app.id.should == old_id
|
54
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
55
|
+
@app.dde_active?.should == true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#stop_dde' do
|
60
|
+
it 'stops DDE that was active' do
|
61
|
+
@app.start_dde {|*args| 1}
|
62
|
+
|
63
|
+
@app.stop_dde
|
64
|
+
@app.id.should == nil
|
65
|
+
@app.dde_active?.should == false
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'preserves init_flags after DDE is stopped (for reinitialization)' do
|
69
|
+
@app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
|
70
|
+
|
71
|
+
@app.stop_dde
|
72
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns self if success (allows method chain)' do
|
76
|
+
@app.start_dde{|*args|}.stop_dde.should == @app
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'raises InitError if dde was not active first' do
|
80
|
+
lambda{ @app.stop_dde}.should raise_error DDE::Errors::InitError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
module DDETest
|
4
|
+
|
5
|
+
describe DDE::Client do
|
6
|
+
# before(:all){@monitor = DDE::Monitor.new}
|
7
|
+
# after(:all){@monitor.stop_dde}
|
8
|
+
|
9
|
+
before(:each ){ @client = DDE::Client.new }
|
10
|
+
# after(:each ){ @client.stop_dde}
|
11
|
+
|
12
|
+
it 'new without parameters creates Client but does not activate DDEML' do
|
13
|
+
@client.id.should == nil
|
14
|
+
@client.conversation.should == nil
|
15
|
+
@client.service.should == nil
|
16
|
+
@client.topic.should == nil
|
17
|
+
@client.dde_active?.should == false
|
18
|
+
@client.conversation_active?.should == false
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'new with attached callback block creates Client and activates DDEML' do
|
22
|
+
client = DDE::Client.new {|*args|}
|
23
|
+
client.id.should be_an Integer
|
24
|
+
client.id.should_not == 0
|
25
|
+
client.dde_active?.should == true
|
26
|
+
client.conversation.should == nil
|
27
|
+
client.conversation_active?.should == false
|
28
|
+
client.service.should == nil
|
29
|
+
client.topic.should == nil
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#start_conversation' do
|
33
|
+
|
34
|
+
context 'with inactive (uninitialized) DDE:' do
|
35
|
+
it 'fails to starts new conversation' do
|
36
|
+
lambda{@client.start_conversation('service', 'topic')}.
|
37
|
+
should raise_error /DDE is not initialized/
|
38
|
+
@client.conversation_active?.should == false
|
39
|
+
lambda{@client.start_conversation(nil, nil)}.
|
40
|
+
should raise_error /DDE is not initialized/
|
41
|
+
@client.conversation_active?.should == false
|
42
|
+
lambda{@client.start_conversation}.
|
43
|
+
should raise_error /DDE is not initialized/
|
44
|
+
@client.conversation_active?.should == false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with active (initialized) DDE AND existing DDE server supporting "service" topic' do
|
49
|
+
before(:each )do
|
50
|
+
@client_calls = []
|
51
|
+
@server_calls = []
|
52
|
+
@client = DDE::Client.new {|*args| @client_calls << args; 1}
|
53
|
+
@server = DDE::Server.new {|*args| @server_calls << args; 1}.start_service('service')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'starts new conversation if DDE is already activated' do
|
57
|
+
res = @client.start_conversation 'service', 'topic'
|
58
|
+
res.should be_true
|
59
|
+
@client.conversation_active?.should == true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns self if success (allows method chain)' do
|
63
|
+
@client.start_conversation('service', 'topic').should == @client
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'sets @conversation, @service and @topic attributes' do
|
67
|
+
@client.start_conversation 'service', 'topic'
|
68
|
+
|
69
|
+
@client.conversation.should be_an Integer
|
70
|
+
@client.conversation.should_not == 0
|
71
|
+
@client.service.should be_a DDE::DdeString
|
72
|
+
@client.service.should == 'service'
|
73
|
+
@client.service.name.should == 'service'
|
74
|
+
@client.conversation.should be_an Integer
|
75
|
+
@client.conversation.should_not == 0
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'initiates XTYP_CONNECT transaction to service`s callback' do
|
79
|
+
@client.start_conversation 'service', 'topic'
|
80
|
+
|
81
|
+
@server_calls.first[0].should == XTYP_CONNECT
|
82
|
+
@server_calls.first[3].should == @client.topic.handle
|
83
|
+
@server_calls.first[4].should == @client.service.handle
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'if server confirms connect, XTYP_CONNECT_CONFIRM transaction to service`s callback follows' do
|
87
|
+
@client.start_conversation 'service', 'topic'
|
88
|
+
|
89
|
+
@server_calls[1][0].should == XTYP_CONNECT_CONFIRM
|
90
|
+
@server_calls[1][3].should == @client.topic.handle
|
91
|
+
@server_calls[1][4].should == @client.service.handle
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'client`s callback receives no transactions' do
|
95
|
+
@client.start_conversation 'service', 'topic'
|
96
|
+
|
97
|
+
p @server_calls, @client.service.handle, @client.topic.handle, @client.conversation
|
98
|
+
@client_calls.should == []
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'fails if another conversation is already in progress' do
|
102
|
+
@client.start_conversation 'service', 'topic'
|
103
|
+
|
104
|
+
lambda{@client.start_conversation 'service1', 'topic1'}.
|
105
|
+
should raise_error /Another conversation already established/
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'fails to start conversation on unsupported service' do
|
109
|
+
lambda{@client.start_conversation('not_a_service', 'topic')}.
|
110
|
+
should raise_error /A client`s attempt to establish a conversation has failed/
|
111
|
+
@client.conversation_active?.should == false
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#stop_conversation' do
|
118
|
+
|
119
|
+
context 'with inactive (uninitialized) DDE:' do
|
120
|
+
it 'fails to stop conversation' do
|
121
|
+
lambda{@client.stop_conversation}.
|
122
|
+
should raise_error /DDE not started/
|
123
|
+
@client.conversation_active?.should == false
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'with active (initialized) DDE AND existing DDE server supporting "service" topic' do
|
129
|
+
before(:each )do
|
130
|
+
@client_calls = []
|
131
|
+
@server_calls = []
|
132
|
+
@client = DDE::Client.new {|*args| @client_calls << args; 1}
|
133
|
+
@server = DDE::Server.new {|*args| @server_calls << args; 1}
|
134
|
+
@server.start_service('service')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'fails to stop conversation' do
|
138
|
+
lambda{@client.stop_conversation}.
|
139
|
+
should raise_error /Conversation not started/
|
140
|
+
@client.conversation_active?.should == false
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'conversation already started' do
|
144
|
+
before(:each ){@client.start_conversation 'service', 'topic'}
|
145
|
+
|
146
|
+
it 'stops conversation' do
|
147
|
+
res = @client.stop_conversation
|
148
|
+
res.should be_true
|
149
|
+
@client.conversation_active?.should == false
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'unsets @conversation, @service and @topic attributes' do
|
153
|
+
@client.stop_conversation
|
154
|
+
@client.conversation.should == nil
|
155
|
+
@client.service.should == nil
|
156
|
+
@client.topic.should == nil
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'does not stop DDE' do
|
160
|
+
@client.stop_conversation
|
161
|
+
@client.dde_active?.should == true
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'initiates XTYP_DISCONNECT transaction to service`s callback' do
|
165
|
+
pending
|
166
|
+
@client.stop_conversation
|
167
|
+
p @server_calls, @client_calls # ?????????? No XTYP_DISCONNECT ? Why ?
|
168
|
+
@server_calls.last[0].should == XTYP_DISCONNECT
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
module DDETest
|
4
|
+
describe DDE::DdeString do
|
5
|
+
before(:each ){ @app = DDE::App.new {|*args|}}
|
6
|
+
|
7
|
+
context ' with valid instance id of active DDE application' do
|
8
|
+
it 'can be created from normal string' do
|
9
|
+
dde_string = DDE::DdeString.new(@app.id, "My_String")
|
10
|
+
dde_string == "My_String"
|
11
|
+
dde_string.handle.should be_an Integer
|
12
|
+
dde_string.handle.should_not == 0
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'can be created from valid DDE string handle' do
|
16
|
+
string_handle = dde_create_string_handle(@app.id, 'My String')
|
17
|
+
dde_string = DDE::DdeString.new(@app.id, string_handle)
|
18
|
+
dde_string == "My_String"
|
19
|
+
dde_string.handle.should be_an Integer
|
20
|
+
dde_string.handle.should_not == 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context ' without instance id of active DDE application' do
|
25
|
+
it 'cannot be created from String' do
|
26
|
+
lambda{DDE::DdeString.new(nil, "My_String")}.should raise_error DDE::Errors::StringError
|
27
|
+
lambda{DDE::DdeString.new(12345, "My_String")}.should raise_error DDE::Errors::StringError
|
28
|
+
lambda{DDE::DdeString.new(0, "My_String")}.should raise_error DDE::Errors::StringError
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'cannot be created from valid string handle' do
|
32
|
+
string_handle = dde_create_string_handle(@app.id, 'My String')
|
33
|
+
lambda{DDE::DdeString.new(nil, string_handle)}.should raise_error DDE::Errors::StringError
|
34
|
+
lambda{DDE::DdeString.new(12345, string_handle)}.should raise_error DDE::Errors::StringError
|
35
|
+
lambda{DDE::DdeString.new(0, string_handle)}.should raise_error DDE::Errors::StringError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|