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 CHANGED
@@ -10,7 +10,7 @@ begin
10
10
  gem.email = "arvitallian@gmail.com"
11
11
  gem.homepage = "http://github.com/arvicco/dde"
12
12
  gem.authors = ["arvicco"]
13
- gem.add_dependency "win_gui", ">= 0.1.0"
13
+ gem.add_dependency "win", ">= 0.1.26"
14
14
  gem.add_development_dependency "rspec", ">= 1.2.9"
15
15
  gem.add_development_dependency "cucumber", ">= 0"
16
16
  gem.files.reject! { |fn| fn.include? "misc" }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.2.8
data/bin/dde_main CHANGED
@@ -4,103 +4,21 @@ lib = File.join(File.dirname(__FILE__), '..', 'lib')
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include? lib
5
5
 
6
6
  require 'dde'
7
+ require 'win/gui/message'
8
+
9
+ include Win::DDE
10
+ include Win::GUI::Message
7
11
 
8
12
  # console output redirection (may need to wrap it in synchronization code, etc)
9
13
  def cout *args
10
14
  print *args
11
15
  end
12
16
 
13
- cout "Starting script\n"
14
-
15
- require 'win/gui/message'
16
-
17
- include Win::GUI::Message
18
- include Win::DDE
19
-
20
- server = DDE::XlServer.new # create server
21
-
22
17
  # std::queue<XlTable> q; // Queue contains the tables to output
23
18
 
24
- # HDDEDATA CALLBACK DdeCallback(UINT uType, UINT uFmt, HCONV hConv, HSZ hsz1, HSZ hsz2,
25
- # HDDEDATA hData, DWORD dwData1, DWORD dwData2)
26
-
27
- dde_callback = lambda do |type, format, conv, hsz1, hsz2, data_handle, data1, data2|
28
- case type
29
- when XTYP_CONNECT # Request to connect from client, creating data exchange channel
30
- # hsz1:: Handle to the topic name.
31
- # hsz2:: Handle to the service name.
32
- # dwData1:: Pointer to a CONVCONTEXT structure that contains context information for the conversation.
33
- # If the client is not a Dynamic Data Exchange Management Library (DDEML) application,
34
- # this parameter is 0.
35
- # dwData2:: Specifies whether the client is the same application instance as the server. If the
36
- # parameter is 1, the client is the same instance. If the parameter is 0, the client
37
- # is a different instance.
38
- # *Returns*:: A server callback function should return TRUE(1) to allow the client to establish a
39
- # conversation on the specified service name and topic name pair, or the function
40
- # should return FALSE to deny the conversation. If the callback function returns TRUE(1)
41
- # and a conversation is successfully established, the system passes the conversation
42
- # todo: handle to the server by issuing an XTYP_CONNECT_CONFIRM transaction to the server's
43
- # callback function (unless the server specified the CBF_SKIP_CONNECT_CONFIRMS flag
44
- # in the DdeInitialize function).
45
-
46
- if hsz2 == server.service.handle
47
- 1 # instead of true # Yes, this server supports requested (name) handle
48
- else
49
- cout "Unable to process connection request for #{hsz2}, service handle is #{server.service}\n"
50
- DDE_FNOTPROCESSED # 0 instead of false # No, server does not support requested (name) handle
51
- end
52
-
53
- when XTYP_POKE # Client initiated XTYP_POKE transaction to push unsolicited data to the server
54
- # format:: Specifies the format of the data sent from the server.
55
- # conv:: Handle to the conversation.
56
- # hsz1:: Handle to the topic name. (Excel: [topic]item ?!)
57
- # hsz2:: Handle to the item name.
58
- # data_handle:: Handle to the data that the client is sending to the server.
59
- # *Returns*:: A server callback function should return the DDE_FACK flag if it processes this
60
- # transaction, the DDE_FBUSY flag if it is too busy to process this transaction,
61
- # or the DDE_FNOTPROCESSED flag if it rejects this transaction.
62
- #
63
- # # CHAR buf[200];
64
-
65
- flag = server.table.get_data(data_handle) # extract client's DDE data into server's xltable
66
- if flag
67
- # Converting hsz1 into "[topic]item" string and
68
- server.table.topic_item = dde_query_string(server.id, hsz1)
69
- server.table.draw # Simply printing it for now, no queues
70
- # // Placing table into print queue
71
- # WaitForSingleObject(hMutex1,INFINITE);
72
- # q.push(server.xltable);
73
- # ReleaseMutex(hMutex1);
74
- # // Allowing the table output thread to start...
75
- # ReleaseSemaphore(hSemaphore,1,NULL);
76
- #
77
- cout "Transaction finished"
78
- DDE_FACK # Transaction successful
79
- else
80
- cout "Unable to receive dataprocess connection request for #{hsz2}, server handle is #{server.handle}\n"
81
- DDE_FNOTPROCESSED # 0 Transaction NOT successful - return (HDDEDATA)TRUE; ?!(why TRUE, not FALSE)
82
- end
83
-
84
- when XTYP_DISCONNECT # DDE client disconnects
85
- # server.xltable.Delete();
86
- # break;
87
- DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
88
-
89
- when XTYP_ERROR # DDE Error
90
- # WaitForSingleObject(hMutex, INFINITE);
91
- # std::cerr<<"DDE error.\n";
92
- # ReleaseMutex(hMutex);
93
- # break;
94
- DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
95
-
96
- else
97
- DDE_FNOTPROCESSED # 0 - return((HDDEDATA)NULL);// is it the same as 0 ?!
98
- end
99
- end
100
-
101
- # Staring service with default name 'excel'
102
- cout "Starting DDE service 'excel'\n"
103
- server.start_service &dde_callback
19
+ # Creating DDE server and staring service with default name 'excel' and default callback
20
+ cout "Starting DDE server with service 'excel'\n"
21
+ server = DDE::XlServer.new.start_service
104
22
 
