1require 'dl'
2require 'dl/callback'
3require 'dl/stack'
4require 'dl/value'
5require 'thread'
6
7module DL
8  parent = DL.fiddle? ? Fiddle::Function : Object
9
10  class Function < parent
11    include DL
12    include ValueUtil
13
14    if DL.fiddle?
15      # :stopdoc:
16      CALL_TYPE_TO_ABI = Hash.new { |h, k|
17        raise RuntimeError, "unsupported call type: #{k}"
18      }.merge({ :stdcall =>
19                (Fiddle::Function::STDCALL rescue Fiddle::Function::DEFAULT),
20                :cdecl   => Fiddle::Function::DEFAULT,
21                nil      => Fiddle::Function::DEFAULT
22              }).freeze
23      private_constant :CALL_TYPE_TO_ABI
24      # :startdoc:
25
26      def self.call_type_to_abi(call_type) # :nodoc:
27        CALL_TYPE_TO_ABI[call_type]
28      end
29      private_class_method :call_type_to_abi
30
31      class FiddleClosureCFunc < Fiddle::Closure # :nodoc: all
32        def initialize ctype, arg, abi, name
33          @name = name
34          super(ctype, arg, abi)
35        end
36        def name
37          @name
38        end
39        def ptr
40          to_i
41        end
42      end
43      private_constant :FiddleClosureCFunc
44
45      def self.class_fiddle_closure_cfunc # :nodoc:
46        FiddleClosureCFunc
47      end
48      private_class_method :class_fiddle_closure_cfunc
49    end
50
51    def initialize cfunc, argtypes, abi = nil, &block
52      if DL.fiddle?
53        abi ||= CALL_TYPE_TO_ABI[(cfunc.calltype rescue nil)]
54        if block_given?
55          @cfunc = Class.new(FiddleClosureCFunc) {
56            define_method(:call, block)
57          }.new(cfunc.ctype, argtypes, abi, cfunc.name)
58        else
59          @cfunc  = cfunc
60        end
61
62        @args   = argtypes
63        super(@cfunc, @args.reject { |x| x == TYPE_VOID }, cfunc.ctype, abi)
64      else
65        @cfunc = cfunc
66        @stack = Stack.new(argtypes.collect{|ty| ty.abs})
67        if( @cfunc.ctype < 0 )
68          @cfunc.ctype = @cfunc.ctype.abs
69          @unsigned = true
70        else
71          @unsigned = false
72        end
73        if block_given?
74          bind(&block)
75        end
76      end
77    end
78
79    def to_i()
80      @cfunc.to_i
81    end
82
83    def name
84      @cfunc.name
85    end
86
87    def call(*args, &block)
88      if DL.fiddle?
89        if block_given?
90          args.find { |a| DL::Function === a }.bind_at_call(&block)
91        end
92        super
93      else
94        funcs = []
95        if $SAFE >= 1 && args.any? { |x| x.tainted? }
96          raise SecurityError, "tainted parameter not allowed"
97        end
98        _args = wrap_args(args, @stack.types, funcs, &block)
99        r = @cfunc.call(@stack.pack(_args))
100        funcs.each{|f| f.unbind_at_call()}
101        return wrap_result(r)
102      end
103    end
104
105    def wrap_result(r)
106      case @cfunc.ctype
107      when TYPE_VOIDP
108        r = CPtr.new(r)
109      else
110        if( @unsigned )
111          r = unsigned_value(r, @cfunc.ctype)
112        end
113      end
114      r
115    end
116
117    def bind(&block)
118      if DL.fiddle?
119        @cfunc = Class.new(FiddleClosureCFunc) {
120          def initialize ctype, args, abi, name, block
121            super(ctype, args, abi, name)
122            @block = block
123          end
124
125          def call *args
126            @block.call(*args)
127          end
128        }.new(@cfunc.ctype, @args, abi, name, block)
129        @ptr = @cfunc
130        return nil
131      else
132        if( !block )
133          raise(RuntimeError, "block must be given.")
134        end
135        unless block.lambda?
136          block = Class.new(self.class){define_method(:call, block); def initialize(obj); obj.instance_variables.each{|s| instance_variable_set(s, obj.instance_variable_get(s))}; end}.new(self).method(:call)
137        end
138        if( @cfunc.ptr == 0 )
139          cb = Proc.new{|*args|
140            ary = @stack.unpack(args)
141            @stack.types.each_with_index{|ty, idx|
142              case ty
143              when TYPE_VOIDP
144                ary[idx] = CPtr.new(ary[idx])
145              end
146            }
147            r = block.call(*ary)
148            wrap_arg(r, @cfunc.ctype, [])
149          }
150          case @cfunc.calltype
151          when :cdecl
152            @cfunc.ptr = set_cdecl_callback(@cfunc.ctype, @stack.size, &cb)
153          when :stdcall
154            @cfunc.ptr = set_stdcall_callback(@cfunc.ctype, @stack.size, &cb)
155          else
156            raise(RuntimeError, "unsupported calltype: #{@cfunc.calltype}")
157          end
158          if( @cfunc.ptr == 0 )
159            raise(RuntimeException, "can't bind C function.")
160          end
161        end
162      end
163    end
164
165    def unbind()
166      if DL.fiddle? then
167        if @cfunc.kind_of?(Fiddle::Closure) and @cfunc.ptr != 0 then
168          call_type = case abi
169                      when CALL_TYPE_TO_ABI[nil]
170                        nil
171                      when CALL_TYPE_TO_ABI[:stdcall]
172                        :stdcall
173                      else
174                        raise(RuntimeError, "unsupported abi: #{abi}")
175                      end
176          @cfunc = CFunc.new(0, @cfunc.ctype, name, call_type)
177          return 0
178        elsif @cfunc.ptr != 0 then
179          @cfunc.ptr = 0
180          return 0
181        else
182          return nil
183        end
184      end
185      if( @cfunc.ptr != 0 )
186        case @cfunc.calltype
187        when :cdecl
188          remove_cdecl_callback(@cfunc.ptr, @cfunc.ctype)
189        when :stdcall
190          remove_stdcall_callback(@cfunc.ptr, @cfunc.ctype)
191        else
192          raise(RuntimeError, "unsupported calltype: #{@cfunc.calltype}")
193        end
194        @cfunc.ptr = 0
195      end
196    end
197
198    def bound?()
199      @cfunc.ptr != 0
200    end
201
202    def bind_at_call(&block)
203      bind(&block)
204    end
205
206    def unbind_at_call()
207    end
208  end
209
210  class TempFunction < Function
211    def bind_at_call(&block)
212      bind(&block)
213    end
214
215    def unbind_at_call()
216      unbind()
217    end
218  end
219
220  class CarriedFunction < Function
221    def initialize(cfunc, argtypes, n)
222      super(cfunc, argtypes)
223      @carrier = []
224      @index = n
225      @mutex = Mutex.new
226    end
227
228    def create_carrier(data)
229      ary = []
230      userdata = [ary, data]
231      @mutex.lock()
232      @carrier.push(userdata)
233      return dlwrap(userdata)
234    end
235
236    def bind_at_call(&block)
237      userdata = @carrier[-1]
238      userdata[0].push(block)
239      bind{|*args|
240        ptr = args[@index]
241        if( !ptr )
242          raise(RuntimeError, "The index of userdata should be lower than #{args.size}.")
243        end
244        userdata = dlunwrap(Integer(ptr))
245        args[@index] = userdata[1]
246        userdata[0][0].call(*args)
247      }
248      @mutex.unlock()
249    end
250  end
251end
252