(Objective-)C から Swift にアプリケーションを移植中ですが、C で記述されたサードパーティ フレームワークを使用する必要があります。 UInts などとしてのフレームワークの機能。そのため、Swift アプリケーション全体で一定のキャスト操作を回避するために、C ヘッダー ファイルを Swift に転送することにしました。すべてのタイプを 1 か所に配置する必要があるためです。
ほとんどすべてを転送でき、多くのハードルを克服しましたが、これは次のとおりです。
C ヘッダーは、特に uint64_t 変数を含む構造体を定義します。この構造体は、データをポインターとしてコールバック関数に転送するために使用されます。コールバック関数は引数として void ポインターを取り、UnsafeMutablePointer 操作でそれを構造体 (または、適切な場合はヘッダーの別の構造体) の型にキャストする必要があります。インポート時に Swift によって自動的に変換された C ヘッダーの元の構造体を使用する限り、キャストとメモリ アクセスはすべて正常に機能します。
ただし、Swift で構造体を手動で複製しても、「バイト フィット」しません。
この状況の縮小された例を示しましょう。
CApiHeader.h ファイル内には次のようなものがあります。
typedef struct{
uint32_t var01;
uint64_t var02;
uint8_t arr[2];
}MyStruct, *MyStructPtr;
私の理解では、これはSwiftに相当するはずです
struct MyStruct{
var01: UInt32
var02: UInt64
arr: (UInt8, UInt8)
}
または、このタプル表記も機能するはずです
typealias MyStruct = (
var01: UInt32,
var02: UInt64,
arr: (UInt8, UInt8)
)
これは正常に機能しますが、UInt64 タイプがあるとすぐには機能しません。
さて、どうなる?
私自身の Swift MyStruct 実装の 1 つにポインターをキャストすると、UInt64 フィールドから始まるホール データが 2 バイトずつシフトされます。したがって、この例では、両方のarrフィールドが正しい位置にありませんが、UInt64 ビット内にあり、その数は 64 である必要があります。したがって、UInt64 フィールドには 48 ビットしかありません。
これは、 UIn64 変数をこの代替に置き換えると、私の観察と一致します
struct MyStruct{
var01: UInt32
reserved: UInt16
var02: UInt32
arr: (UInt8, UInt8)
}
またはこれ
struct MyStruct{
var01: UInt32
var02: (UInt32, UInt32)
arr: (UInt8, UInt8)
}
(または同等のタプル表記) arrフィールドを正しく配置します。ただし、 var02は複数のアドレス範囲に分割されているため、直接使用できるデータが含まれていないことは容易に推測できます。最初の選択肢では、Swift が予約フィールドとvar02フィールドの間のギャップを 16 ビットで埋めているように見えるため、さらに悪化します。
そのため、Swift での C 構造体の同等の変換はわかりませんでした。
ここで正確に何が起こり、Swift は実際に C ヘッダーから構造体をどのように変換しますか?
ヒントや説明、あるいは解決策を教えてください。
アップデート
C フレームワークには、次のシグネチャを持つ API 関数があります。
int16_t setHandlers(MessageHandlerProc messageHandler);
MessageHandlerProc はプロシージャ タイプです。
typedef void (*messageHandlerProc)(unsigned int id, unsigned int messageType, void *messageArgument);
したがって、setHandlers はフレームワーク内の C プロシージャであり、コールバック関数へのポインターを取得します。このコールバック関数は、void ポインターの引数を提供する必要があります。
typedef struct {
uint16_t revision;
uint16_t client;
uint16_t cmd;
int16_t parameter;
int32_t value;
uint64_t time;
uint8_t stats[8];
uint16_t compoundValueOld;
int16_t axis[6];
uint16_t address;
uint32_t compoundValueNew;
} DeviceState, *DeviceStatePtr;
Swift は、convention(c) 構文を使用して messageHandlerProc をインポートできるほどスマートなので、プロシージャ タイプを直接使用できます。一方、標準の func 構文を使用して messageHandler コールバック関数をこの型にビットキャストすることはできません。そこで、クロージャー構文を使用してコールバック関数を定義しました。
let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in
...
}
上記の構造を元の投稿のさまざまな構造に変換しました。
そしていいえ!統計を Swift Array として定義しても機能しません。Swift の Array は拡張型であるため、Swift の Array は C の Array と同等ではありません。ポインターを使用して書き込みおよび読み取りを行うと、例外が発生します
タプルのみが Swift でネイティブに実装されており、その上でポインターを使用して前後に実行できます。
わかりました...これはすべて正常に機能し、データが利用可能になるたびにコールバック関数が呼び出されます。
したがって、 myMessageHandler内では、void ポインターである msgArgPtr 内に格納された Data を使用したいため、 DeviceState にキャストする必要があります。
let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory
次のような状態にアクセスします。
...
print(state.time)
print(state.stats.0)
...
自動生成されたDeviceStateの Swift ペンダントを使用すると、すべてうまく機能します。time 変数には Unix タイム スタンプがあり、次の統計 (タプル構文でアクセス可能!!!) はすべて、それらが属する場所です。
ただし、手動で実装した構造体を使用すると、完全に無意味なタイムスタンプ値になり、統計フィールドが左にシフトされます (時間フィールドに向かって - 統計「配列」からのビットが含まれているため、タイムスタンプ値が役に立たないのはおそらくそのためです)。 . したがって、stats の最後の 2 つのフィールドでは、compoundValueOldと最初の軸フィールドから値を取得します - もちろんすべてオーバーフローします。
時間の値を犠牲にして、UInt64 変数を 2 つの UInt32 型のタプルで変更するか、それを UInt32 型に変更してtimeの直前に UInt16 型の補助変数を追加することを厭わない限り、統計を受け取ります」 array" を正しい位置合わせにします。
良い1日を!:-)
マーティン