dde 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.8
1
+ 0.2.9
@@ -20,11 +20,14 @@ end
20
20
  cout "Starting DDE server with service 'excel'\n"
21
21
  server = DDE::XlServer.new.start_service
22
22
 
23
- msg = Msg.new # pointer to Msg FFI struct
23
+ # Command line args define actions to be run after each successful DDE data transaction
24
+ server.actions = ARGV.empty? ? [:timer] : ARGV
25
+
26
+ # Starting message loop (necessary for DDE message processing)
27
+ cout "Starting DDE message loop\n"
24
28
 
25
- # Starting message loop (necessary for DDE processing)
26
- cout "Starting message loop\n"
29
+ msg = Msg.new # pointer to Msg FFI struct
27
30
  while msg = get_message(msg)
28
- translate_message(msg);
29
- dispatch_message(msg);
31
+ translate_message(msg);
32
+ dispatch_message(msg);
30
33
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dde}
8
- s.version = "0.2.8"
8
+ s.version = "0.2.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["arvicco"]
12
- s.date = %q{2010-03-17}
12
+ s.date = %q{2010-03-23}
13
13
  s.default_executable = %q{dde_main}
14
14
  s.description = %q{Server that mimics Excel receiving XLTable data via DDE protocol}
15
15
  s.email = %q{arvitallian@gmail.com}
@@ -13,7 +13,7 @@ module DDE
13
13
  # super init_flags, &dde_callback
14
14
  # end
15
15
 
16
- # establish a conversation with a server application that supports the specified service
16
+ # Establish a conversation with a server application that supports the specified service
17
17
  # name and topic name pair.
18
18
  def start_conversation( service=nil, topic=nil )
19
19
  try "Starting conversation #{service} #{topic}", DDE::Errors::ClientError do
@@ -29,6 +29,7 @@ module DDE
29
29
  end
30
30
  end
31
31
 
32
+ # Stops active conversation, raises error if no conversations active
32
33
  def stop_conversation
33
34
  try "Stopping conversation", DDE::Errors::ClientError do
34
35
  error "DDE not started" unless dde_active?
@@ -45,6 +46,55 @@ module DDE
45
46
  end
46
47
  end
47
48
 
49
+ # Sends XTYP_POKE transaction to server if conversation was already established.
50
+ # data:: data being sent (will be coerced to String unless is already a (packed) String)
51
+ # format:: standard clipboard format of submitted data item (default CF_TEXT)
52
+ def send_data( data, format = CF_TEXT, item = "" )
53
+ data_pointer = FFI::MemoryPointer.from_string(data.to_s)
54
+ result, trans_id = start_transaction(XTYP_POKE, data_pointer, data_pointer.size, format, item)
55
+ result
56
+ end
57
+
58
+ # Initiates transaction to server if conversation was already established.
59
+ # transaction_type:: XTYP_ADVSTART, XTYP_ADVSTOP, XTYP_EXECUTE, XTYP_POKE, XTYP_REQUEST
60
+ # data_pointer:: pointer to data being sent (either FFI::MemoryPointer or DDE data_handle)
61
+ # cb:: data set size (or -1 to indicate that data_pointer is in fact DDE data_handle)
62
+ # format:: standard clipboard format of submitted data item (default CF_TEXT)
63
+ # item:: item to which transaction is related (String, DdeString or DDE string handle)
64
+ # timeout:: timeout in milliseconds or TIMEOUT_ASYNC to indicate async transaction
65
+ #
66
+ # *Returns*:: A pair of [result, trans_id]. Result is nil for failed transactions,
67
+ # DDE data handle for synchronous transactions in which the client expects data from the server,
68
+ # nonzero for successful transactions where clients does not expect data from server.
69
+ # Trans_id: for asynchronous transactions, a unique transaction identifier for use with the
70
+ # DdeAbandonTransaction function and the XTYP_XACT_COMPLETE transaction. For synchronous transactions,
71
+ # the low-order word of this variable contains any applicable DDE_ flags resulting from the transaction.
72
+ #
73
+ def start_transaction( transaction_type, data_pointer=nil, cb = data_pointer ? data_pointer.size : 0,
74
+ format=CF_TEXT, item=0, timeout=1000)
75
+
76
+ result = nil
77
+ trans_id = FFI::MemoryPointer.new(:uint32).put_uint32(0,0)
78
+
79
+ try "Sending data to server", DDE::Errors::ClientError do
80
+ error "DDE not started" unless dde_active?
81
+ error "Conversation not started" unless conversation_active?
82
+
83
+ item_handle = case item
84
+ when String
85
+ DDE::DdeString.new(@id, service).handle
86
+ when DdeString
87
+ item.handle
88
+ else
89
+ item
90
+ end
91
+
92
+ error unless result = dde_client_transaction(data_pointer, cb, @conversation, item_handle,
93
+ format, transaction_type, timeout, trans_id)
94
+ end
95
+ [result, trans_id.get_uint32(0)]
96
+ end
97
+
48
98
  def conversation_active?
49
99
  !!@conversation
50
100
  end
@@ -26,7 +26,7 @@ module DDE
26
26
  end
27
27
  rescue => e
28
28
  end
29
- raise DDE::Errors::StringError, "Failed to initialize DDE string: #{e}" unless @handle && @name && !e
29
+ raise DDE::Errors::StringError, "Failed to initialize DDE string: #{e} #{e.backtrace.join("\n")}" unless @handle && @name && !e
30
30
  super @name
31
31
  end
32
32
  end
@@ -6,7 +6,7 @@ module DDE
6
6
  attr_accessor :print, :calls
