解決方案1 - Java
在SWIG中有很多不同的方法可以解決這個問題。我已經開始使用一種解決方案,只需要您多寫一點Java(在SWIG界面內),並自動應用這些解決方案,使您的函數返回您想要的語義String[]
。
一起來,我寫了一個小test.h文件,讓我們行使我們正在趕製的typemaps:
static const char *GetThings(void) {
return "Hello\0World\0This\0Is\0A\0Lot\0Of Strings\0";
}
沒什麼特別的只是一個單一的功能,其將多個字符串合併爲一個,並具有雙重終止\0
(最後一個隱含在C中的字符串常量中)。
然後我寫了下面的SWIG接口把它包起來:
%module test
%{
#include "test.h"
%}
%include <carrays.i>
%array_functions(signed char, ByteArray);
%apply SWIGTYPE* { const char *GetThings };
%pragma(java) moduleimports=%{
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
%}
%pragma(java) modulecode=%{
static private String[] pptr2array(long in, boolean owner) {
SWIGTYPE_p_signed_char raw=null;
try {
raw = new SWIGTYPE_p_signed_char(in, owner);
ArrayList<String> tmp = new ArrayList<String>();
int pos = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (ByteArray_getitem(raw, pos) != 0) {
byte c;
while ((c = ByteArray_getitem(raw, pos++)) != 0) {
bos.write(c);
}
tmp.add(bos.toString());
bos.reset();
}
return tmp.toArray(new String[tmp.size()]);
}
finally {
if (owner && null != raw) {
delete_ByteArray(raw);
}
}
}
%}
%typemap(jstype) const char *GetThings "String[]";
%typemap(javaout) const char *GetThings {
return pptr2array($jnicall, $owner);
}
%include "test.h"
本質是什麼做的是使用carrays.i SWIG庫文件揭露,讓我們得到,設置一些功能,準確地刪除陣列像C中的原始指針會。由於SWIG特殊情況下char *
默認情況下,雖然我們必須打破這個功能,我們正在尋找%apply
,因爲我們不希望發生這種情況。使用signed char
作爲數組函數可以獲得我們想要的:映射到Java中的Byte
而不是String
。
jstype typemap只是將生成的函數返回類型更改爲我們想要的類型:String[]
。 javaout typemap解釋了我們如何從JNI調用返回的轉換(一個long
,因爲我們故意停止它被封裝爲一個普通的以null結束的字符串),而是使用我們在模塊內部寫入的額外Java(pptr2array
)爲我們做這項工作。
Inside pptr2array
我們基本上是在每個字符串中逐字節地構建輸出數組。我使用了ArrayList
,因爲我寧願動態增長它,而不是在輸出上做兩遍。使用ByteArrayOutputStream
是建立由字節一個字節數組字節,它有兩個主要的優點一種巧妙的方法:
- 多字節的Unicode可以正常工作是這樣的。這與將每個字節轉換爲char並單獨附加到String(Builder)相反。
- 我們可以對每個字符串重新使用相同的ByteArrayOutputStream,這樣可以重新使用緩衝區。不是一個真正的交易斷路器在這個規模,但沒有從天做1
還有一點要注意做傷害:爲了$owner
正確設置,並指出,如果我們預計free()
的從C函數返回的內存,您需要使用%newobject
。見discussion of $owner
in docs。
解決方案2 - JNI
如果你喜歡,你可以寫幾乎相同的解決方案,但完全在typemaps做一些JNI調用來代替:
%module test
%{
#include "test.h"
#include <assert.h>
%}
%typemap(jni) const char *GetThings "jobjectArray";
%typemap(jtype) const char *GetThings "String[]";
%typemap(jstype) const char *GetThings "String[]";
%typemap(javaout) const char *GetThings {
return $jnicall;
}
%typemap(out) const char *GetThings {
size_t count = 0;
const char *pos = $1;
while (*pos) {
while (*pos++); // SKIP
++count;
}
$result = JCALL3(NewObjectArray, jenv, count, JCALL1(FindClass, jenv, "java/lang/String"), NULL);
pos = $1;
size_t idx = 0;
while (*pos) {
jobject str = JCALL1(NewStringUTF, jenv, pos);
assert(idx<count);
JCALL3(SetObjectArrayElement, jenv, $result, idx++, str);
while (*pos++); // SKIP
}
//free($1); // Iff you need to free the C function's return value
}
%include "test.h"
在這裏,我們已經基本上完成同樣的事情,但添加了3個更多的類型映射。 jtype和jnitype typemaps告訴SWIG生成的JNI代碼和相應的函數將返回的返回類型,分別爲Java和C(JNI)類型。 javaout typemap變得更簡單,它所做的就是直接傳遞String[]
作爲String[]
。
但是,在typemap中是工作發生的地方。我們在本機代碼中分配一個String[]
的Java數組。這是通過第一遍簡單計算有多少元素來完成的。 (在C中一次傳球沒有簡單的方法)。然後在第二遍中,我們調用NewStringUTF
並將其存儲到我們先前創建的輸出數組對象的正確位置。所有的JNI調用都使用SWIG特定的JCALLx macros,它們允許它們在C和C++編譯器中工作。在這裏沒有必要使用它們,但這並不是一個壞習慣。
所有仍然需要完成的工作是免費的,如果需要的話返回函數的結果。 (在我的例子中,它是一個const char*
字符串文字,所以我們不釋放它)。
解決方案3 - ç
當然,如果你喜歡只寫C,你還可以得到一個解決方案。我已經概述等的可能性在這裏:
%module test
%{
#include "test.h"
%}
%rename(GetThings) GetThings_Wrapper;
%immutable;
%inline %{
typedef struct {
const char *str;
} StrArrHandle;
StrArrHandle GetThings_Wrapper() {
const StrArrHandle ret = {GetThings()};
return ret;
}
%}
%extend StrArrHandle {
const char *next() {
const char *ret = $self->str;
if (*ret)
$self->str += strlen(ret)+1;
else
ret = NULL;
return ret;
}
}
%ignore GetThings;
%include "test.h"
注意,在這種情況下從您的封裝代碼暴露出的解決方案改變了GetThings()
返回類型。現在它返回一箇中間類型,它只存在於包裝中,StrArrHandle
。
這種新類型的目的是揭示您需要使用您的實際功能提供的所有答案的額外功能。我通過使用%inline
來聲明和定義一個額外的函數來實現這個功能,該函數將實際調用包裝爲GetThings()
,並使用一個額外的類型來保存它返回給我們稍後使用的指針。
我用%ignore
和%rename
仍然聲稱我的包裝功能被稱爲GetThings
(即使它不是爲了避免生成的C代碼中的名稱衝突)。我可以跳過%ignore
,而不是在文件底部添加%include
,但是基於這樣的假設:在現實世界中,頭文件中可能還有更多東西需要包裝,這個示例可能更有用。
使用%extend
我們可以在我們創建的包裝類型中添加一個方法,該方法返回當前字符串(如果不是最後)並且前進光標。如果您有責任釋放原始函數的返回值,您也希望保留該函數的副本,並使用%extend
爲SWIG添加一個「析構函數」,以在對象獲取垃圾回收時調用。
我告訴SWIG,不允許用戶使用%nodefaultctor
構造StrArrHandle
對象。 SWIG將爲StrArrHandle
的str
成員生成一個獲得者。 %immutable
阻止它產生一個setter,這根本就沒有意義。你可以忽略它%ignore
或拆分StrArrHandle
而不是使用%inline
,只是不告訴SWIG該成員。
這個
現在你可以從Java中使用類似稱之爲:
StrArrHandle ret = test.GetThings();
for (String s = ret.next(); s != null; s = ret.next()) {
System.out.println(s);
}
如果你想雖然你可能會爲了回報Java數組與溶液#1部分有機結合這一點。你想要添加兩個typemaps爲,靠近頂部:
%typemap(jstype) StrArrHandle "String[]";
%typemap(javaout) StrArrHandle {
$javaclassname tmp = new $javaclassname($jnicall, $owner);
// You could use the moduleimports pragma here too, this is just for example
java.util.ArrayList<String> out = new java.util.ArrayList<String>();
for (String s = tmp.next(); s != null; s = tmp.next()) {
out.add(s);
}
return out.toArray(new String[out.size()]);
}
其中有幾乎相同的結果的解決方案1,但在一個非常不同的方式。
我從來沒有聽說過像NULL字符串列表這樣的事情。 –
Hi Klas,接口規範指出:返回值是指向一系列以null結尾的字符串的指針,以第二個空字符結尾。 – c3po
NUL分隔的字符串很少見。我懷疑在SWIG中會有內置轉換。您將不得不編寫代碼將字符串轉換爲可通過'typemap'獲取的數組類型。 –