如何让Swig正确地包装在C中修改的char*缓冲区作为Java某种东西?

9

我想将一些旧代码封装以便在Java中使用,我很高兴看到Swig可以处理头文件并生成一个几乎可以工作的良好封装。现在我正在寻找使其真正工作的深度魔法。

在C语言中,我有一个函数长这样:

DLL_IMPORT int DustyVoodoo(char *buff, int len,  char *curse);

这个函数返回一个整数错误码以表示它是否失败。参数如下:
  • buff 是一个字符缓冲区
  • len 是缓冲区中数据的长度
  • curse 是另一个字符缓冲区,其中包含调用 DustyVoodoo 的结果
所以,你可以看到,实际上是通过第三个参数返回结果。此外,len 令人困惑,因为它可能是两个缓冲区的长度,但在调用代码中它们总是被分配为相同的大小,但考虑到 DustyVoodoo 的作用,它们不需要是相同的大小。为了安全起见,在实践中应该将两个缓冲区设置为相同的大小,比如512个字符。
生成绑定的 C 代码如下:
SWIGEXPORT jint JNICALL Java_pemapiJNI_DustyVoodoo(JNIEnv *jenv, jclass jcls, jstring 

jarg1, jint jarg2, jstring jarg3) {
  jint jresult = 0 ;
  char *arg1 = (char *) 0 ;
  int arg2 ;
  char *arg3 = (char *) 0 ;
  int result;

  (void)jenv;
  (void)jcls;
  arg1 = 0;
  if (jarg1) {
    arg1 = (char *)(*jenv)->GetStringUTFChars(jenv, jarg1, 0);
    if (!arg1) return 0;
  }
  arg2 = (int)jarg2; 
  arg3 = 0;
  if (jarg3) {
    arg3 = (char *)(*jenv)->GetStringUTFChars(jenv, jarg3, 0);
    if (!arg3) return 0;
  }
  result = (int)PemnEncrypt(arg1,arg2,arg3);
  jresult = (jint)result; 
  if (arg1) (*jenv)->ReleaseStringUTFChars(jenv, jarg1, (const char *)arg1);
  if (arg3) (*jenv)->ReleaseStringUTFChars(jenv, jarg3, (const char *)arg3);
  return jresult;
}

它的功能是正确的;然而,它忽略了cursed不仅是一个输入,它被函数改变后应该作为输出返回。它也不知道Java字符串实际上是缓冲区,并且应该由适当大小的数组支持。

我认为Swig可以在这里做正确的事情,但我无法从文档中找出如何告诉Swig它需要知道的内容。有没有任何typemap大师在场?


1
只是一个有趣的旁白,我正在谷歌搜索这个问题,并很高兴看到了一篇堆栈溢出的文章,听起来正好符合我的需求。它是我自己提出的、但尚未得到回答的问题。30分钟内排名谷歌的前几位,还不错。 - Ukko
1
30分钟?通常会更快 ;) - Thomas
3个回答

8
感谢Thomas正确引导。解决方法是创建一个自定义类型映射,使用StringBuffer获取结果。我在SWIG安装的examples/java/typemap目录中找到了代码。之前我可能在搜索时忽略了它。
下面附上示例代码,我目前正在使用建议的替代方法。然而,使用BYTE类型映射的第一种方法需要对我的Java代码进行一些更改,但从长远来看可能更合理。
感谢您的帮助,现在我可以看看是否能接受自己的答案...
/* File : example.i */
%module example
%{
/*
   example of a function that returns a value in the char * argument
   normally used like:

   char buf[bigenough];
   f1(buf);
*/

void f1(char *s) {
  if(s != NULL) {
    sprintf(s, "hello world");
  }
}

void f2(char *s) {
  f1(s);
}

void f3(char *s) {
  f1(s);
}

%}

/* default behaviour is that of input arg, Java cannot return a value in a 
 * string argument, so any changes made by f1(char*) will not be seen in the Java
 * string passed to the f1 function.
*/
void f1(char *s);

%include various.i