7
7
 
8
8
  # Creates new DDE monitor instance
9
- def initialize(init_flags=nil, &callback)
9
+ def initialize(init_flags=nil, print = nil, &callback)
10
10
  init_flags ||=
11
11
  APPCLASS_MONITOR | # this is monitor
12
12
  MF_CALLBACKS | # monitor callback functions
@@ -17,6 +17,7 @@ module DDE
17
17
  MF_POSTMSGS | # monitor posted DDE messages
18
18
  MF_SENDMSGS # monitor sent DDE messages
19
19
 
20
+ @print = print
20
21
  @calls = []
21
22
 
22
23
  callback ||= lambda do |*args|
@@ -7,12 +7,14 @@ module DDE
7
7
  class XlServer < Server
8
8
 
9
9
  attr_reader :format, # data format(s) (registered clipboard formats) that server supports
10
- :table # data table for data storage
10
+ :data # data storage/processor
11
+
12
+ attr_accessor :actions # Actions to be run on table after each successful DDE input (:draw, :debug, :timer)
11
13
 
12
14
  # Creates new Xl Server instance
13
15
  def initialize(init_flags = nil, &dde_callback )
14
16
 
15
- @table = DDE::XlTable.new
17
+ @data = DDE::XlTable.new
16
18
 
17
19
  # Trying to register or retrieve existing format XlTable
18
20
  try 'Registering format XlTable', DDE::Errors::FormatError do
@@ -25,88 +27,63 @@ module DDE
25
27
  # HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hConv, HSZ hsz1, HSZ hsz2,
26
28
  # HDDEDATA hData, DWORD dwData1, DWORD dwData2)
27
29
  def default_callback
28
- lambda do |type, format, conv, hsz1, hsz2, data, data1, data2|
30
+ lambda do |type, format, conv, hsz1, hsz2, data_handle, data1, data2|
29
31
  case type
30
32
  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).
33
+ # format:: Not used.
34
+ # conv:: Not used.
35
+ # hsz1:: Handle to the topic name.
36
+ # hsz2:: Handle to the service name.
37
+ # data_handle:: Handle to DDE data. Meaning depends on the type of the current transaction.
38
+ # data1:: Pointer to a CONVCONTEXT structure that contains context information for the conversation.
39
+ # If the client is not a DDEML application, this parameter is 0.
40
+ # data2:: Specifies whether the client is the same application instance as the server. If the parameter
41
+ # is 1, the client is the same instance. If it is 0, the client is a different instance.
42
+ # *Returns*:: A server callback function should return TRUE(1, but DDE_FACK works just fine too)
43
+ # to allow the client to establish a conversation on the specified service name and topic
44
+ # name pair, or the function should return FALSE to deny the conversation. If the callback
45
+ # function returns TRUE and a conversation is successfully established, the system passes
46
+ # the conversation handle to the server by issuing an XTYP_CONNECT_CONFIRM transaction to
47
+ # the server's callback function (unless the server specified the CBF_SKIP_CONNECT_CONFIRMS
48
+ # flag in the DdeInitialize function).
49
49
 
50
50
  if hsz2 == @service.handle
51
51
  cout "Service #{@service}: connect requested by client\n"
52
- 1 # instead of true # Yes, this server supports requested (name) handle
52
+ DDE_FACK # instead of true # Yes, this server supports requested (name) handle
53
53
  else
54
54
  cout "Service #{@service} unable to process connection request for #{hsz2}\n"
55
55
  DDE_FNOTPROCESSED # 0 instead of false # No, server does not support requested (name) handle
56
56
  end
57
57
 
58
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
- #
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
+ @data.topic = dde_query_string(@id, hsz1) # Convert hsz1 into "[topic]item" string and
69
+ if @data.receive(data_handle) # Receive incoming DDE data and process it
70
+
71
+ # Perform actions like :draw, :debug, :timer, :formats on received data (default :timer)
72
+ @actions.each{|action| @data.send(action.to_sym)}
80
73
  DDE_FACK # Transaction successful
81
74
  else
75
+ @data.debug
82
76
  cout "Service #{@service} unable to process data request (XTYP_POKE) for #{hsz2}"
83
77
  DDE_FNOTPROCESSED # 0 Transaction NOT successful - return (HDDEDATA)TRUE; ?!(why TRUE, not FALSE)
84
78
  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
79
  else
99
80
  DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
100
81
  end
101
82
  end
102
83
  end
103
84
 
104
- # Make 'excel' the default name for named service
105
- alias_method :__start_service, :start_service
106
-
107
85
  def start_service( name='excel', init_flags=nil, &dde_callback)
108
- dde_callback ||= default_callback
109
- __start_service( name, init_flags, &dde_callback )
86
+ super name, init_flags, &dde_callback || default_callback
110
87
  end
111
88
 
112
89
  end
@@ -66,13 +66,25 @@ module DDE
66
66
  @table.each{|row| cout @topic; row.each {|col| cout " #{col}"}; cout "\n"}
67
67
  end
68
68
 
69
- def get_data(dde_handle)
69
+ def debug
70
+ return false if empty?
71
+ Encoding.default_external = 'cp866'
72
+ # omitting separator gymnastics for now
73
+ cout "-----\n"
74
+ @table.each_with_index{|row, i| (cout @topic, i; p row) unless row == []}
75
+ STDIN.gets
76
+ end
77
+
78
+ def receive(data_handle, mode = :collect)
79
+ $mode = mode
70
80
  start = Time.now
71
81
 