105
23
  msg = Msg.new # pointer to Msg FFI struct
106
24
 
data/dde.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dde}
8
- s.version = "0.2.2"
8
+ s.version = "0.2.8"
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-02-26}
12
+ s.date = %q{2010-03-17}
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}
@@ -31,8 +31,11 @@ Gem::Specification.new do |s|
31
31
  "doc/dde_formats.doc",
32
32
  "doc/ddeml.d.txt",
33
33
  "doc/types.txt",
34
- "doc/~$Table_format.doc",
35
- "doc/~$e_formats.doc",
34
+ "exp/exp_client.rb",
35
+ "exp/exp_dde_monitor.rb",
36
+ "exp/exp_dde_server.rb",
37
+ "exp/exp_lib.rb",
38
+ "exp/exp_server.rb",
36
39
  "features/dde.feature",
37
40
  "features/step_definitions/dde_steps.rb",
38
41
  "features/support/env.rb",
@@ -44,6 +47,7 @@ Gem::Specification.new do |s|
44
47
  "lib/dde/server.rb",
45
48
  "lib/dde/xl_server.rb",
46
49
  "lib/dde/xl_table.rb",
50
+ "spec/dde/app_shared.rb",
47
51
  "spec/dde/app_spec.rb",
48
52
  "spec/dde/client_spec.rb",
49
53
  "spec/dde/dde_string_spec.rb",
@@ -61,7 +65,8 @@ Gem::Specification.new do |s|
61
65
  s.rubygems_version = %q{1.3.5}
62
66
  s.summary = %q{DDE server for Ruby}
