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