dde 0.2.2 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
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