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 +1 -1
- data/VERSION +1 -1
- data/bin/dde_main +7 -89
- data/dde.gemspec +13 -8
- data/doc/dde_formats.doc +0 -0
- data/exp/exp_client.rb +44 -0
- data/exp/exp_dde_monitor.rb +18 -0
- data/exp/exp_dde_server.rb +36 -0
- data/exp/exp_lib.rb +38 -0
- data/exp/exp_server.rb +44 -0
- data/lib/dde.rb +1 -0
- data/lib/dde/app.rb +12 -5
- data/lib/dde/monitor.rb +70 -3
- data/lib/dde/xl_server.rb +82 -1
- data/lib/dde/xl_table.rb +102 -104
- data/spec/dde/app_shared.rb +85 -0
- data/spec/dde/app_spec.rb +2 -78
- data/spec/dde/client_spec.rb +68 -40
- data/spec/dde/monitor_spec.rb +15 -2
- data/spec/dde/server_shared.rb +12 -11
- data/spec/dde/server_spec.rb +10 -1
- data/spec/dde/xl_server_spec.rb +11 -0
- data/spec/dde/xl_table_spec.rb +1 -1
- metadata +11 -6
- data/doc/~$Table_format.doc +0 -0
- data/doc/~$e_formats.doc +0 -0
data/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 "
|
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.
|
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
|
-
#
|
25
|
-
|
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.
|
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-
|
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
|
-
"
|
35
|
-
"
|
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/
|
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<
|
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<
|
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<
|
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
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
|
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
|
-
|
20
|
-
|
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
|