dde 0.2.2 → 0.2.8
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/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/dde_main +7 -89
- data/dde.gemspec +13 -8
- data/doc/dde_formats.doc +0 -0
- data/exp/exp_client.rb +44 -0
- data/exp/exp_dde_monitor.rb +18 -0
- data/exp/exp_dde_server.rb +36 -0
- data/exp/exp_lib.rb +38 -0
- data/exp/exp_server.rb +44 -0
- data/lib/dde.rb +1 -0
- data/lib/dde/app.rb +12 -5
- data/lib/dde/monitor.rb +70 -3
- data/lib/dde/xl_server.rb +82 -1
- data/lib/dde/xl_table.rb +102 -104
- data/spec/dde/app_shared.rb +85 -0
- data/spec/dde/app_spec.rb +2 -78
- data/spec/dde/client_spec.rb +68 -40
- data/spec/dde/monitor_spec.rb +15 -2
- data/spec/dde/server_shared.rb +12 -11
- data/spec/dde/server_spec.rb +10 -1
- data/spec/dde/xl_server_spec.rb +11 -0
- data/spec/dde/xl_table_spec.rb +1 -1
- metadata +11 -6
- data/doc/~$Table_format.doc +0 -0
- data/doc/~$e_formats.doc +0 -0
data/lib/dde/xl_server.rb
CHANGED
@@ -22,9 +22,90 @@ module DDE
|
|
22
22
|
super init_flags, &dde_callback
|
23
23
|
end
|
24
24
|
|
25
|
+
# HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hConv, HSZ hsz1, HSZ hsz2,
|
26
|
+
# HDDEDATA hData, DWORD dwData1, DWORD dwData2)
|
27
|
+
def default_callback
|
28
|
+
lambda do |type, format, conv, hsz1, hsz2, data, data1, data2|
|
29
|
+
case type
|
30
|
+
when XTYP_CONNECT # Request to connect from client, creating data exchange channel
|
31
|
+
# format:: Not used.
|
32
|
+
# conv:: Not used.
|
33
|
+
# hsz1:: Handle to the topic name.
|
34
|
+
# hsz2:: Handle to the service name.
|
35
|
+
# data:: Handle to DDE data. Meaning of this parameter depends on the type of the current transaction.
|
36
|
+
# data1:: Pointer to a CONVCONTEXT structure that contains context information for the conversation.
|
37
|
+
# If the client is not a Dynamic Data Exchange Management Library (DDEML) application,
|
38
|
+
# this parameter is 0.
|
39
|
+
# data2:: Specifies whether the client is the same application instance as the server. If the
|
40
|
+
# parameter is 1, the client is the same instance. If the parameter is 0, the client
|
41
|
+
# is a different instance.
|
42
|
+
# *Returns*:: A server callback function should return TRUE(1) to allow the client to establish a
|
43
|
+
# conversation on the specified service name and topic name pair, or the function
|
44
|
+
# should return FALSE to deny the conversation. If the callback function returns TRUE(1)
|
45
|
+
# and a conversation is successfully established, the system passes the conversation
|
46
|
+
# todo: handle to the server by issuing an XTYP_CONNECT_CONFIRM transaction to the server's
|
47
|
+
# callback function (unless the server specified the CBF_SKIP_CONNECT_CONFIRMS flag
|
48
|
+
# in the DdeInitialize function).
|
49
|
+
|
50
|
+
if hsz2 == @service.handle
|
51
|
+
cout "Service #{@service}: connect requested by client\n"
|
52
|
+
1 # instead of true # Yes, this server supports requested (name) handle
|
53
|
+
else
|
54
|
+
cout "Service #{@service} unable to process connection request for #{hsz2}\n"
|
55
|
+
DDE_FNOTPROCESSED # 0 instead of false # No, server does not support requested (name) handle
|
56
|
+
end
|
57
|
+
|
58
|
+
when XTYP_POKE # Client initiated XTYP_POKE transaction to push unsolicited data to the server
|
59
|
+
# format:: Specifies the format of the data sent from the server.
|
60
|
+
# conv:: Handle to the conversation.
|
61
|
+
# hsz1:: Handle to the topic name. (Excel: [topic]item ?!)
|
62
|
+
# hsz2:: Handle to the item name.
|
63
|
+
# data_handle:: Handle to the data that the client is sending to the server.
|
64
|
+
# *Returns*:: A server callback function should return the DDE_FACK flag if it processes this
|
65
|
+
# transaction, the DDE_FBUSY flag if it is too busy to process this transaction,
|
66
|
+
# or the DDE_FNOTPROCESSED flag if it rejects this transaction.
|
67
|
+
|
68
|
+
if @table.get_data(data) # Extract incoming DDE data from client into server's table
|
69
|
+
# Converting hsz1 into "[topic]item" string and
|
70
|
+
@table.topic = dde_query_string(@id, hsz1)
|
71
|
+
# @table.draw # Simply printing it for now, no queues
|
72
|
+
@table.timer
|
73
|
+
# // Placing table into print queue
|
74
|
+
# WaitForSingleObject(hMutex1,INFINITE);
|
75
|
+
# q.push(server.xltable);
|
76
|
+
# ReleaseMutex(hMutex1);
|
77
|
+
# // Allowing the table output thread to start...
|
78
|
+
# ReleaseSemaphore(hSemaphore,1,NULL);
|
79
|
+
#
|
80
|
+
DDE_FACK # Transaction successful
|
81
|
+
else
|
82
|
+
cout "Service #{@service} unable to process data request (XTYP_POKE) for #{hsz2}"
|
83
|
+
DDE_FNOTPROCESSED # 0 Transaction NOT successful - return (HDDEDATA)TRUE; ?!(why TRUE, not FALSE)
|
84
|
+
end
|
85
|
+
|
86
|
+
when XTYP_DISCONNECT # DDE client disconnects
|
87
|
+
# server.xltable.Delete();
|
88
|
+
# break;
|
89
|
+
DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
|
90
|
+
|
91
|
+
when XTYP_ERROR # DDE Error
|
92
|
+
# WaitForSingleObject(hMutex, INFINITE);
|
93
|
+
# std::cerr<<"DDE error.\n";
|
94
|
+
# ReleaseMutex(hMutex);
|
95
|
+
# break;
|
96
|
+
DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
|
97
|
+
|
98
|
+
else
|
99
|
+
DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
25
104
|
# Make 'excel' the default name for named service
|
26
105
|
alias_method :__start_service, :start_service
|
27
|
-
|
106
|
+
|
107
|
+
def start_service( name='excel', init_flags=nil, &dde_callback)
|
108
|
+
dde_callback ||= default_callback
|
28
109
|
__start_service( name, init_flags, &dde_callback )
|
29
110
|
end
|
30
111
|
|
data/lib/dde/xl_table.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module DDE
|
2
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
|
-
|
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
9
|
|
10
10
|
# XLTable class represents a single chunk of DDE data formatted as an Excel table
|
11
11
|
class XlTable
|
@@ -32,21 +32,26 @@ module DDE
|
|
32
32
|
TDT_TABLE => 'TDT_TABLE'
|
33
33
|
}
|
34
34
|
|
35
|
-
attr_accessor :
|
35
|
+
attr_accessor :topic, # topic prefix
|
36
|
+
:time, # time spent parsing last transaction data
|
37
|
+
:total_time, # total time spent parsing data
|
38
|
+
:num_trans # number of data transactions
|
36
39
|
|
37
40
|
def initialize
|
38
|
-
@
|
41
|
+
@table = [] # Array contains Arrays of Strings
|
39
42
|
@col = 0
|
40
43
|
@row = 0
|
44
|
+
@total_time = 0
|
45
|
+
@total_records = 0
|
41
46
|
# omitting separators for now
|
42
47
|
end
|
43
48
|
|
44
49
|
# tests if table data is empty or contains data in inconsistent state
|
45
50
|
def empty?
|
46
|
-
@
|
51
|
+
@table.empty? ||
|
47
52
|
@row == 0 || @col == 0 ||
|
48
|
-
@row != @
|
49
|
-
@col != @
|
53
|
+
@row != @table.size ||
|
54
|
+
@col != @table.first.size # assumes first element is also an Array
|
50
55
|
end
|
51
56
|
|
52
57
|
def data?;
|
@@ -55,118 +60,111 @@ module DDE
|
|
55
60
|
|
56
61
|
def draw
|
57
62
|
return false if empty?
|
58
|
-
|
63
|
+
Encoding.default_external = 'cp866'
|
59
64
|
# omitting separator gymnastics for now
|
60
65
|
cout "-----\n"
|
61
|
-
@
|
66
|
+
@table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
|
62
67
|
end
|
63
68
|
|
64
69
|
def get_data(dde_handle)
|
65
|
-
|
70
|
+
start = Time.now
|
66
71
|
|
67
|
-
|
68
|
-
|
69
|
-
offset = 0
|
72
|
+
@offset = 0
|
73
|
+
@pos = 0 #; @c=0; @r=0
|
70
74
|
|
71
|
-
|
72
|
-
return nil unless data
|
73
|
-
|
75
|
+
@data, size = dde_get_data(dde_handle)
|
76
|
+
return nil unless @data && # DDE data is present at given dde_handle
|
77
|
+
read_int == TDT_TABLE && # and, first data block is tdtTable
|
78
|
+
read_int == 4 # and, its length is 4 bytes
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
|
80
|
+
@row = read_int
|
81
|
+
@col = read_int
|
82
|
+
return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
|
78
83
|
|
79
|
-
@
|
80
|
-
@col = data.get_int16(offset+2)
|
81
|
-
offset += 4
|
82
|
-
#p "row, col", @row, @col
|
84
|
+
@table = Array.new(@row){||Array.new}
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
+
while @offset < @data.size
|
87
|
+
type = read_int # Next data field(s) type
|
88
|
+
byte_size = read_int # Next data field(s) length in bytes
|
86
89
|
|
87
|
-
|
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}"
|
90
|
+
#p "type #{TDT_TYPES[type]}, cb #{byte_size}, row #{@pos/@col}, col #{@pos%@col}"
|
97
91
|
case type
|
98
|
-
when
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
92
|
+
when TDT_STRING # Strings, length byte followed by chars, no zero termination
|
93
|
+
field_end = @offset + byte_size
|
94
|
+
while @offset < field_end do
|
95
|
+
length = read_char
|
96
|
+
self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
|
97
|
+
@offset += length
|
120
98
|
end
|
121
|
-
when
|
122
|
-
(
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
99
|
+
when TDT_FLOAT # Float, 8 bytes (used to represent Integers too in Quik!)
|
100
|
+
(byte_size/8).times do
|
101
|
+
# self.table = read_double
|
102
|
+
# end
|
103
|
+
float_or_int = @data.get_float64(@offset)
|
104
|
+
@offset += 8
|
105
|
+
int = float_or_int.round
|
106
|
+
self.table = float_or_int == int ? int : float_or_int
|
163
107
|
end
|
108
|
+
when TDT_BLANK # Number of blank cells, 2 bytes
|
109
|
+
(byte_size/2).times { read_int.times { self.table = "" } }
|
110
|
+
when TDT_SKIP # Number of cells to skip, 2 bytes - in Quik, it means that these cells contain 0
|
111
|
+
(byte_size/2).times { read_int.times { self.table = 0 } }
|
112
|
+
when TDT_INT # Int, 2 bytes
|
113
|
+
(byte_size/2).times { self.table = read_int }
|
114
|
+
when TDT_BOOL # Bool, 2 bytes 0/1
|
115
|
+
(byte_size/2).times { self.table = read_int == 0 }
|
116
|
+
when TDT_ERROR # Error enum, 2 bytes
|
117
|
+
(byte_size/2).times { self.table = "Error:#{read_int}" }
|
164
118
|
else
|
119
|
+
cout "Type: #{type}, #{TDT_TYPES[type]}"
|
165
120
|
return nil
|
166
121
|
end
|
167
122
|
end
|
168
|
-
#TODO: free FFI::Pointer ? delete []data;
|
123
|
+
#TODO: free FFI::Pointer ? delete []data; // Free memory
|
124
|
+
@time = Time.now - start
|
125
|
+
@total_time += @time
|
126
|
+
@total_records += @row
|
169
127
|
true # Data acquisition successful
|
170
128
|
end
|
129
|
+
|
130
|
+
def timer
|
131
|
+
cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
|
132
|
+
@total_time} s(#{@total_time/@total_records} s/rec)\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
def read_char
|
136
|
+
@offset += 1
|
137
|
+
@data.get_int8(@offset-1)
|
138
|
+
end
|
139
|
+
|
140
|
+
def read_int
|
141
|
+
@offset += 2
|
142
|
+
@data.get_int16(@offset-2)
|
143
|
+
end
|
144
|
+
|
145
|
+
def read_double
|
146
|
+
@offset += 8
|
147
|
+
@data.get_float64(@offset-8)
|
148
|
+
end
|
149
|
+
|
150
|
+
def read_bytes(length=1)
|
151
|
+
@offset += length
|
152
|
+
@data.get_bytes(@offset-length, length)
|
153
|
+
end
|
154
|
+
|
155
|
+
def table=(value)
|
156
|
+
# @table[@r][@c] = value
|
157
|
+
# todo: Add code for adding value to data row here (pack?)
|
158
|
+
# @c+=1
|
159
|
+
# if @c == @col
|
160
|
+
# @c =0
|
161
|
+
# @r+=1
|
162
|
+
# end
|
163
|
+
# todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
|
164
|
+
|
165
|
+
@table[@pos/@col][@pos%@col] = value
|
166
|
+
@pos += 1
|
167
|
+
end
|
168
|
+
|
171
169
|
end
|
172
170
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DDETest
|
2
|
+
shared_examples_for "DDE App" do
|
3
|
+
before(:each ){ @app = described_class.new }
|
4
|
+
after(:each ){ @app.stop_dde if @app.dde_active?}
|
5
|
+
|
6
|
+
it 'starts with nil id and flags if no arguments given' do
|
7
|
+
@app.id.should == nil
|
8
|
+
@app.init_flags.should == nil
|
9
|
+
@app.dde_active?.should == false
|
10
|
+
end unless described_class == DDE::Monitor
|
11
|
+
|
12
|
+
it 'starts DDE (initializes as STANDARD DDE app) with given callback block' do
|
13
|
+
@app = described_class.new {|*args|}
|
14
|
+
@app.id.should be_an Integer
|
15
|
+
@app.id.should_not == 0
|
16
|
+
@app.init_flags.should == APPCLASS_STANDARD
|
17
|
+
@app.dde_active?.should == true
|
18
|
+
end unless described_class == DDE::Monitor
|
19
|
+
|
20
|
+
describe '#start_dde' do
|
21
|
+
it 'starts DDE with callback and default init_flags' do
|
22
|
+
res = @app.start_dde {|*args|}
|
23
|
+
res.should be_true
|
24
|
+
@app.id.should be_an Integer
|
25
|
+
@app.id.should_not == 0
|
26
|
+
@app.init_flags.should == APPCLASS_STANDARD
|
27
|
+
@app.dde_active?.should == true
|
28
|
+
end unless described_class == DDE::Monitor
|
29
|
+
|
30
|
+
it 'returns self if success (allows method chain)' do
|
31
|
+
@app.start_dde {|*args|}.should == @app
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'starts DDE with callback and given init_flags' do
|
35
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args|}
|
36
|
+
res.should be_true
|
37
|
+
@app.id.should be_an Integer
|
38
|
+
@app.id.should_not == 0
|
39
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
40
|
+
@app.dde_active?.should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises InitError if no callback was given' do
|
44
|
+
lambda{ @app.start_dde}.should raise_error DDE::Errors::InitError
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'reinitializes with new flags and callback if it was already initialized' do
|
48
|
+
@app.start_dde {|*args| 1}
|
49
|
+
old_id = @app.id
|
50
|
+
res = @app.start_dde( APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS ){|*args| 2}
|
51
|
+
res.should be_true
|
52
|
+
@app.id.should == old_id
|
53
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
54
|
+
@app.dde_active?.should == true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#stop_dde' do
|
59
|
+
it 'stops DDE that was active' do
|
60
|
+
@app.start_dde {|*args| 1}
|
61
|
+
|
62
|
+
@app.stop_dde
|
63
|
+
@app.id.should == nil
|
64
|
+
@app.dde_active?.should == false
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'preserves init_flags after DDE is stopped (for reinitialization)' do
|
68
|
+
@app.start_dde(APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS) {|*args| 1}
|
69
|
+
|
70
|
+
@app.stop_dde
|
71
|
+
@app.init_flags.should == APPCLASS_STANDARD | CBF_FAIL_CONNECTIONS
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns self if success (allows method chain)' do
|
75
|
+
@app.start_dde{|*args|}.stop_dde.should == @app
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'raises InitError if dde was not active first' do
|
79
|
+
lambda{ @app.stop_dde}.should raise_error DDE::Errors::InitError
|
80
|
+
end
|
81
|
+
end unless described_class == DDE::Monitor
|
82
|
+
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/spec/dde/app_spec.rb
CHANGED
@@ -1,85 +1,9 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/app_shared')
|
2
3
|
|
3
4
|
module DDETest
|
4
5
|
describe DDE::App do
|
5
|
-
|
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
|
6
|
+
it_should_behave_like "DDE App"
|
83
7
|
|
84
8
|
end
|
85
9
|
end
|