/* use the BYTE argout typemap to get around this. Changes in the string by 
 * f2 can be seen in Java. */
void f2(char *BYTE);



/* Alternative approach uses a StringBuffer typemap for argout */

/* Define the types to use in the generated JNI C code and Java code */
%typemap(jni) char *SBUF "jobject"
%typemap(jtype) char *SBUF "StringBuffer"
%typemap(jstype) char *SBUF "StringBuffer"

/* How to convert Java(JNI) type to requested C type */
%typemap(in) char *SBUF {

  $1 = NULL;
  if($input != NULL) {
    /* Get the String from the StringBuffer */
    jmethodID setLengthID;
    jclass sbufClass = (*jenv)->GetObjectClass(jenv, $input);
    jmethodID toStringID = (*jenv)->GetMethodID(jenv, sbufClass, "toString", "()Ljava/lang/String;");
    jstring js = (jstring) (*jenv)->CallObjectMethod(jenv, $input, toStringID);

    /* Convert the String to a C string */
    const char *pCharStr = (*jenv)->GetStringUTFChars(jenv, js, 0);

    /* Take a copy of the C string as the typemap is for a non const C string */
    jmethodID capacityID = (*jenv)->GetMethodID(jenv, sbufClass, "capacity", "()I");
    jint capacity = (*jenv)->CallIntMethod(jenv, $input, capacityID);
    $1 = (char *) malloc(capacity+1);
    strcpy($1, pCharStr);

    /* Release the UTF string we obtained with GetStringUTFChars */
    (*jenv)->ReleaseStringUTFChars(jenv,  js, pCharStr);

    /* Zero the original StringBuffer, so we can replace it with the result */
    setLengthID = (*jenv)->GetMethodID(jenv, sbufClass, "setLength", "(I)V");
    (*jenv)->CallVoidMethod(jenv, $input, setLengthID, (jint) 0);
  }
}

/* How to convert the C type to the Java(JNI) type */
%typemap(argout) char *SBUF {

  if($1 != NULL) {
    /* Append the result to the empty StringBuffer */
    jstring newString = (*jenv)->NewStringUTF(jenv, $1);
    jclass sbufClass = (*jenv)->GetObjectClass(jenv, $input);
    jmethodID appendStringID = (*jenv)->GetMethodID(jenv, sbufClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
    (*jenv)->CallObjectMethod(jenv, $input, appendStringID, newString);

    /* Clean up the string object, no longer needed */
    free($1);
    $1 = NULL;
  }  
}
/* Prevent the default freearg typemap from being used */
%typemap(freearg) char *SBUF ""

/* Convert the jstype to jtype typemap type */
%typemap(javain) char *SBUF "$javainput"

/* apply the new typemap to our function */
void f3(char *SBUF);

明天之前我不能接受自己的答案,哎呀。现在我们知道了。 - Ukko

6
也许 SWIG 文档的 这部分 对您有帮助:

A common problem in some C programs is handling parameters passed as simple pointers or references. For example:

void add(int x, int y, int *result) {
    *result = x + y;
}

[...]

The typemaps.i library file will help in these situations. For example:

%module example
%include "typemaps.i"

void add(int, int, int *OUTPUT);

还有一个处理数组的章节

很抱歉,这不是一个现成的、完整的答案。SWIG有时候会让人感到费解。


SWIG令人难以置信,我会在周末晚些时候让你知道它的运作方式。谢谢。 - Ukko

0

如果您和我一样懒惰,并且有权稍微更改C/C++函数的原型,那么另一个简单而愚蠢的解决方法是将参数类型(在接口文件中)更改为unsigned char*而不是char*,然后SWIG将其映射到SWIGTYPE_p_unsigned_char :-) 而不是Java中的String


不幸的是,当取消引用 SWIGTYPE_p_unsigned_char 时只会得到一个字符,所以它不好用 - 对于误导我感到抱歉。顺便问一下,如果在上面的例子中我必须使用 ByteBuffer 而不是 StringBuffer,那么 typemap 中的更改是什么?有人能评论一下吗? - Yogesh Devi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接