72
82
  @offset = 0
73
83
  @pos = 0 #; @c=0; @r=0
74
84
 
75
- @data, size = dde_get_data(dde_handle)
85
+ @data, total_size = dde_get_data(data_handle) #dde_access_data(dde_handle)
86
+ p @data.get_bytes(0, total_size) if $mode == :debug
87
+
76
88
  return nil unless @data && # DDE data is present at given dde_handle
77
89
  read_int == TDT_TABLE && # and, first data block is tdtTable
78
90
  read_int == 4 # and, its length is 4 bytes
@@ -81,40 +93,42 @@ module DDE
81
93
  @col = read_int
82
94
  return nil unless @row != 0 && @col != 0 # Make sure nonzero row and col
83
95
 
96
+ p "data set size #{total_size}, row #{@row}, col #{@col}" if $mode == :debug
97
+ @strings = @floats = @flints = @ints = @blanks = @skips = @bools = @errors = 0
98
+
84
99
  @table = Array.new(@row){||Array.new}
85
100
 
86
- while @offset < @data.size
101
+ while @offset <= total_size-4 # Need at least 4 bytes ahead to read data type and size
87
102
  type = read_int # Next data field(s) type
88
- byte_size = read_int # Next data field(s) length in bytes
103
+ size = read_int # Next data field(s) length in bytes
89
104
 
90
- #p "type #{TDT_TYPES[type]}, cb #{byte_size}, row #{@pos/@col}, col #{@pos%@col}"
105
+ p "type #{TDT_TYPES[type]}, cb #{size}, row #{@pos/@col}, col #{@pos%@col}" if $mode == :debug
91
106
  case type
92
107
  when TDT_STRING # Strings, length byte followed by chars, no zero termination
93
- field_end = @offset + byte_size
108
+ field_end = @offset + size
94
109
  while @offset < field_end do
95
110
  length = read_char
96
111
  self.table = @data.get_bytes(@offset, length) #read_bytes(length)#.force_encoding('CP1251').encode('CP866')
97
112
  @offset += length
113
+ @strings += 1
98
114
  end
99
115
  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)
116
+ (size/8).times do
117
+ float_or_int = @data.get_float64(@offset) # self.table = read_double
104
118
  @offset += 8
105
119
  int = float_or_int.round
106
- self.table = float_or_int == int ? int : float_or_int
120
+ self.table = float_or_int == int ? (@flints += 1; int) : (@floats +=1; float_or_int)
107
121
  end
108
122
  when TDT_BLANK # Number of blank cells, 2 bytes
109
- (byte_size/2).times { read_int.times { self.table = "" } }
123
+ (size/2).times { read_int.times { self.table = ""; @blanks += 1 } }
110
124
  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 } }
125
+ (size/2).times { read_int.times { self.table = 0; @skips += 1 } }
112
126
  when TDT_INT # Int, 2 bytes
113
- (byte_size/2).times { self.table = read_int }
127
+ (size/2).times { self.table = read_int; @ints += 1 }
114
128
  when TDT_BOOL # Bool, 2 bytes 0/1
115
- (byte_size/2).times { self.table = read_int == 0 }
129
+ (size/2).times { self.table = read_int == 0; @bools += 1 }
116
130
  when TDT_ERROR # Error enum, 2 bytes
117
- (byte_size/2).times { self.table = "Error:#{read_int}" }
131
+ (size/2).times { self.table = "Error:#{read_int}"; @errors += 1 }
118
132
  else
119
133
  cout "Type: #{type}, #{TDT_TYPES[type]}"
120
134
  return nil
@@ -124,6 +138,7 @@ module DDE
124
138
  @time = Time.now - start
125
139
  @total_time += @time
126
140
  @total_records += @row
141
+ #dde_unaccess_data(dde_handle)
127
142
  true # Data acquisition successful
128
143
  end
129
144
 
@@ -131,6 +146,11 @@ module DDE
131
146
  cout "Last: #{@row} in #{@time} s(#{@time/@row} s/rec), total: #{@total_records} in #{
132
147
  @total_time} s(#{@total_time/@total_records} s/rec)\n"
133
148
  end
149
+
150
+ def formats
151
+ cout "Strings #{@strings} Floats #{@floats} FlInts #{@flints} Ints #{@ints} Blanks #{
152
+ @blanks} Skips #{@skips} Bools #{@bools} Errors #{@errors}\n"
153
+ end
134
154
 
135
155
  def read_char
136
156
  @offset += 1
@@ -161,7 +181,7 @@ module DDE
161
181
  # @r+=1
162
182
  # end
163
183
  # todo: Add code for (sync!) publishing of assembled data row here (bunny? rosetta_queue?)
164
-
184
+ p value if $mode == :debug
165
185
  @table[@pos/@col][@pos%@col] = value
166
186
  @pos += 1
167
187
  end
@@ -16,7 +16,7 @@ module DDETest
16
16
  @app.init_flags.should == APPCLASS_STANDARD
17
17
  @app.dde_active?.should == true
18
18
  end unless described_class == DDE::Monitor
19
-
19
+
20
20
  describe '#start_dde' do
21
21
  it 'starts DDE with callback and default init_flags' do
22
22
  res = @app.start_dde {|*args|}
@@ -4,6 +4,5 @@ require File.expand_path(File.dirname(__FILE__) + '/app_shared')
4
4
  module DDETest
5
5
  describe DDE::App do
6
6
  it_should_behave_like "DDE App"
7
-
8
7
  end
9
8
  end
@@ -3,32 +3,6 @@ require File.expand_path(File.dirname(__FILE__) + '/app_shared')
3
3
 
