module ZMQ
# Wraps the libzmq library and attaches to the functions that are
# common across the 2.x and 3.x APIs.
#
module LibZMQ
extend FFI::Library
begin
# bias the library discovery to a path inside the gem first, then
# to the usual system paths
inside_gem = File.join(File.dirname(__FILE__), '..', '..', 'ext')
if FFI::Platform::IS_WINDOWS
local_path=ENV['PATH'].split(';')
else
local_path=ENV['PATH'].split(':')
end
ZMQ_LIB_PATHS = [
inside_gem, '/usr/local/lib', '/opt/local/lib', '/usr/local/homebrew/lib', '/usr/lib64'
].map{|path| "#{path}/libzmq.#{FFI::Platform::LIBSUFFIX}"}
ffi_lib(ZMQ_LIB_PATHS + %w{libzmq})
rescue LoadError
if ZMQ_LIB_PATHS.push(*local_path).any? {|path|
File.file? File.join(path, "libzmq.#{FFI::Platform::LIBSUFFIX}")}
warn "Unable to load this gem. The libzmq library exists, but cannot be loaded."
warn "If this is Windows:"
warn "- Check that you have MSVC runtime installed or statically linked"
warn "- Check that your DLL is compiled for #{FFI::Platform::ADDRESS_SIZE} bit"
else
warn "Unable to load this gem. The libzmq library (or DLL) could not be found."
warn "If this is a Windows platform, make sure libzmq.dll is on the PATH."
warn "If the DLL was built with mingw, make sure the other two dependent DLLs,"
warn "libgcc_s_sjlj-1.dll and libstdc++6.dll, are also on the PATH."
warn "For non-Windows platforms, make sure libzmq is located in this search path:"
warn ZMQ_LIB_PATHS.inspect
end
raise LoadError, "The libzmq library (or DLL) could not be loaded"
end
# Size_t not working properly on Windows
find_type(:size_t) rescue typedef(:ulong, :size_t)
# Context and misc api
#
# @blocking = true is a hint to FFI that the following (and only the following)
# function may block, therefore it should release the GIL before calling it.
# This can aid in situations where the function call will/may block and another
# thread within the lib may try to call back into the ruby runtime. Failure to
# release the GIL will result in a hang; the hint *may* allow things to run
# smoothly for Ruby runtimes hampered by a GIL.
#
# This is really only honored by the MRI implementation but it *is* necessary
# otherwise the runtime hangs (and requires a kill -9 to terminate)
#
@blocking = true
attach_function :zmq_version, [:pointer, :pointer, :pointer], :void
@blocking = true
attach_function :zmq_errno, [], :int
@blocking = true
attach_function :zmq_strerror, [:int], :pointer
def self.version
if @version.nil?
major = FFI::MemoryPointer.new :int
minor = FFI::MemoryPointer.new :int
patch = FFI::MemoryPointer.new :int
LibZMQ.zmq_version major, minor, patch
@version = {:major => major.read_int, :minor => minor.read_int, :patch => patch.read_int}
end
@version
end
def self.version2?() version[:major] == 2 && version[:minor] >= 1 end
def self.version3?() version[:major] == 3 && version[:minor] >= 2 end
# Context initialization and destruction
@blocking = true
attach_function :zmq_init, [:int], :pointer
@blocking = true
attach_function :zmq_term, [:pointer], :int
# Message API
@blocking = true
attach_function :zmq_msg_init, [:pointer], :int
@blocking = true
attach_function :zmq_msg_init_size, [:pointer, :size_t], :int
@blocking = true
attach_function :zmq_msg_init_data, [:pointer, :pointer, :size_t, :pointer, :pointer], :int
@blocking = true
attach_function :zmq_msg_close, [:pointer], :int
@blocking = true
attach_function :zmq_msg_data, [:pointer], :pointer
@blocking = true
attach_function :zmq_msg_size, [:pointer], :size_t
@blocking = true
attach_function :zmq_msg_copy, [:pointer, :pointer], :int
@blocking = true
attach_function :zmq_msg_move, [:pointer, :pointer], :int
# Used for casting pointers back to the struct
#
class Msg < FFI::Struct
layout :content, :pointer,
:flags, :uint8,
:vsm_size, :uint8,
:vsm_data, [:uint8, 30]
end # class Msg
# Socket API
@blocking = true
attach_function :zmq_socket, [:pointer, :int], :pointer
@blocking = true
attach_function :zmq_setsockopt, [:pointer, :int, :pointer, :int], :int
@blocking = true
attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int
@blocking = true
attach_function :zmq_bind, [:pointer, :string], :int
@blocking = true
attach_function :zmq_connect, [:pointer, :string], :int
@blocking = true
attach_function :zmq_close, [:pointer], :int
# Device API
@blocking = true
attach_function :zmq_device, [:int, :pointer, :pointer], :int
# Poll API
@blocking = true
attach_function :zmq_poll, [:pointer, :int, :long], :int
module PollItemLayout
def self.included(base)
if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE==64
# On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
fd_type=:uint64
else
fd_type=:int
end
base.class_eval do
layout :socket, :pointer,
:fd, fd_type,
:events, :short,
:revents, :short
end
end
end # module PollItemLayout
class PollItem < FFI::Struct
include PollItemLayout
def socket() self[:socket]; end
def fd() self[:fd]; end
def readable?
(self[:revents] & ZMQ::POLLIN) > 0
end
def writable?
(self[:revents] & ZMQ::POLLOUT) > 0
end
def both_accessible?
readable? && writable?
end
def inspect
"socket [#{socket}], fd [#{fd}], events [#{self[:events]}], revents [#{self[:revents]}]"
end
def to_s; inspect; end
end # class PollItem
end
# Attaches to those functions specific to the 2.x API
#
if LibZMQ.version2?
module LibZMQ
# Socket api
@blocking = true
attach_function :zmq_recv, [:pointer, :pointer, :int], :int
@blocking = true
attach_function :zmq_send, [:pointer, :pointer, :int], :int
end
end
# Attaches to those functions specific to the 3.x API
#
if LibZMQ.version3?
module LibZMQ
# New Context API
@blocking = true
attach_function :zmq_ctx_new, [], :pointer
@blocking = true
attach_function :zmq_ctx_destroy, [:pointer], :int
@blocking = true
attach_function :zmq_ctx_set, [:pointer, :int, :int], :int
@blocking = true
attach_function :zmq_ctx_get, [:pointer, :int], :int
# Message API
@blocking = true
attach_function :zmq_msg_send, [:pointer, :pointer, :int], :int
@blocking = true
attach_function :zmq_msg_recv, [:pointer, :pointer, :int], :int
@blocking = true
attach_function :zmq_msg_more, [:pointer], :int
@blocking = true
attach_function :zmq_msg_get, [:pointer, :int], :int
@blocking = true
attach_function :zmq_msg_set, [:pointer, :int, :int], :int
# Monitoring API
# zmq_ctx_set_monitor is no longer supported as of version >= 3.2.1
# replaced by zmq_socket_monitor
if LibZMQ.version[:minor] > 2 || (LibZMQ.version[:minor] == 2 && LibZMQ.version[:patch] >= 1)
@blocking = true
attach_function :zmq_socket_monitor, [:pointer, :pointer, :int], :int
else
@blocking = true
attach_function :zmq_ctx_set_monitor, [:pointer, :pointer], :int
end
# Socket API
@blocking = true
attach_function :zmq_unbind, [:pointer, :string], :int
@blocking = true
attach_function :zmq_disconnect, [:pointer, :string], :int
@blocking = true
attach_function :zmq_recvmsg, [:pointer, :pointer, :int], :int
@blocking = true
attach_function :zmq_recv, [:pointer, :pointer, :size_t, :int], :int
@blocking = true
attach_function :zmq_sendmsg, [:pointer, :pointer, :int], :int
@blocking = true
attach_function :zmq_send, [:pointer, :pointer, :size_t, :int], :int
module EventDataLayout
def self.included(base)
base.class_eval do
layout :event, :int,
:addr, :string,
:field2, :int
end
end
end # module EventDataLayout
class EventData < FFI::Struct
include EventDataLayout
def event() self[:event]; end
def addr() self[:addr]; end
alias :address :addr
def fd() self[:field2]; end
alias :err :fd
alias :interval :fd
def inspect
"event [#{event}], addr [#{addr}], fd [#{fd}], field2 [#{fd}]"
end
def to_s; inspect; end
end # class EventData
end
end
# Sanity check; print an error and exit if we are trying to load an unsupported
# version of libzmq.
#
unless LibZMQ.version2? || LibZMQ.version3?
hash = LibZMQ.version
version = "#{hash[:major]}.#{hash[:minor]}.#{hash[:patch]}"
raise LoadError, "The libzmq version #{version} is incompatible with ffi-rzmq."
end
end # module ZMQ