Xamarin.iOSでネイティブライブラリをバインディングしようとする際の、ポインタの扱い方いくつか。
特にダブルポインタの扱い等。
StackOverFlowに質問したりしつつ、ああでもないこうでもないと半月くらい試行錯誤してやっと動いたものなので、もっと効率よい書き方あるよ、とかあればツッコミも待ちつつ。
…というか書き始めてから気付いたけど、Rolfさんから追加回答来てたよ。
とりあえず自分なりの解を書いてからチェックしよ。
配列をポインタで渡すパターン
Objective-C側こんな感じの。
MarshalTest.h
typedef struct
{
float x,y,z;
} Marshal3D;
@interface MarshalTest : NSObject
-(void)addMarshals:(Marshal3D *)marshals num:(int)numCoord;
MarshalTest.m
#import "MarshalTest.h"
@implementation MarshalTest
{
/// Number of coordinates to display in linear
int storedNum;
/// Coordinates we'll display for the linear (lon,lat,Z in display units)
Marshal3D *coords;
}
-(void)addMarshals:(Marshal3D *)marshals num:(int)numCoord
{
coords = (Marshal3D *)realloc(coords, sizeof(Marshal3D)*(numCoord+storedNum));
for (unsigned int ii=0;ii<numCoord;ii++)
coords[storedNum + ii] = marshals[ii];
storedNum += numCoord;
}
これはこんな感じでバインディングできた。
StructsAndEnums.cs
[StructLayout(LayoutKind.Sequential)]
public struct Marshal3D
{
public float x;
public float y;
public float z;
};
ApiDefinition.cs
[Export ("addMarshals:num:")]
void AddMarshals (IntPtr marshals, int numCoord);
Extra.cs
public unsafe void AddMarshals (Marshal3D[] marshals) {
var count = marshals.Length;
fixed (Marshal3D* ptr = &marshals[0])
AddMarshals ((IntPtr)ptr, count);
}
配列をポインタで渡すパターン:コンストラクタ編
こんな感じの
MarshalTest.h
-(id)initWithMarshals:(Marshal3D *)marshals num:(int)numCoord;
は、通常メソッドの場合、バインディングをダミーメソッドにして、Extra.csでラップするメソッドを書いてやって解決できたけど、コンストラクタだとそれができないので、個人的にはstaticメソッドでファクトリパターンに持ち込むしかないのかなあと。
でもRolfさんが何か書いてたので後でチェック。
配列をダブルポインタで受け取るパターン
これが一番難儀した。
こんな感じの。
MarshalTest.h
-(int)getMarshals2:(Marshal3D **)marshals;
MarshalTest.m
-(int)getMarshals2:(Marshal3D **)marshals
{
Marshal3D* retArr = malloc(sizeof(Marshal3D)*storedNum);
for (int i=0;i<storedNum;i++) {
retArr[i] = coords[i];
}
*marshals = retArr;
return storedNum;
}
こんな感じで取る事ができた。
ApiDefinition.cs
[Export ("getMarshals:")]
[Internal]
int GetMarshals (out IntPtr Marshals);
Extra.cs
public unsafe Marshal3D [] GetMarshals () {
IntPtr intptr;
var count = GetMarshals (out intptr);
var array = new Marshal3D[count];
for (var i = 0; i < count; i++)
{
var loopPtr = (Marshal3D*)(sizeof(Marshal3D) * i + (int)intptr);
array [i] = *loopPtr;
}
Marshal.FreeHGlobal (intptr);
return array;
}
ポインタのインクリメントとかループでのコピーとかもっとやりようありそうだけど、本筋ではないのでやっつけで。
さて、Rolfさん模範解答をチェックしよ。
[追記]
Rolfさんの返答を見て…
配列をポインタで渡すパターン:コンストラクタ編
こういう書き方ができるって…本当だ!できた!
なるほど、staticな関数が呼べるのか!
ApiDefinition.cs
[Export ("initWithMarshals:num:")]
[Internal]
IntPtr Constructor (IntPtr marshals, int numCoord);
Extra.cs
public MarshalTest(Marshal3D[] marshals) : this (GetPointer(marshals),marshals.Length)
{
}
static IntPtr GetPointer(Marshal3D[] marshals)
{
fixed (Marshal3D* ptr = &marshals[0])
return (IntPtr) ptr;
}
配列をダブルポインタで受け取るパターン
こう変えれば、ほんの少しだけエレガント?になる。
Extra.cs
public unsafe Marshal3D [] GetMarshals () {
IntPtr intptr;
var count = GetMarshals (out intptr);
var array = new Marshal3D[count];
Marshal3D* loopPtr = (Marshal3D*) intptr;
for (var i = 0; i < count; i++)
array [i] = *loopPtr++;
Marshal.FreeHGlobal (intptr);
return array;
}