4
4
  module DDETest
5
5
 
6
- def start_callback_recorder
7
- @client_calls = []
8
- @server_calls = []
9
- @client = DDE::Client.new {|*args| @client_calls << extract_values(*args); 1}
10
- @server = DDE::Server.new do |*args|
11
- @server_calls << extract_values(*args)
12
- #puts "#{Time.now.strftime('%T.%6N')} #{extract_values(*args)}"
13
- DDE_FACK
14
- end
15
- @server.start_service('service')
16
- end
17
-
18
- def stop_callback_recorder
19
- @client.stop_conversation if @client.conversation_active?
20
- #@client.stop_dde if @client.dde_active?
21
- @server.stop_service if @server.service_active?
22
- @server.stop_dde if @server.dde_active?
23
- end
24
-
25
- def extract_values(type, format, conv, hsz1, hsz2, data, data1, data2)
26
- [Win::DDE::TYPES[type], format, conv,
27
- dde_query_string(@client.id, hsz1),
28
- dde_query_string(@client.id, hsz2),
29
- data, data1, data2]
30
- end
31
-
32
6
  describe DDE::Client do
33
7
  before(:each){ @client = DDE::Client.new }
34
8
  after(:each){ @client.stop_dde if @client.dde_active?}
@@ -103,7 +77,7 @@ module DDETest
103
77
  it 'initiates XTYP_CONNECT transaction to service`s callback' do
104
78
  @client.start_conversation 'service', 'topic'
105
79
 
106
- @server_calls.first[0].should == 'XTYP_CONNECT'
80
+ @server_calls.first[0].should == :XTYP_CONNECT
107
81
  @server_calls.first[3].should == @client.topic
108
82
  @server_calls.first[4].should == @client.service
109
83
  end
@@ -112,7 +86,7 @@ module DDETest
112
86
  @client.start_conversation 'service', 'topic'
113
87
 
114
88
  # p @server_calls, @client_calls # ?????????? No XTYP_DISCONNECT ? Why ?
115
- @server_calls[1][0].should == 'XTYP_CONNECT_CONFIRM'
89
+ @server_calls[1][0].should == :XTYP_CONNECT_CONFIRM
116
90
  @server_calls[1][3].should == @client.topic
117
91
  @server_calls[1][4].should == @client.service
118
92
  end
@@ -157,9 +131,7 @@ module DDETest
157
131
 
158
132
  context 'with active (initialized) DDE AND existing DDE server supporting "service" topic' do
159
133
  before(:each ){start_callback_recorder}
160
- after(:each )do
161
- stop_callback_recorder
162
- end
134
+ after(:each ){stop_callback_recorder}
163
135
 
164
136
  it 'fails to stop conversation' do
165
137
  lambda{@client.stop_conversation}.
@@ -193,12 +165,36 @@ module DDETest
193
165
  pending
194
166
  @client.stop_conversation
195
167
  p @server_calls, @client_calls # ?????????? No XTYP_DISCONNECT ? Why ?
196
- @server_calls.last[0].should == 'XTYP_DISCONNECT'
168
+ @server_calls.last[0].should == :XTYP_DISCONNECT
197
169
  end
198
170
 
199
171
  end # context 'conversation already started'
200
172
 
201
173
  end # context 'with active (initialized) DDE AND existing DDE server supporting "service" topic'
202
174
  end # describe '#stop_conversation'
175
+
176
+ describe '#send_data' do
177
+ context 'with active (initialized) DDE AND existing DDE server supporting "service" topic' do
178
+ before(:each )do
179
+ start_callback_recorder do |*args|
180
+ @server_calls << extract_values(*args)
181
+ if args[0] == XTYP_POKE
182
+ @data, @size = dde_get_data(args[5])
183
+ end
184
+ DDE_FACK
185
+ end
186
+ @client.start_conversation 'service', 'topic'
187
+ end
188
+ after(:each ){stop_callback_recorder}
189
+
190
+ it 'sends data to server' do
191
+ @client.send_data TEST_STRING, CF_TEXT, "item"
192
+ @server_calls.last[0].should == :XTYP_POKE
193
+ @data.get_bytes(0, @size).rstrip.should == TEST_STRING
194
+ end
195
+
196
+ end # context 'with active (initialized) DDE'
197
+ end # describe #send_data
198
+
203
199
  end # describe DDE::Client
204
200
  end
@@ -3,11 +3,15 @@ require File.expand_path(File.dirname(__FILE__) + '/app_shared')
3
3
 
4
4
  module DDETest
5
5
 
6
+ describe DDE::Monitor, " in general" do
7
+ it_should_behave_like "DDE App"
8
+ end
9
+
6
10
  describe DDE::Monitor do
7
- before(:each){ @monitor = DDE::Monitor.new }
8
- after(:each){ @monitor.stop_dde }
11
+ before(:each){ }
12
+ after(:each){ @monitor.stop_dde if @monitor.dde_active? }
13
+ # SEEMS LIKE IT DOESN'T stop system from sending :XTYP_MONITOR transactions to already dead callback :(
9
14
 
10
- it_should_behave_like "DDE App"
11
15
 
12
16
  it 'starts without constructor parameters' do
13
17
  @monitor = DDE::Monitor.new
@@ -26,69 +30,5 @@ module DDETest
26
30
  MF_SENDMSGS # monitor sent DDE messages
27
31
  end
28
32
 
