如何使 Swig 正确地将在 C 中修改的 char* 缓冲区包装为 Java Something-or-other?

Posted

技术标签:

【中文标题】如何使 Swig 正确地将在 C 中修改的 char* 缓冲区包装为 Java Something-or-other?【英文标题】:How can I make Swig correctly wrap a char* buffer that is modified in C as a Java Something-or-other? 【发布时间】:2010-04-29 19:09:34 【问题描述】:

我正在尝试包装一些遗留代码以在 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 它需要知道什么。家里有打字机吗?

【问题讨论】:

有点搞笑,我自己在谷歌上搜索,很高兴看到一篇听起来完全符合我需要的堆栈溢出文章。这是我自己的,但没有回答的问题。 30分钟到谷歌顶,还不错。 30 分钟?它通常更快;) 【参考方案1】:

感谢 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);

【讨论】:

直到明天我才能接受自己的答案,哦,好吧。现在我们知道了。【参考方案2】:

也许 SWIG 文档的 this part 会有所帮助:

一些 C 程序中的一个常见问题是处理作为简单指针或引用传递的参数。例如:

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

[...]

typemaps.i 库文件将在这些情况下提供帮助。例如:

%module example
%include "typemaps.i"

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

还有一个section on wrapping arrays。

很抱歉,这不是一个现成的完整答案。 SWIG 有时令人费解。

【讨论】:

SWIG 正在折腾,我会在本周末晚些时候告诉你它是如何工作的。谢谢。【参考方案3】:

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

【讨论】:

不幸的是,SWIGTYPE_p_unsigned_char 在取消引用时只给出一个字符,所以它不好 - 抱歉误导。顺便说一句,我想知道 - 如果在上面的示例中我必须使用 ByteBuffer 而不是 StringBuffer 那么 typemap 的变化是什么?有人可以对此发表评论吗?

以上是关于如何使 Swig 正确地将在 C 中修改的 char* 缓冲区包装为 Java Something-or-other?的主要内容,如果未能解决你的问题,请参考以下文章

SWIG:你能否使用 SWIG 专门使用 C++ 头文件使 C++ 在 Python 中可用?

如何将复数从 python numpy 传递给 c(目前正在尝试使用 SWIG)

如何使用 Swig 中止调用 C 函数的 Python 脚本?

如何使用 SWIG 从 C++ 调用 Java?

如何正确地将已附加到上下文的实体标记为已修改?

如何正确地将浮点指针从 C 库传递到其 C# 包装器