dde 0.2.9 → 0.2.11
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/.document +5 -5
- data/.gitignore +21 -22
- data/LICENSE +20 -20
- data/README.rdoc +19 -19
- data/Rakefile +60 -60
- data/VERSION +1 -1
- data/bin/dde_main +32 -32
- data/dde.gemspec +99 -99
- data/doc/ddeml.d.txt +374 -374
- data/doc/types.txt +159 -159
- data/exp/exp_client.rb +44 -44
- data/exp/exp_dde_monitor.rb +18 -18
- data/exp/exp_dde_server.rb +36 -36
- data/exp/exp_lib.rb +38 -38
- data/exp/exp_server.rb +44 -44
- data/features/dde.feature +9 -9
- data/features/support/env.rb +4 -4
- data/lib/dde.rb +9 -9
- data/lib/dde/app.rb +91 -91
- data/lib/dde/client.rb +102 -102
- data/lib/dde/dde_string.rb +32 -32
- data/lib/dde/monitor.rb +93 -93
- data/lib/dde/server.rb +40 -40
- data/lib/dde/xl_server.rb +89 -89
- data/lib/dde/xl_table.rb +190 -190
- data/spec/dde/app_shared.rb +84 -84
- data/spec/dde/app_spec.rb +7 -7
- data/spec/dde/client_spec.rb +199 -199
- data/spec/dde/dde_string_spec.rb +39 -39
- data/spec/dde/monitor_spec.rb +33 -33
- data/spec/dde/server_shared.rb +91 -91
- data/spec/dde/server_spec.rb +36 -36
- data/spec/dde/xl_server_spec.rb +63 -63
- data/spec/dde/xl_table_spec.rb +50 -50
- data/spec/spec.opts +2 -2
- data/spec/spec_helper.rb +21 -25
- metadata +35 -18
data/exp/exp_lib.rb
CHANGED
@@ -1,38 +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
|
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
|
-
|
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
CHANGED
@@ -1,44 +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
|
8
|
-
|
9
|
-
require 'win/dde'
|
10
|
-
include Win::
|
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::
|
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::
|
43
|
-
|
44
|
-
p Win::
|
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/features/dde.feature
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
Feature: something something
|
2
|
-
In order to something something
|
3
|
-
A user something something
|
4
|
-
something something something
|
5
|
-
|
6
|
-
Scenario: something something
|
7
|
-
Given inspiration
|
8
|
-
When I create a sweet new gem
|
9
|
-
Then everyone should see how awesome I am
|
1
|
+
Feature: something something
|
2
|
+
In order to something something
|
3
|
+
A user something something
|
4
|
+
something something something
|
5
|
+
|
6
|
+
Scenario: something something
|
7
|
+
Given inspiration
|
8
|
+
When I create a sweet new gem
|
9
|
+
Then everyone should see how awesome I am
|
data/features/support/env.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
-
require 'dde'
|
3
|
-
|
4
|
-
require 'spec/expectations'
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'dde'
|
3
|
+
|
4
|
+
require 'spec/expectations'
|
data/lib/dde.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# console output redirection (may need to wrap it in synchronization code, etc)
|
2
|
-
require 'rubygems'
|
3
|
-
require 'win/dde'
|
4
|
-
require 'dde/dde_string'
|
5
|
-
require 'dde/app'
|
6
|
-
require 'dde/server'
|
7
|
-
require 'dde/client'
|
8
|
-
require 'dde/monitor'
|
9
|
-
require 'dde/xl_server'
|
1
|
+
# console output redirection (may need to wrap it in synchronization code, etc)
|
2
|
+
require 'rubygems'
|
3
|
+
require 'win/dde'
|
4
|
+
require 'dde/dde_string'
|
5
|
+
require 'dde/app'
|
6
|
+
require 'dde/server'
|
7
|
+
require 'dde/client'
|
8
|
+
require 'dde/monitor'
|
9
|
+
require 'dde/xl_server'
|
data/lib/dde/app.rb
CHANGED
@@ -1,92 +1,92 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
module Errors # :nodoc:
|
4
|
-
def self.[](error_code)
|
5
|
-
Win::
|
6
|
-
end
|
7
|
-
|
8
|
-
class InitError < RuntimeError # :nodoc:
|
9
|
-
end
|
10
|
-
class FormatError < RuntimeError # :nodoc:
|
11
|
-
end
|
12
|
-
class StringError < RuntimeError # :nodoc:
|
13
|
-
end
|
14
|
-
class ServiceError < RuntimeError # :nodoc:
|
15
|
-
end
|
16
|
-
class ClientError < RuntimeError # :nodoc:
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Class encapsulates DDE application.
|
21
|
-
# such as
|
22
|
-
class App
|
23
|
-
include Win::
|
24
|
-
|
25
|
-
attr_reader :id, :init_flags
|
26
|
-
|
27
|
-
# Creates new DDE application (and starts DDE instance if dde_callback block is attached)
|
28
|
-
def initialize( init_flags=nil, &dde_callback )
|
29
|
-
@init_flags = init_flags
|
30
|
-
|
31
|
-
start_dde init_flags, &dde_callback if dde_callback
|
32
|
-
|
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
|
43
|
-
|
44
|
-
# (Re)Initialize application with DDEML library, providing attached dde callback
|
45
|
-
# either preserved @init_flags or init_flags argument are used
|
46
|
-
def start_dde( init_flags=nil, &dde_callback )
|
47
|
-
@init_flags = init_flags || @init_flags || APPCLASS_STANDARD
|
48
|
-
|
49
|
-
try "Starting DDE" do
|
50
|
-
@id, status = dde_initialize @id, @init_flags, &dde_callback
|
51
|
-
error(status) unless @id && status == DMLERR_NO_ERROR
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# (Re)Initialize application with DDEML library, providing attached dde callback
|
56
|
-
def stop_dde
|
57
|
-
try "Stopping DDE" do
|
58
|
-
error "DDE not started" unless dde_active?
|
59
|
-
error unless dde_uninitialize(@id) # Uninitialize app with DDEML library
|
60
|
-
@id = nil # Clear instance id if uninitialization successful
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Expects a block, yields to it inside a rescue block, raises given error_type with extended fail message.
|
65
|
-
# Returns self in case of success (to enable method chaining).
|
66
|
-
def try( action, error_type=
|
67
|
-
begin
|
68
|
-
yield
|
69
|
-
rescue => e
|
70
|
-
raise error_type, action + " failed with: #{e}"
|
71
|
-
end
|
72
|
-
self
|
73
|
-
end
|
74
|
-
|
75
|
-
# Raises Runtime error with message based on given message (DdeGetLastError message if no message given)
|
76
|
-
def error( message = nil )
|
77
|
-
raise case message
|
78
|
-
when Integer
|
79
|
-
|
80
|
-
when nil
|
81
|
-
|
82
|
-
else
|
83
|
-
message
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def dde_active?
|
88
|
-
!!@id
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
1
|
+
module Dde
|
2
|
+
|
3
|
+
module Errors # :nodoc:
|
4
|
+
def self.[](error_code)
|
5
|
+
Win::Dde::ERRORS[error_code]
|
6
|
+
end
|
7
|
+
|
8
|
+
class InitError < RuntimeError # :nodoc:
|
9
|
+
end
|
10
|
+
class FormatError < RuntimeError # :nodoc:
|
11
|
+
end
|
12
|
+
class StringError < RuntimeError # :nodoc:
|
13
|
+
end
|
14
|
+
class ServiceError < RuntimeError # :nodoc:
|
15
|
+
end
|
16
|
+
class ClientError < RuntimeError # :nodoc:
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Class encapsulates DDE application. Dde::App serves as a base for more specific types,
|
21
|
+
# such as Dde::Server or Dde:: Client.
|
22
|
+
class App
|
23
|
+
include Win::Dde
|
24
|
+
|
25
|
+
attr_reader :id, :init_flags
|
26
|
+
|
27
|
+
# Creates new DDE application (and starts DDE instance if dde_callback block is attached)
|
28
|
+
def initialize( init_flags=nil, &dde_callback )
|
29
|
+
@init_flags = init_flags
|
30
|
+
|
31
|
+
start_dde init_flags, &dde_callback if dde_callback
|
32
|
+
|
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
|
43
|
+
|
44
|
+
# (Re)Initialize application with DDEML library, providing attached dde callback
|
45
|
+
# either preserved @init_flags or init_flags argument are used
|
46
|
+
def start_dde( init_flags=nil, &dde_callback )
|
47
|
+
@init_flags = init_flags || @init_flags || APPCLASS_STANDARD
|
48
|
+
|
49
|
+
try "Starting DDE" do
|
50
|
+
@id, status = dde_initialize @id, @init_flags, &dde_callback
|
51
|
+
error(status) unless @id && status == DMLERR_NO_ERROR
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# (Re)Initialize application with DDEML library, providing attached dde callback
|
56
|
+
def stop_dde
|
57
|
+
try "Stopping DDE" do
|
58
|
+
error "DDE not started" unless dde_active?
|
59
|
+
error unless dde_uninitialize(@id) # Uninitialize app with DDEML library
|
60
|
+
@id = nil # Clear instance id if uninitialization successful
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Expects a block, yields to it inside a rescue block, raises given error_type with extended fail message.
|
65
|
+
# Returns self in case of success (to enable method chaining).
|
66
|
+
def try( action, error_type=Dde::Errors::InitError )
|
67
|
+
begin
|
68
|
+
yield
|
69
|
+
rescue => e
|
70
|
+
raise error_type, action + " failed with: #{e}"
|
71
|
+
end
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Raises Runtime error with message based on given message (DdeGetLastError message if no message given)
|
76
|
+
def error( message = nil )
|
77
|
+
raise case message
|
78
|
+
when Integer
|
79
|
+
Dde::Errors[message]
|
80
|
+
when nil
|
81
|
+
Dde::Errors[dde_get_last_error(@id)]
|
82
|
+
else
|
83
|
+
message
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def dde_active?
|
88
|
+
!!@id
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
92
|
end
|
data/lib/dde/client.rb
CHANGED
@@ -1,103 +1,103 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
# Class encapsulates DDE Client that requests connection with DDE server and exchanges data with it via DDE
|
4
|
-
class Client < App
|
5
|
-
|
6
|
-
attr_reader :conversation, # active DDE conversation that client is engaged in
|
7
|
-
:service, #service that the client is connected to
|
8
|
-
:topic, # active DDE conversation topic
|
9
|
-
:item # active DDE conversation item
|
10
|
-
|
11
|
-
# # Creates new DDE client instance
|
12
|
-
# def initialize(init_flags = nil, &dde_callback )
|
13
|
-
# super init_flags, &dde_callback
|
14
|
-
# end
|
15
|
-
|
16
|
-
# Establish a conversation with a server application that supports the specified service
|
17
|
-
# name and topic name pair.
|
18
|
-
def start_conversation( service=nil, topic=nil )
|
19
|
-
try "Starting conversation #{service} #{topic}",
|
20
|
-
error "DDE is not initialized" unless dde_active?
|
21
|
-
error "Another conversation already established" if conversation_active?
|
22
|
-
|
23
|
-
# Create DDE strings for service and topic unless they are omitted
|
24
|
-
@service =
|
25
|
-
@topic =
|
26
|
-
|
27
|
-
# Initiate new DDE conversation, returns conversation handle or nil
|
28
|
-
error unless @conversation = dde_connect(@id, @service.handle, @topic.handle)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Stops active conversation, raises error if no conversations active
|
33
|
-
def stop_conversation
|
34
|
-
try "Stopping conversation",
|
35
|
-
error "DDE not started" unless dde_active?
|
36
|
-
error "Conversation not started" unless conversation_active?
|
37
|
-
|
38
|
-
error unless dde_disconnect(@conversation) && # Stop DDE conversation
|
39
|
-
dde_free_string_handle(@id, @service.handle) && # Free string handles for service name
|
40
|
-
dde_free_string_handle(@id, @topic.handle) # Free string handles for topic name
|
41
|
-
|
42
|
-
# Unset attributes for conversation, service and topic
|
43
|
-
@conversation = nil
|
44
|
-
@service = nil
|
45
|
-
@topic = nil
|
46
|
-
end
|
47
|
-
end
|
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",
|
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
|
-
|
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
|
-
|
98
|
-
def conversation_active?
|
99
|
-
!!@conversation
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
1
|
+
module Dde
|
2
|
+
|
3
|
+
# Class encapsulates DDE Client that requests connection with DDE server and exchanges data with it via DDE
|
4
|
+
class Client < App
|
5
|
+
|
6
|
+
attr_reader :conversation, # active DDE conversation that client is engaged in
|
7
|
+
:service, #service that the client is connected to
|
8
|
+
:topic, # active DDE conversation topic
|
9
|
+
:item # active DDE conversation item
|
10
|
+
|
11
|
+
# # Creates new DDE client instance
|
12
|
+
# def initialize(init_flags = nil, &dde_callback )
|
13
|
+
# super init_flags, &dde_callback
|
14
|
+
# end
|
15
|
+
|
16
|
+
# Establish a conversation with a server application that supports the specified service
|
17
|
+
# name and topic name pair.
|
18
|
+
def start_conversation( service=nil, topic=nil )
|
19
|
+
try "Starting conversation #{service} #{topic}", Dde::Errors::ClientError do
|
20
|
+
error "DDE is not initialized" unless dde_active?
|
21
|
+
error "Another conversation already established" if conversation_active?
|
22
|
+
|
23
|
+
# Create DDE strings for service and topic unless they are omitted
|
24
|
+
@service = Dde::DdeString.new(@id, service) if service
|
25
|
+
@topic = Dde::DdeString.new(@id, topic) if topic
|
26
|
+
|
27
|
+
# Initiate new DDE conversation, returns conversation handle or nil
|
28
|
+
error unless @conversation = dde_connect(@id, @service.handle, @topic.handle)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Stops active conversation, raises error if no conversations active
|
33
|
+
def stop_conversation
|
34
|
+
try "Stopping conversation", Dde::Errors::ClientError do
|
35
|
+
error "DDE not started" unless dde_active?
|
36
|
+
error "Conversation not started" unless conversation_active?
|
37
|
+
|
38
|
+
error unless dde_disconnect(@conversation) && # Stop DDE conversation
|
39
|
+
dde_free_string_handle(@id, @service.handle) && # Free string handles for service name
|
40
|
+
dde_free_string_handle(@id, @topic.handle) # Free string handles for topic name
|
41
|
+
|
42
|
+
# Unset attributes for conversation, service and topic
|
43
|
+
@conversation = nil
|
44
|
+
@service = nil
|
45
|
+
@topic = nil
|
46
|
+
end
|
47
|
+
end
|
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
|
+
|
98
|
+
def conversation_active?
|
99
|
+
!!@conversation
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
103
|
end
|