29
- # context 'with existing DDE clients and server supporting "service" topic' do
30
- # before(:each )do
31
- # @client_calls = []
32
- # @server_calls = []
33
- # @client = DDE::Client.new {|*args| @client_calls << args; 1}
34
- # @server = DDE::Server.new {|*args| @server_calls << args; 1}
35
- # @server.start_service('service')
36
- # end
37
- #
38
- # it 'starts new conversation if DDE is already activated' do
39
- # res = @client.start_conversation 'service', 'topic'
40
- # res.should == true
41
- # @client.conversation_active?.should == true
42
- # end
43
- #
44
- # it 'sets @conversation, @service and @topic attributes' do
45
- # @client.start_conversation 'service', 'topic'
46
- #
47
- # @client.conversation.should be_an Integer
48
- # @client.conversation.should_not == 0
49
- # @client.service.should be_a DDE::DdeString
50
- # @client.service.should == 'service'
51
- # @client.service.name.should == 'service'
52
- # @client.conversation.should be_an Integer
53
- # @client.conversation.should_not == 0
54
- # end
55
- #
56
- # it 'initiates XTYP_CONNECT transaction to service`s callback' do
57
- # @client.start_conversation 'service', 'topic'
58
- #
59
- # @server_calls.first[0].should == XTYP_CONNECT
60
- # @server_calls.first[3].should == @client.topic.handle
61
- # @server_calls.first[4].should == @client.service.handle
62
- # end
63
- #
64
- # it 'if server confirms connect, XTYP_CONNECT_CONFIRM transaction to service`s callback follows' do
65
- # @client.start_conversation 'service', 'topic'
66
- #
67
- # @server_calls[1][0].should == XTYP_CONNECT_CONFIRM
68
- # @server_calls[1][3].should == @client.topic.handle
69
- # @server_calls[1][4].should == @client.service.handle
70
- # end
71
- #
72
- # it 'client`s callback receives no transactions' do
73
- # @client.start_conversation 'service', 'topic'
74
- #
75
- # p @server_calls, @client.service.handle, @client.topic.handle, @client.conversation
76
- # @client_calls.should == []
77
- # end
78
- #
79
- # it 'fails if another conversation is already in progress' do
80
- # @client.start_conversation 'service', 'topic'
81
- #
82
- # lambda{@client.start_conversation 'service1', 'topic1'}.
83
- # should raise_error /Another conversation already established/
84
- # end
85
- #
86
- # it 'fails to start conversation on unsupported service' do
87
- # lambda{@client.start_conversation('not_a_service', 'topic')}.
88
- # should raise_error /A client`s attempt to establish a conversation has failed/
89
- # @client.conversation_active?.should == false
90
- # end
91
- #
92
- # end
93
33
  end
94
34
  end
@@ -5,78 +5,68 @@ module DDETest
5
5
 
6
6
  it_should_behave_like "DDE App"
7
7
 
8
- it 'new without parameters creates Server but does not activate DDEML or start service' do
9
- @server.id.should == nil
10
- @server.service.should == nil
11
- @server.dde_active?.should == false
12
- @server.service_active?.should == false
13
- end
14
-
15
- it 'new with attached callback block creates Server and activates DDEML, but does not start service' do
16
- @server = described_class.new {|*args|}
17
- @server.id.should be_an Integer
18
- @server.id.should_not == 0
19
- @server.dde_active?.should == true
20
- @server.service.should == nil
21
- @server.service_active?.should == false
22
- end
23
-
24
- describe '#start_service' do
25
-
26
- context 'with inactive (uninitialized) DDE:' do
27
- it 'with attached block, initializes DDE and starts new service' do
28
- @server.start_service('myservice') {|*args|}.should be_true
29
-
30
- @server.service.should be_a DDE::DdeString
31
- @server.service.should == 'myservice'
32
- @server.service.name.should == 'myservice'
33
- @server.service.handle.should be_an Integer
34
- @server.service.handle.should_not == 0
35
- @server.service_active?.should == true
36
- end
8
+ context 'specific to Servers:' do
9
+ before(:each ){ @server = described_class.new {|*args|}}
10
+ after(:each ){ @server.stop_dde if @server.dde_active?}
11
+
12
+ it 'new with attached callback block creates Server and activates DDEML, but does not start service' do
13
+ @server = described_class.new {|*args|}
14
+ @server.id.should be_an Integer
15
+ @server.id.should_not == 0
16
+ @server.dde_active?.should == true
17
+ @server.service.should == nil
18
+ @server.service_active?.should == false
19
+ end
37
20
 
38
- it 'returns self if success (allows method chain)' do
39
- res = @server.start_service('myservice') {|*args|}
40
- res.should == @server
41
- end
21
+ describe '#start_service' do
42
22
 
43
- end
23
+ context 'with inactive (uninitialized) DDE:' do
24
+ it 'with attached block, initializes DDE and starts new service' do
25
+ @server.start_service('myservice') {|*args|}.should be_true
44
26
 
45
- context 'with active (initialized) DDE:' do
46
- before(:each ){ @server = described_class.new {|*args|}}
27
+ @server.service.should be_a DDE::DdeString
28
+ @server.service.should == 'myservice'
29
+ @server.service.name.should == 'myservice'
30
+ @server.service.handle.should be_an Integer
31
+ @server.service.handle.should_not == 0
32
+ @server.service_active?.should == true
33
+ end
47
34
 
48
- it 'starts new service with given name' do
49
- res = @server.start_service 'myservice'
50
- res.should be_true
35
+ it 'returns self if success (allows method chain)' do
36
+ res = @server.start_service('myservice') {|*args|}
37
+ res.should == @server
38
+ end
39
+ end # context 'with inactive (uninitialized) DDE:'
51
40
 