63
67
  s.test_files = [
64
- "spec/dde/app_spec.rb",
68
+ "spec/dde/app_shared.rb",
69
+ "spec/dde/app_spec.rb",
65
70
  "spec/dde/client_spec.rb",
66
71
  "spec/dde/dde_string_spec.rb",
67
72
  "spec/dde/monitor_spec.rb",
@@ -77,16 +82,16 @@ Gem::Specification.new do |s|
77
82
  s.specification_version = 3
78
83
 
79
84
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
80
- s.add_runtime_dependency(%q<win_gui>, [">= 0.1.0"])
85
+ s.add_runtime_dependency(%q<win>, [">= 0.1.26"])
81
86
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
82
87
  s.add_development_dependency(%q<cucumber>, [">= 0"])
83
88
  else
84
- s.add_dependency(%q<win_gui>, [">= 0.1.0"])
89
+ s.add_dependency(%q<win>, [">= 0.1.26"])
85
90
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
86
91
  s.add_dependency(%q<cucumber>, [">= 0"])
87
92
  end
88
93
  else
89
- s.add_dependency(%q<win_gui>, [">= 0.1.0"])
94
+ s.add_dependency(%q<win>, [">= 0.1.26"])
90
95
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
91
96
  s.add_dependency(%q<cucumber>, [">= 0"])
92
97
  end
data/doc/dde_formats.doc CHANGED
Binary file
data/exp/exp_client.rb ADDED
@@ -0,0 +1,44 @@
1
+ # Quick and dirty DDE Client (for experimentation)
2
+
3
+ require 'win/gui/message'
4
+ include Win::GUI::Message
5
+
6
+ #require_relative 'exp_lib'
7
+ #include DDELib
8
+
9
+ require 'win/dde'
10
+ include Win::DDE
11
+
12
+ calls = []
13
+ buffer = FFI::MemoryPointer.new(:long).write_long(0)
14
+ buffer.address
15
+
16
+ callback = lambda do |*args|
17
+ calls << [*args]
18
+ DDE_FACK
19
+ end
20
+
21
+ p status = DdeInitialize(buffer, callback, APPCLASS_STANDARD, 0)
22
+ p id = buffer.read_long
23
+
24
+ service = FFI::MemoryPointer.from_string('test_service')
25
+
26
+ p handle = DdeCreateStringHandle(id, service, CP_WINANSI)
27
+
28
+ p conv_handle = DdeConnect(id, handle, handle, nil)
29
+
30
+ str = FFI::MemoryPointer.from_string("Poke_string\n\x00\x00")
31
+
32
+ p DdeClientTransaction(str, str.size, conv_handle, handle, CF_TEXT, XTYP_POKE, 1000, nil)
33
+ p Win::DDE::ERRORS[DdeGetLastError(id)]
34
+ p DdeClientTransaction(str, str.size, conv_handle, handle, CF_TEXT, XTYP_EXECUTE, 1000, nil)
35
+ p Win::DDE::ERRORS[DdeGetLastError(id)]
36
+ sleep 0.01
37
+ p DdeClientTransaction(str, str.size, conv_handle, handle, CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, nil)
38
+ p Win::DDE::ERRORS[DdeGetLastError(id)]
39
+
40
+ p DdeDisconnect(conv_handle)
41
+
42
+ p calls.map{|c| c.map{|e|e.respond_to?(:address) ? e.address : (Win::DDE::TYPES[e] || e)}}
43
+
44
+ p Win::DDE::ERRORS[DdeGetLastError(id)]
@@ -0,0 +1,18 @@
1
+ # Objective DDE Server (for experimentation)
2
+
3
+ require 'win/gui/message'
4
+ include Win::GUI::Message
5
+
6
+ require 'dde'
7
+ include Win::DDE
8
+
9
+ calls = []
10
+ $monitor = DDE::Monitor.new
11
+ msg = Msg.new # pointer to Msg FFI struct
12
+
13
+ # Starting message loop (necessary for DDE processing)
14
+ puts "Starting message loop\n"
15
+ while msg = get_message()
16
+ translate_message(msg)
17
+ dispatch_message(msg)
18
+ end
@@ -0,0 +1,36 @@
1
+ # Objective DDE Server (for experimentation)
2
+
3
+ require 'win/gui/message'
4
+ include Win::GUI::Message
5
+
6
+ require 'dde'
7
+ include Win::DDE
8
+
9
+ calls = []
10
+ @server = DDE::Server.new do |*args|
11
+ calls << extract_values(*args) #[Win::DDE::TYPES[args.shift]]+args; 1}
12
+ puts "#{Time.now.strftime('%T.%6N')} #{extract_values(*args)}"
13
+ args.first == XTYP_CONNECT ? 1 : DDE_FACK
14
+ end
15
+ sleep 0.05
16
+ @server.start_service('test_service')
17
+
18
+ def extract_values(type, format, conv, hsz1, hsz2, data, data1, data2)
19
+ [Win::DDE::TYPES[type], format, conv,
20
+ dde_query_string(@server.id, hsz1),
21
+ dde_query_string(@server.id, hsz2),
22
+ data, data1, data2]
23
+ end
24
+
25
+ msg = Msg.new # pointer to Msg FFI struct
26
+
27
+ # Starting message loop (necessary for DDE processing)
28
+ puts "Starting message loop\n"
29
+ while msg = get_message()
30
+ translate_message(msg)
31
+ dispatch_message(msg)
32
+ end
33
+
34
+ p calls.map{|c| c.map{|e|e.respond_to?(:address) ? e.address : (Win::DDE::TYPES[e] || e)}}
35
+
36
+ p Win::DDE::ERRORS[DdeGetLastError(@server.id)]
data/exp/exp_lib.rb ADDED
@@ -0,0 +1,38 @@
1
+ # Quick and dirty DDE Library
2
+
3
+ require 'ffi'
4
+ require 'win/dde'
5
+ require 'win/gui/message'
6
+ require_relative 'exp_lib'
7
+
8
+ module DDELib
9
+ extend FFI::Library
10
+ CP_WINANSI = 1004
11
+ DNS_REGISTER = 1
12
+ APPCLASS_STANDARD = 0
13
+ CF_TEXT = 1
14
+
15
+ XTYPF_NOBLOCK = 0x0002
16
+ XCLASS_BOOL = 0x1000
17
+ XCLASS_FLAGS = 0x4000
18
+ XTYP_CONNECT = 0x0060 | XCLASS_BOOL | XTYPF_NOBLOCK
19
+ XTYP_POKE = 0x0090 | XCLASS_FLAGS
20
+ XTYP_EXECUTE = 0x0050 | XCLASS_FLAGS
21
+ TIMEOUT_ASYNC = 0xFFFFFFFF
22
+
23
+ DDE_FACK = 0x8000
24
+
25
+ ffi_lib 'user32', 'kernel32' # Default library
26
+ ffi_convention :stdcall
27
+
28
+ callback :DdeCallback, [:uint, :uint, :ulong, :pointer, :pointer, :pointer, :pointer], :ulong
29
+
30
+ attach_function(:DdeInitializeA, [:pointer, :DdeCallback, :uint32, :uint32], :uint)
31
+ attach_function(:DdeCreateStringHandleA, [:uint32, :pointer, :int], :ulong)
32
+ attach_function :DdeNameService, [:uint32, :ulong, :ulong, :uint], :ulong
33
+ attach_function(:DdeConnect, [:uint32, :ulong, :ulong, :pointer], :ulong)
34
+ attach_function :DdeDisconnect, [:ulong], :int
35
+ attach_function(:DdeClientTransaction, [:pointer, :uint32, :ulong, :ulong, :uint, :uint, :uint32, :pointer], :pointer)
36
+ attach_function :DdeGetLastError, [:uint32], :int
37
+ end
38
+
data/exp/exp_server.rb ADDED
@@ -0,0 +1,44 @@
1
+ # Quick and dirty DDE Server (for experimentation)
2
+
3
+ require 'win/gui/message'
4
+ include Win::GUI::Message
5
+
6
+ #require_relative 'exp_lib'
7
+ #include DDELib
8
+
9
+ require 'win/dde'
10
+ include Win::DDE
11
+
12
+ calls = []
13
+ buffer = FFI::MemoryPointer.new(:long).write_long(0)
14
+ buffer.address
15
+
16
+ callback = lambda do |*args|
17
+ calls << [*args]
18
+ puts "#{Time.now.strftime('%T.%6N')} #{args.map{|e|e.respond_to?(:address) ? e.address : (Win::DDE::TYPES[e] || e)}}"
19
+ args.first == XTYP_CONNECT ? 1 : DDE_FACK
20
+ end
21
+
22
+ status = DdeInitialize(buffer, callback, APPCLASS_STANDARD, 0)
23
+ id = buffer.read_long
24
+
25
+ service = FFI::MemoryPointer.from_string('test_service')
26
+
27
+ p handle = DdeCreateStringHandle(id, service, CP_WINANSI)
28
+
29
+ p DdeNameService(id, handle, 0, DNS_REGISTER)
30
+
31
+ #p DdeDisconnect(conv_handle)
32
+
33
+ msg = Msg.new # pointer to Msg FFI struct
34
+
35
+ # Starting message loop (necessary for DDE processing)
36
+ puts "Starting message loop\n"
37
+ while msg = get_message()
38
+ translate_message(msg)
39
+ dispatch_message(msg)
40
+ end
41
+
42
+ p calls.map{|c| c.map{|e|e.respond_to?(:address) ? e.address : (Win::DDE::TYPES[e] || e)}}
43
+
44
+ p Win::DDE::ERRORS[DdeGetLastError(id)]
data/lib/dde.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # console output redirection (may need to wrap it in synchronization code, etc)
2
+ require 'rubygems'
2
3
  require 'win/dde'
3
4
  require 'dde/dde_string'
4
5
  require 'dde/app'
data/lib/dde/app.rb CHANGED
@@ -24,16 +24,22 @@ module DDE
24
24
 
25
25
  attr_reader :id, :init_flags
26
26
 
27
- # Creates new DDE application and starts DDE instance
28
- # if dde_callback block is attached
27
+ # Creates new DDE application (and starts DDE instance if dde_callback block is attached)
29
28
  def initialize( init_flags=nil, &dde_callback )
30
29
  @init_flags = init_flags
31
30
 
32
31
  start_dde init_flags, &dde_callback if dde_callback
33
32
 
34
- # todo: Destructor to ensure Dde instance is uninitialized and string handles freed (is it even working?)
35
- #ObjectSpace.define_finalizer self, ->(id) { stop_dde }
36
33
  end
34
+ # # todo: Destructor to ensure Dde instance is uninitialized and string handles freed...
35
+ # ObjectSpace.define_finalizer( self, self.class.finalize))
36
+ # end
37
+ #
38
+ # # need to have class method, otherwise proc traps reference to instance (self) and the object
39
+ # # is never garbage-collected (http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/)
40
+ # def self.finalize()
41
+ # proc { stop_dde } #does NOT work since stop_dde is instance method (depends on self)
42
+ # end
37
43
 
38
44
  # (Re)Initialize application with DDEML library, providing attached dde callback
39
45
  # either preserved @init_flags or init_flags argument are used
@@ -49,7 +55,8 @@ module DDE
49
55
  # (Re)Initialize application with DDEML library, providing attached dde callback
50
56
  def stop_dde
51
57
  try "Stopping DDE" do
52
- error unless @id && dde_uninitialize(@id) # Uninitialize app with DDEML library
58
+ error "DDE not started" unless dde_active?
59
+ error unless dde_uninitialize(@id) # Uninitialize app with DDEML library
53
60
  @id = nil # Clear instance id if uninitialization successful
54
61
  end
55
62
  end
data/lib/dde/monitor.rb CHANGED
@@ -3,6 +3,8 @@ module DDE
3
3
  # Class encapsulates DDE Monitor that prints all DDE transactions to console
4
4
  class Monitor < App
5
5
 
6
+ attr_accessor :print, :calls
7
+
6
8
  # Creates new DDE monitor instance
7
9
  def initialize(init_flags=nil, &callback)
8
10
  init_flags ||=
@@ -15,12 +17,77 @@ module DDE
15
17
  MF_POSTMSGS | # monitor posted DDE messages
16
18
  MF_SENDMSGS # monitor sent DDE messages
17
19
 
20
+ @calls = []
21
+
18
22
  callback ||= lambda do |*args|
19
- p args.unshift(Win::DDE::TYPES[args.shift]).push(Win::DDE::FLAGS[args.pop])
20
- 1
23
+ time = Time.now.strftime('%T.%6N')
24
+ values = extract_values(*args)
25
+ @calls << [time, values]
26
+ puts "#{time} #{values}" if @print
27
+ DDE_FACK
21
28
  end
22
-
29
+
23
30
  super init_flags, &callback
24
31
  end
32
+
33
+ def extract_values(*args)
34
+ values = args.map {|arg| interprete_value(arg)}
35
+
36
+ # if this is a MONITOR transaction, extract hdata using the DdeAccessData
37
+ if values.first == :XTYP_MONITOR
38
+ data_type = case values.last
39
+ when :MF_CALLBACKS
40
+ MonCbStruct #.new(dde_get_data(args[5]).first)
41
+ # cb:: Specifies the structure's size, in bytes.
42
+ # dwTime:: Specifies the Windows time at which the transaction occurred. Windows time is the number of
43
+ # milliseconds that have elapsed since the system was booted.
44
+ # hTask:: Handle to the task (app instance) containing the DDE callback function that received the transaction.
45
+ # dwRet:: Specifies the value returned by the DDE callback function that processed the transaction.
46
+ # wType:: Specifies the transaction type.
47
+ # wFmt:: Specifies the format of the data exchanged (if any) during the transaction.
48
+ # hConv:: Handle to the conversation in which the transaction took place.
49
+ # hsz1:: Handle to a string.
50
+ # hsz2:: Handle to a string.
51
+ # hData:: Handle to the data exchanged (if any) during the transaction.
52
+ # dwData1:: Specifies additional data.
53
+ # dwData2:: Specifies additional data.
54
+ # cc:: Specifies a CONVCONTEXT structure containing language information used to share data in different languages.
55
+ # cbData:: Specifies the amount, in bytes, of data being passed with the transaction. This value can be
56
+ # more than 32 bytes.
57
+ # Data:: Contains the first 32 bytes of data being passed with the transaction (8 * sizeof(DWORD)).
58
+
59
+ when :MF_CONV
60
+ MonConvStruct
61
+ when :MF_ERRORS
62
+ MonErrStruct
63
+ when :MF_HSZ_INFO
64
+ MonHszStruct
65
+ when :MF_LINKS
66
+ MonLinksStruct
67
+ else
68
+ MonMsgStruct
69
+ end
70
+
71
+ #casting DDE data pointer into appropriate struct type
72
+ struct_pointer, size = dde_get_data(args[5])
73
+ data = data_type.new(struct_pointer)
74
+
75
+ values = [values.first, values.last] + data.members.map do |member|
76
+ value = data[member] rescue 'plonk'
77
+ "#{member}: #{interprete_value(value)}"
78
+ end
79
+ end
80
+
81
+ values
82
+ end
83
+
84
+ def interprete_value(arg)
85
+ return arg unless arg.kind_of? Fixnum rescue return 'plAnk'
86
+ return 0 if arg == 0
87
+ #Trying to interpete arg as a DDE string
88
+ dde_query_string(@id, arg)\
89
+ || Win::DDE.constants(false).inject(nil) {|res, const| arg == Win::DDE.const_get(const) ? res || const : res }\
90
+ || arg
91
+ end
25
92
  end
26
93
  end