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