Modifying SWIG Interface file to Support C void* and structure return types

回眸只為那壹抹淺笑 提交于 2019-12-12 01:52:26

问题


I'm using SWIG to generate my JNI layer for a large set of C APIs and I was wondering what is the best practices for the below situations. The below not only pertain to SWIG but JNI in general.

When C functions return pointers to Structures, should the SWIG interface file (JNI logic) be heavily used or should C wrapper functions be created to return the data in pieces (i.e. a char array that contains the various data elements)? When C Functions return void* should the C APIs be modified to return the actual data type, whether it be primitive or structure types? I'm unsure if I want to add a mass amount of logic and create a middle layer (SWIG interface file/JNI logic). Thoughts?


回答1:


My approach to this in the past has been to write as little code as possible to make it work. When I have to write code to make it work I write it in this order of preference:

  1. Write as C or C++ in the original library - everyone can use this code, you don't have to write anything Java or SWIG specific (e.g. add more overloads in C++, add more versions of functions in C, use return types that SWIG knows about in them)

  2. Write more of the target language - supply "glue" to bring some bits of the library together. In this case that would be Java.

    It doesn't really matter if this is "pure" Java, outside of SWIG altogether, or as part of the SWIG interface file from my perspective. Users of the Java interface shouldn't be able to distinguish the two. You can use SWIG to help avoid repetition in a number of cases though.

  3. Write some JNI through SWIG typemaps. This is ugly, error prone if you're not familiar with writing it, harder to maintain (arguably) and only useful to SWIG+Java. Using SWIG typemaps does at least mean you only write it once for every type you wrap.

    The times I'd favour this over 2. is one or more of:

    1. When it comes up a lot (saves repetitious coding)
    2. I don't know the target language at all, in which case using the language's C API probably is easier than writing something in that language
    3. The users will expect this
    4. Or it just isn't possible to use the previous styles.

Basically these guidelines I suggested are trying to deliver functionality to as many users of the library as possible whilst minimising the amount of extra, target language specific code you have to write and reducing the complexity of it when you do have to write it.


For a specific case of sockaddr_in*:

Approach 1

The first thing I'd try and do is avoid wrapping anything more than a pointer to it. This is what swig does by default with the SWIGTYPE_p_sockaddr_in thing. You can use this "unknown" type in Java quite happily if all you do is pass it from one thing to another, store in containers/as a member etc., e.g.

public static void main(String[] argv) {
  Module.takes_a_sockaddr(Module.returns_a_sockaddr());
}

If that doesn't do the job you could do something like write another function, in C:

const char * sockaddr2host(struct sockaddr_in *in); // Some code to get the host as a string
unsigned short sockaddr2port(struct sockaddr_in *in); // Some code to get the port

This isn't great in this case though - you've got some complexity to handle there with address families that I'd guess you'd rather avoid (that's why you're using sockaddr_in in the first place), but it's not Java specific, it's not obscure syntax and it all happens automatically for you besides that.

Approach 2

If that still isn't good enough then I'd start to think about writing a little bit of Java - you could expose a nicer interface by hiding the SWIGTYPE_p_sockaddr_in type as a private member of your own Java type, and wrapping the call to the function that returns it in some Java that constructs your type for you, e.g.

public class MyExtension {
  private MyExtension() { }
  private SWIGTYPE_p_sockaddr_in detail;
  public static MyExtension native_call() {
    MyExtension e = new MyExtension();
    e.detail = Module.real_native_call();
    return e;
  }

  public void some_call_that_takes_a_sockaddr() {
    Module.real_call(detail);
  }
}

No extra SWIG to write, no JNI to write. You could do this through SWIG using %pragma(modulecode) to make it all overloads on the actual Module SWIG generates - this feels more natural to the Java users probably (it doesn't look like a special case) and isn't really any more complex. The hardwork is being done by SWIG still, this just provides some polish that avoids repetitious coding on the Java side.

Approach 3

This would basically be the second part of my previous answer. It's nice because it looks and feels native to the Java users and the C library doesn't have to be modified either. In essence the typemap provides a clean-ish syntax for encapsulating the JNI calls for converting from what Java users expect to what C works with and neither side knows about the other side's outlook.

The downside though is that it is harder to maintain and really hard to debug. My experience has been that SWIG has a steep learning curve for things like this, but once you reach a point where it doesn't take too much effort to write typemaps like that the power they give you through re-use and encapsulation of the C type->Java type mapping is very useful and powerful.

If you're part of a team, but the only person who really understands the SWIG interface then that puts a big "what if you get hit by a bus?" factor on the project as a whole. (Probably quite good for making you unfirable though!)



来源:https://stackoverflow.com/questions/8404850/modifying-swig-interface-file-to-support-c-void-and-structure-return-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!