dde 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.
- 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
|