52
- @server.service.should be_a DDE::DdeString
53
- @server.service.should == 'myservice'
54
- @server.service.name.should == 'myservice'
55
- @server.service.handle.should be_an Integer
56
- @server.service.handle.should_not == 0
57
- end
41
+ context 'with active (initialized) DDE:' do
58
42
 
59
- it 'fails to starts new service if name is not a String' do
60
- lambda{@server.start_service(11)}.should raise_error DDE::Errors::ServiceError
61
- @server.service_active?.should == false
62
- end
43
+ it 'starts new service with given name' do
44
+ res = @server.start_service 'myservice'
45
+ res.should be_true
63
46
 
64
- end
65
- end
47
+ @server.service.should be_a DDE::DdeString
48
+ @server.service.should == 'myservice'
49
+ @server.service.name.should == 'myservice'
50
+ @server.service.handle.should be_an Integer
51
+ @server.service.handle.should_not == 0
52
+ end
66
53
 
67
- describe '#stop_service' do
54
+ it 'fails to starts new service if name is not a String' do
55
+ lambda{@server.start_service(11)}.should raise_error DDE::Errors::ServiceError
56
+ @server.service_active?.should == false
57
+ end
58
+ end # context 'with active (initialized) DDE:'
59
+ end # describe '#start_service'
68
60
 
69
- context 'with inactive (uninitialized) DDE:' do
70
- it 'fails to stop service' do
71
- lambda{@server.stop_service}.should raise_error DDE::Errors::ServiceError
61
+ describe '#stop_service' do
62
+ context 'with inactive (uninitialized) DDE:' do
63
+ it 'fails to stop service' do
64
+ lambda{@server.stop_service}.should raise_error DDE::Errors::ServiceError
65
+ end
72
66
  end
73
- end
74
67
 
75
- context 'with active (initialized) DDE:' do
76
- before(:each){ @server = described_class.new {|*args|}}
77
-
78
- context 'with already registered DDE service: "myservice"' do
79
- before(:each){ @server.start_service('myservice')}
68
+ context 'with active (initialized) DDE: and already registered DDE service: "myservice"' do
69
+ before(:each){ @server = described_class.new {|*args|}; @server.start_service('myservice')}
80
70
  after(:each){ @server.stop_service if @server.service_active?}
81
71
 
82
72
  it 'stops previously registered service' do
@@ -95,10 +85,8 @@ module DDETest
95
85
  it 'returns self if success (allows method chain)' do
96
86
  @server.stop_service.should == @server
97
87
  end
98
-
99
- end
100
-
101
- end
102
- end
103
- end
88
+ end # context 'with active (initialized) DDE: and already registered DDE service: "myservice"'
89
+ end # describe '#stop_service'
90
+ end # context 'specific to Servers:'
91
+ end # shared_examples_for "DDE Server"
104
92
  end
@@ -3,6 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '/server_shared')
3
3
 
4
4
  module DDETest
5
5
 
6
+ describe DDE::Server, ' in general:' do
7
+ it_should_behave_like "DDE Server"
8
+ end
9
+
6
10
  describe DDE::Server do
7
11
  before(:each ){ @server = DDE::Server.new }
8
12
  after(:each) do
@@ -10,7 +14,12 @@ module DDETest
10
14
  @server.stop_dde if @server.dde_active?
11
15
  end
12
16
 
13
- it_should_behave_like "DDE Server"
17
+ it 'new without parameters creates Server but does not activate DDEML or start service' do
18
+ @server.id.should == nil
19
+ @server.service.should == nil
20
+ @server.dde_active?.should == false
21
+ @server.service_active?.should == false
22
+ end
14
23
 
15
24
  describe '#start_service' do
16
25
 
@@ -23,8 +32,6 @@ module DDETest
23
32
  lambda{@server.start_service('myservice')}.should raise_error DDE::Errors::ServiceError
24
33
  @server.service_active?.should == false
25
34
  end
26
-
27
- end
28
-
29
- end
35
+ end #describe '#start_service'
36
+ end # describe DDE::Server do
30
37
  end
@@ -3,6 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '/server_shared')
3
3
 
4
4
  module DDETest
5
5
 
6
+ describe DDE::XlServer, ' in general:' do
7
+ it_should_behave_like "DDE Server"
8
+ end
9
+
6
10
  describe DDE::XlServer do
7
11
  before(:each ){ @server = DDE::XlServer.new }
8
12
  after(:each) do
@@ -10,17 +14,15 @@ module DDETest
10
14
  @server.stop_dde if @server.dde_active?
11
15
  end
12
16
 
13
- it_should_behave_like "DDE Server"
14
-
15
- it 'new without parameters creates empty table attribute' do
16
- @server.table.should be_an DDE::XlTable
17
- @server.table.should be_empty
17
+ it 'new without parameters has empty data attribute' do
18
+ @server.data.should be_an DDE::XlTable
19
+ @server.data.should be_empty
18
20
  end
19
21
 
20
- it 'new with attached callback block creates empty table attribute' do
22
+ it 'new with attached callback block has empty data attribute' do
21
23
  server = DDE::XlServer.new {|*args|}
22
- @server.table.should be_an DDE::XlTable
23
- @server.table.should be_empty
24
+ @server.data.should be_an DDE::XlTable
25
+ @server.data.should be_empty
24
26
  end
25
27
 
26
28
  describe '#start_service' do
@@ -55,8 +57,7 @@ module DDETest
55
57
  @server.service.handle.should be_an Integer
