help-smalltalk
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Help-smalltalk] Re: Calling LDAP C API from Smalltalk


From: Paolo Bonzini
Subject: [Help-smalltalk] Re: Calling LDAP C API from Smalltalk
Date: Fri, 19 Sep 2008 09:21:53 +0200
User-agent: Thunderbird 2.0.0.16 (Macintosh/20080707)

> So in order to call the LDAP C API from Smalltalk I have some questions:-
> 1. How does one action "DLD class>>#addLibrary:" so as to link GNU
> Smalltalk with the LDAP library? I don't know what it means!

You just have to add something like

   DLD addLibrary: 'libldap'

before the method definitions.

> 2. Where is the mapping specified between the C function, and the
> Smalltalk name that I am to provide for the function? I wasn't able to
> find this in the documentation. For example, the first function is an
> ldap_init and the C definition looks like this:-

There are two main possibilities: everything goes on the class side, or
constructor functions go on the class side and everything else on the
instance side.  In this case, you'd probably opt for the latter.

Also, do you want your methods named #open:/#init: or
#ldapOpen:/#ldapInit:?  Or maybe more Smalltalk-ish #openOn:/#on:?
These are the "design" choices to be made.

> 3. In the definition above, I'm expecting to get back a pointer to an
> LDAP struct. Would the return type in the Smalltalk function be a
> "cObject"?

No, it would be #{LDAP} so that you can use APIs like ldap_simple_bind_s
as instance methods of LDAP.  For arguments, instead, just use #cObject.

> If it helps, the definition in ldap.h for the LDAP struct is just...
>    typedef struct ldap LDAP;

If it is opaque, just define

  CObject subclass: LDAP []

otherwise you would have to use CStruct (there are examples in the Cairo
bindings.

Here are the definitions for a couple of methods (untested)

  DLD addLibrary: 'libldap'.

  CObject subclass: LDAP [
      LDAP class >> openOn: host port: port [
          "Maybe you prefer #open:port: as the selector, of course."
          <cCall: 'ldap_open' returning: #{LDAP} args: #(#string #int)>
      ]

      LDAP class >> on: host port: port [
          "Maybe you prefer #init:port: as the selector, of course."
          <cCall: 'ldap_init' returning: #{LDAP} args: #(#string #int)>
      ]

      LDAP class >> errorString [
          "Utility methods might also go on the class side."
          <cCall: 'ldap_err2string' returning: #string args: #(#int)>
      ]

      simpleBindS: foo bar: bar [
          "Didn't bother looking at the documentation, so I don't know
           what foo and bar actually are...  I declared them as #cObject
           below, but maybe #string is better...  In any case, passing
           `nil' gives a NULL to the C side."
          <cCall: 'ldap_simple_bind_s' returning: #int
               args: #(#self #cObject #cObject)>
      ]
  ]

Other random things to know:

- to pass a pointer by reference, use #cObjectPtr and pass a variable
that was set to "CObject new".

- to return an array of strings use #{CString} as the return type (for
ldap_get_values).  Then, you can do "(array at: 0)", "(array at: 1)",
etc. to get the strings, or use the other idiom in the translated
program below.  You can also make a subclass of CString and return that
class, so that you can override #free to call ldap_value_free.  I assume
this below.

- Likewise, you can make a subclass of CObject for the value returned by
ldap_first_attribute and ldap_next_attribute, so that you can override
#free to call ldap_memfree.  Similarly, you can make a subclass of
CObject for BER (ber_free) and LDAPMessage (ldap_msgfree).  Do that just
to override #free, or as the beginning of a `more Smalltalk-looking'
implementation (more hints below).

- You also need to define constant methods, probably on the class side.
 That's not hard to do with sed starting from #defines.  Enums are a
little harder.

Here is the translation of your program to Smalltalk, with more commentary:

  CObject extend [
    "Will be in 3.1"
    isNull [ ^address = 0 ]
  ]

  hostName := 'localhost'.
  portNumber := LDAP defaultPort.   "LDAP_PORT"
  findDN := 'uid=bjensen, ou=People, o=airius.com'.
  ldap := LDAP on: hostName port: portNumber.
  ldap isNil ifTrue: [ File checkError. ObjectMemory quit: 1 ].

  "Omitting more error checking.  You can also wrap the functions
   in `two levels': define the C calls with methods like
   #primSimpleBindS:bar:, and wrappers that check the return
   code and raise an exception if the return code is bad.
   As an advantage, you can also create the CObjects for
   #cObjectPtr parameters in the wrapper and return them.

   On an even higher level, you could add a field in LDAPMessage
   to point back to the LDAP object, so as to have methods like

      LDAPMessage>>first
          ^self ldap firstEntry: self

   or even LDAPMessage>>keysAndValuesDo: implementing the
   loop below, to be used like

       e keysAndValuesDo: [ :a :vals |
           vals do: [ :each | (a->each) printNl ] ]

   As you can see, designing basic bindings is one thing and can
   be automated (e.g. if SWIG had GNU Smalltalk support); designing
   bindings that look native is another thing and very hard to
   automate.  For an example, check out packages/gdbm which has
   the same example using the low-level API and a high-level
   Dictionary-like binding."

  ldap simpleBindS: nil bar: nil.
  ldap searchExtS: findDN scope: LDAP scopeBase
       ???: 'objectclass=*' ???: nil ???: 0 ???: nil ???: nil
       ???: LDAP noLimit ???: LDAP noLimit
       result: (result := LDAPMessage new).

  e := ldap firstEntry: result.
  e isNil ifFalse: [
    ('Found ', findDN) displayNl.

    "Iterate through each attribute in the entry.  Should return a
     #string"
    aPtr := ldap firstAttribute: e ber: (ber := BER new).
    [ aPtr isNil ] whileFalse: [
      "Convert as soon as possible to a String, and use that in
       the remainder.  In more complicated examples it would cause
       less memory management headaches."
      a := String fromCData: aPtr.
      aPtr free.

      vals := ldap getValues: e attribute: a.
      vals isNull ifTrue: [
        val := vals.
        [ val value isNil ] whileFalse: [
           (a->val value) printNl.
           val incr ].

        vals free
      ]

      a := ldap nextAttribute: e ber: ber.
    ].
  ].

  "I don't know if it makes sense but BER>>#free could call
   `self free: 0'."
  ber isNil ifFalse: [ ber free ].
  result free.
  ldap unbind



That's it, HTH!

Paolo




reply via email to

[Prev in Thread] Current Thread [Next in Thread]