dde 0.2.8 → 0.2.9

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/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