56
58
  @server.service.handle.should_not == 0
57
59
  end
58
- end
60
+ end # context 'with active (initialized) DDE:'
59
61
  end # describe '#start_service'
60
-
61
- end
62
+ end # describe DDE::XlServer
62
63
  end
@@ -1,12 +1,51 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  module DDETest
4
+
5
+ DDE_DATA = "\x10\x00\x04\x00\b\x00\t\x00\x02\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x18\x00fffff\x8Ee@\x00\x00\x00\x00\x00y\xB7@\x00\x00\x00\x00\x80b\xC1@\x02\x00\x01\x00\x00\x01\x00(\x00\x9A\x99\x99\x99\x99\x99\xB9?q=\n\xD7\xA3\x98e@)\\\x8F\xC2\xF5`e@\\\x8F\xC2\xF5(\x8Ce@H\xE1z$XLZA\x01\x00H\x00q=\n\xD7\xA3\x88e@\x00\x00\x00\x00\x80\xED\xC9@\x00\x00\x00\x00\x00\x00\x00\x00\xB8\x1E\x85\xEBQ e@{\x14\xAEG\xE1z\xF4?\xAEG\xE1z\x14\xCEe@\xA4p=\n\xD7\ve@H\xE1z\x14\xAE\x7Fe@\x00\x00\x80\x94\xC0-\aB\x01\x00H\x00ffff&\xF4\xB2@\x00\x00\x00\x00\x00P\x91@\x00\x00\x00\x00\x00\x00\x00\x00=\n\xD7\xA30\xA2\xB2@ffffff\xEE?\x00\x00\x00\x00\x00$\xB3@\x00\x00\x00\x00\x00\x9A\xB2@=\n\xD7\xA3\xF0\xE1\xB2@\x00\x00\x90\x9DEy\xF0A\x01\x00H\x00\x00\x00\x00\x00\x00\xD0m@\x00\x00\x00\x00\x00\xF9\xBE@\x00\x00\x00\x00\x00\x00\x00\x00\x9A\x99\x99\x99\x99\x99m@\xB8\x1E\x85\xEBQ\xB8\xAE?H\xE1z\x14\xAE\xFFm@\x00\x00\x00\x00\x00hm@\xECQ\xB8\x1E\x85\xC3m@\x00\x00\xC0\x8Fp\x80\xE6A\x01\x00H\x00\xCD\xCC\xCC\xCC\xCC\xBCT@\x00\x00\x00\x00\x80\xAF\xE7@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90T@{\x14\xAEG\xE1z\xA4?fffff\xE6T@\x9A\x99\x99\x99\x99YT@\x1F\x85\xEBQ\xB8\xAET@\x00\x00t\x80r<\x12B\x01\x00\b\x00\xF6(\\\x8F\xC2\n\x97@\x02\x00\b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00H\x00\x83\xC0\xCA\xA1E\xB6\xB3?\x00\x00\x00\x00 \xC8\xF1@\x00\x00\x00\x00\x00\x00\x00\x00@\xA4\xDF\xBE\x0E\x9C\xB3?\xA4p=\n\xD7\xA3\xC0?\t\xF9\xA0g\xB3\xEA\xB3?\xBAk\t\xF9\xA0g\xB3?r\xF9\x0F\xE9\xB7\xAF\xB3?\x00\x00 \xBC\xA35\xE9A"
6
+ DRAW_OUTPUT = "-----
7
+ [G]V
8
+ [G]V 172.45 6009 8901 0.1 172.77 171.03 172.38 6893920.57
9
+ [G]V 172.27 13275 0 169.01 1.28 174.44 168.37 171.99 12443980432
10
+ [G]V 4852.15 1108 0 4770.19 0.95 4900 4762 4833.94 4422130137
11
+ [G]V 238.5 7929 0 236.8 0.06 239.99 235.25 238.11 3020129406
12
+ [G]V 82.95 48508 0 82.25 0.04 83.6 81.4 82.73 19580887069
13
+ [G]V 1474.69
14
+ [G]V 0.077 72834 0 0.0766 0.13 0.0778 0.0758 0.0769 3383565793
15
+ Last: 8 in 0.0 s(0.0 s/rec), total: 8 in 0.0 s(0.0 s/rec)"
16
+
4
17
  describe DDE::XlTable do
18
+ before(:each) {@data = DDE::XlTable.new}
19
+
20
+ it 'starts out empty and without topic' do
21
+ @data.should be_empty
22
+ @data.topic.should == nil
23
+ end
5
24
 
6
- it 'starts out empty and without item/topic' do
7
- table = DDE::XlTable.new
8
- table.should be_empty
9
- table.topic.should == nil
25
+ context 'with server/client pair and started conversation' do
26
+ before(:each )do
27
+ start_callback_recorder do|*args|
28
+ @server_calls << extract_values(*args)
29
+ if args[0] == XTYP_POKE
30
+ p extract_values(*args)
31
+ @data.receive(args[5]) #, :debug)
32
+ end
33
+ DDE_FACK
34
+ end
35
+ @client.start_conversation 'service', 'topic'
36
+ end
37
+
38
+ after(:each ){stop_callback_recorder}
39
+
40
+ it 'starts out empty and without topic' do
41
+ pending
42
+ @client.send_data DDE_DATA, CF_TEXT, "item"
43
+ @data.should_not be_empty
44
+ @data.topic.should == nil
45
+ @data.draw.should == nil
46
+ end
10
47
  end
48
+
11
49
  end
50
+
12
51
  end
