4

(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日を!:-)

マーティン

4

2 に答える 2

3

これは、更新された質問を読み、さらに実験した後の私の以前の回答の更新です。問題は、インポートされた C 構造体と Swift で手動で実装した構造体との間のアライメントの不一致であると思います。昨日提案されたように、Cヘルパー関数を使用してvoidポインターからC構造体のインスタンスを取得することで問題を解決できます。これは、手動で実装されたSwift構造体に変換できます。

次のような DeviceState 構造の簡略化されたモックアップを作成した後、問題を再現できました。

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;
} APIStruct;

対応する手作りの Swift ネイティブ構造は次のとおりです。

struct MyStruct
{
    init( _apis : APIStruct)
    {
        revision = _apis.revision
        client = _apis.client  
        cmd = _apis.cmd        
        parameter = _apis.parameter
        value = _apis.value
        time = _apis.time
        stats = _apis.stats
        compoundValueOld = _apis.compoundValueOld
    }

    var     revision : UInt16
    var     client : UInt16          
    var     cmd : UInt16             
    var     parameter : Int16
    var     value : Int32
    var     time : UInt64
    var     stats : (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8);
    var     compoundValueOld : UInt16
}

使用している C フレームワークは、異なる構造体パッキングを使用してコンパイルされている可能性があり、その結果アライメントが一致しなくなります。使った

#pragma pack(2) 

私のCコードで、SwiftのネイティブとインポートされたC構造体の間のビットマッチングを壊します。

私が何かをするなら

func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
     ...
    let _locMS:MyStruct = (UnsafeMutablePointer<MyStruct>(p)).memory
     ...
}

_locMS のデータは、C コードによって配置されたものとは異なります。この問題は、C コードでプラグマを使用して構造体のパッキングを変更した場合にのみ発生します。上記の安全でない変換は、デフォルトの配置が使用されている場合は正常に機能します。この問題は次のように解決できます。

let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer<APIStruct>(p)).memory)

ところで、Swift が C 構造体をインポートする方法では、配列メンバーはタプルになります。これは、タプル表記を使用して Swift でそれらにアクセスする必要があるという事実からわかります。

github に配置したこれらすべてを示すサンプル Xcode プロジェクトがあります。

https://github.com/omniprog/xcode-samples

明らかに、ヘルパー C 関数を使用して void ポインターから APIStruct を取得し、APIStruct を MyStruct に変換する方法は、構造体の使用方法、構造体の大きさ、およびパフォーマンス要件に応じて、オプションになる場合とそうでない場合があります。アプリケーションの。ご覧のとおり、このアプローチには構造のコピーが含まれます。他のアプローチとしては、Swift コードとサード パーティの C フレームワークの間に C レイヤーを作成する、C 構造のメモリ レイアウトを調査し、クリエイティブな方法でアクセスする (簡単に壊れる可能性があります)、インポートされた C 構造体をより広範囲に使用する、などがあります。あなたのSwiftコードなど...

これは、不要なコピーを行わずに C コードと Swift コードの間でデータを共有し、Swift で行われた変更を C コードに表示する方法です。ただし、次のアプローチでは、オブジェクトの有効期間やその他のメモリ管理の問題に注意することが不可欠です。次のようにクラスを作成できます。

// This typealias isn't really necessary, just a convenience
typealias APIStructPtr = UnsafeMutablePointer<APIStruct>

struct MyStructUnsafe
{
    init( _p : APIStructPtr )
    {
        pAPIStruct = _p
    }

    var time: UInt64 {
        get {
            return pAPIStruct.memory.time
        }
        set( newVal ) {
            pAPIStruct.memory.time = newVal
        }
    }
    var   pAPIStruct: APIStructPtr
}

次に、この構造を次のように使用できます。

func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
   ...
   var _myUnsafe : MyStructUnsafe = MyStructUnsafe(_p: APIStructPtr(p))
   ... 
   _myUnsafe.time = 9876543210  // this change is visible in C code!
   ...
}
于 2015-10-30T04:00:28.290 に答える
1

あなたの2つの定義は同等ではありません。配列はタプルと同じではありません。あなたの Cstructは 24 バイトを与えます (理由については、この質問を参照してください)。Swift のサイズは、実装方法によって異なります。

struct MyStruct1 {
    var var01: UInt32
    var var02: UInt64
    var arr: (UInt8, UInt8)
}

typealias MyStruct2 = (
    var01: UInt32,
    var02: UInt64,
    arr: (UInt8, UInt8)
)

struct MyStruct3 {
    var var01: UInt32
    var var02: UInt64
    var arr: [UInt8] = [0,0]
}

print(sizeof(MyStruct1)) // 18
print(sizeof(MyStruct2)) // 18
print(sizeof(MyStruct3)) // 24, match C's
于 2015-10-29T23:22:49.527 に答える