@@ -1,49 +1,110 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'spec'
4
- require 'spec/autorun'
5
- require 'dde'
6
-
7
- # Customize RSpec with my own extensions
8
- module SpecMacros
9
-
10
- # wrapper for it method that extracts description from example source code, such as:
11
- # spec { use{ function(arg1 = 4, arg2 = 'string') }}
12
- def spec &block
13
- it description_from(caller[0]), &block # it description_from(*block.source_location), &block
14
- #do lambda(&block).should_not raise_error end
15
- end
16
-
17
- # reads description line from source file and drops external brackets like its{}, use{}
18
- # accepts as arguments either file name and line or call stack member (caller[0])
19
- def description_from(*args)
20
- case args.size
21
- when 1
22
- file, line = args.first.scan(/\A(.*?):(\d+)/).first
23
- when 2
24
- file, line = args
25
- end
26
- File.open(file) do |f|
27
- f.lines.to_a[line.to_i-1].gsub( /(spec.*?{)|(use.*?{)|}/, '' ).strip
28
- end
29
- end
30
- end
31
-
32
- Spec::Runner.configure { |config| config.extend(SpecMacros) }
33
-
34
- module DDETest
35
-
36
- include Win::DDE
37
- # @@monitor = DDE::Monitor.new
38
-
39
- TEST_IMPOSSIBLE = 'Impossible'
40
-
41
- def use
42
- lambda {yield}.should_not raise_error
43
- end
44
-
45
- def any_block
46
- lambda {|*args| args}
47
- end
48
-
49
- end
1
+ require 'rubygems'
2
+ require 'spork'
3
+
4
+ Spork.prefork do
5
+ # Loading more in this block will cause your tests to run faster. However,
6
+ # if you change any configuration or code from libraries loaded here, you'll
7
+ # need to restart spork for it take effect.
8
+
9
+ end
10
+
11
+ Spork.each_run do
12
+ # This code will be run each time you run your specs.
13
+
14
+ end
15
+
16
+ # --- Instructions ---
17
+ # - Sort through your spec_helper file. Place as much environment loading
18
+ # code that you don't normally modify during development in the
19
+ # Spork.prefork block.
20
+ # - Place the rest under Spork.each_run block
21
+ # - Any code that is left outside of the blocks will be ran during preforking
22
+ # and during each_run!
23
+ # - These instructions should self-destruct in 10 seconds. If they don't,
24
+ # feel free to delete them.
25
+ #
26
+
27
+
28
+
29
+
30
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
31
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
32
+ require 'spec'
33
+ require 'spec/autorun'
34
+ require 'dde'
35
+
36
+ # Customize RSpec with my own extensions
37
+ module SpecMacros
38
+
39
+ # wrapper for it method that extracts description from example source code, such as:
40
+ # spec { use{ function(arg1 = 4, arg2 = 'string') }}
41
+ def spec &block
42
+ it description_from(caller[0]), &block # it description_from(*block.source_location), &block
43
+ #do lambda(&block).should_not raise_error end
44
+ end
45
+
46
+ # reads description line from source file and drops external brackets like its{}, use{}
47
+ # accepts as arguments either file name and line or call stack member (caller[0])
48
+ def description_from(*args)
49
+ case args.size
50
+ when 1
51
+ file, line = args.first.scan(/\A(.*?):(\d+)/).first
52
+ when 2
53
+ file, line = args
54
+ end
55
+ File.open(file) do |f|
56
+ f.lines.to_a[line.to_i-1].gsub( /(spec.*?{)|(use.*?{)|}/, '' ).strip
57
+ end
58
+ end
59
+ end
60
+
61
+ Spec::Runner.configure { |config| config.extend(SpecMacros) }
62
+
63
+ module DDETest
64
+
65
+ include Win::DDE
66
+ # @@monitor = DDE::Monitor.new
67
+
68
+ TEST_IMPOSSIBLE = 'Impossible'
69
+ TEST_STRING = "Data String"
70
+
71
+ def use
72
+ lambda {yield}.should_not raise_error
73
+ end
74
+
75
+ def any_block
76
+ lambda {|*args| args}
77
+ end
78
+
79
+ def start_callback_recorder(&server_block)
80
+ @client_calls = []
81
+ @server_calls = []
82
+ @client = DDE::Client.new {|*args| @client_calls << extract_values(*args); DDE_FACK}
83
+ @server = DDE::Server.new &server_block || proc {|*args| @server_calls << extract_values(*args); DDE_FACK }
84
+ @server.start_service('service')
85
+ end
86
+
87
+ def stop_callback_recorder
88
+ @client.stop_conversation if @client.conversation_active?
89
+ @server.stop_service if @server.service_active?
90
+ @server.stop_dde if @server.dde_active?
91
+ @client.stop_dde if @client.dde_active? #for some reason, need to stop @server FIRST, and @client LATER
92
+ end
93
+
94
+ def extract_values(*args)
95
+ args.map do |arg|
96
+ case arg
97
+ when 0
98
+ 0
99
+ when Integer
100
+ id = @client.id if @client
101
+ id ||= @server.id if @server
102
+ dde_query_string(id, arg)\
103
+ || Win::DDE.constants(false).inject(nil) {|res, const| arg == Win::DDE.const_get(const) ? res || const : res }\
104
+ || arg
105
+ else
106
+ arg
107
+ end
108
+ end
109
+ end
110
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dde
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - arvicco
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-03-17 00:00:00 +03:00
12
+ date: 2010-03-23 00:00:00 +03:00
13
13
  default_executable: dde_main
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency