Swift Academy · レッスン

withUnsafeBytesとC相互運用

生バイトを受け取るC APIとの橋渡しを行います。

レッスン 4/413 ステップ

「withUnsafeBytesとC相互運用」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

型付きデータから生バイトへ

値をIntやstructとしてではなく、生バイトの並びとして扱う必要がある場合があります。ハッシュ化したり、ファイルに書き込んだり、C APIに渡したりする場合です。withUnsafeBytesを使うと、値のストレージをUInt8として一時的に参照できます。

var value: UInt32 = 0x01020304
withUnsafeBytes(of: &value) { rawBuffer in
    print(rawBuffer.count) // 4 bytes
}

UnsafeRawBufferPointer

クロージャはUnsafeRawBufferPointerを受け取ります。これは要素型を持たないバイトのバッファです。インデックスを使って個々のUInt8値を読み取ります。これは読み取り専用で、可変形式はwithUnsafeMutableBytesです。

var value: UInt32 = 0x01020304
withUnsafeBytes(of: &value) { bytes in
    for b in bytes { print(String(format: "%02x", b)) }
}
// Order depends on endianness

エンディアンが重要

観測されるバイト順は、プラットフォームのエンディアンによって異なります。リトルエンディアンのマシン(Apple silicon、Intel)では、最下位バイトが先に並びます。プロトコル用にシリアライズする場合は、bigEndianまたはlittleEndianを使って明示的に変換してください。

let host: UInt32 = 0x01020304
let networkOrder = host.bigEndian
var be = networkOrder
withUnsafeBytes(of: &be) { bytes in
    // Always 01 02 03 04 regardless of platform
    print(Array(bytes))
}

structのバイト列

withUnsafeBytes(of:)は、structを含むあらゆる値で動作します。バッファの長さはMemoryLayout<T>.sizeと等しく、パディングバイトも含まれますが、その内容は規定されません。

struct RGB { var r: UInt8; var g: UInt8; var b: UInt8 }
var color = RGB(r: 255, g: 128, b: 0)
withUnsafeBytes(of: &color) { bytes in
    print(bytes[0], bytes[1], bytes[2]) // 255 128 0
}

バイト列から値を読み込む

生バイトを型付きの値として再解釈するには、生バッファに対してload(fromByteOffset:as:)を使用します。オフセットは型のアラインメント要件を満たす必要があり、バイト列は実際にその型の有効な値を表していなければなりません。

let raw: [UInt8] = [0x04, 0x03, 0x02, 0x01]
let n = raw.withUnsafeBytes { ptr in
    ptr.load(fromByteOffset: 0, as: UInt32.self)
}
print(String(format: "0x%08x", n))

パックされたデータにはloadUnaligned

バイナリ形式では、アラインメント用のパディングなしでフィールドを詰めることがよくあります。アラインメントされていないオフセットでloadを呼び出すと未定義動作になるため、任意のオフセットから安全に読み取るにはloadUnaligned(fromByteOffset:as:)を使用してください。

let packet: [UInt8] = [0xFF, 0x04, 0x03, 0x02, 0x01]
let field = packet.withUnsafeBytes { ptr in
    ptr.loadUnaligned(fromByteOffset: 1, as: UInt32.self)
}
print(field)

Swift DataとCの橋渡し

多くのC関数は、const void *と長さを受け取ります。Dataと配列はwithUnsafeBytesを提供しているため、コピーせずにbaseAddressとcountを直接渡せます。

import Foundation
let data = Data([0x68, 0x69]) // "hi"
data.withUnsafeBytes { (raw: UnsafeRawBufferPointer) in
    if let base = raw.baseAddress {
        // c_consume(base, raw.count)
        print("ptr", base, "len", raw.count)
    }
}

Cポインタを受け取る

Cのコールバックからconst void *と長さを受け取ったら、それをUnsafeRawBufferPointerでラップして安全に反復処理します。Swiftでは、CポインタはUnsafeRawPointer?としてインポートされます。

func handleC(_ ptr: UnsafeRawPointer, _ len: Int) {
    let buffer = UnsafeRawBufferPointer(start: ptr, count: len)
    let checksum = buffer.reduce(0) { $0 &+ Int($1) }
    print("checksum:", checksum)
}

bindMemoryと再解釈

生のメモリ領域を型付きの要素として扱うには、bindMemory(to:capacity:)でコンパイラに解釈方法を伝えます。同じメモリを互換性のない2つの型にバインドするなど、これを誤って使用すると、Swiftの厳格なエイリアシング規則に違反し、未定義動作になります。

let raw = UnsafeMutableRawPointer.allocate(
    byteCount: 8, alignment: 8)
defer { raw.deallocate() }
let typed = raw.bindMemory(to: Int.self, capacity: 1)
typed.pointee = 1234
print(typed.pointee)

assumingMemoryBoundとbindMemory

bindMemoryはメモリの解釈方法を恒久的に変更します。一方、assumingMemoryBound(to:)は、何も変更せずに、すでにその型にバインドされていると断言します。後者は、指定した型の値を保持していると確信できるメモリなど、既存のバインディングが確実な場合にのみ使用してください。

func sumInts(_ raw: UnsafeRawPointer, _ count: Int) -> Int {
    let p = raw.assumingMemoryBound(to: Int.self)
    var total = 0
    for i in 0..<count { total += p[i] }
    return total
}

安全性チェックリスト

生バイトとCとの相互運用では、多くのクラッシュが潜んでいます。次の点を覚えておいてください。

  • クロージャのポインタを外部に逃がさないでください。
  • アラインメントを守るか、Unaligned形式を使用してください。
  • シリアライズ時はendiannessを明示的に処理してください。
  • メモリを互換性のない型に再バインドしないでください。
  • 空のバッファではbaseAddressがnilになるため、必ず対応してください。
// Convert any value to a hex string safely
import Foundation
func hex<T>(_ v: T) -> String {
    var value = v
    return withUnsafeBytes(of: &value) { bytes in
        bytes.map { String(format: "%02x", $0) }.joined()
    }
}

クイックチェック

パックされたバイナリデータの読み取りについて、理解度を確認してください。

まとめ

生バイトを介して、Swiftの値とCを橋渡しする方法を学びました。

  • withUnsafeBytes(of:)は、あらゆる値をUInt8として参照します。
  • load / loadUnalignedは、アラインメントを守りながらバイト列を型付きの値として再解釈します。
  • Dataと配列はwithUnsafeBytesを提供し、ポインタと長さをCに渡せます。
  • bindMemoryとassumingMemoryBoundで、生メモリをどのように型付けするかを制御します。
  • エンディアン、アラインメント、ポインタの逸脱が主な危険要因です。
無料で開始

AI チューターと学ぶ Swift — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
122
レッスン
409

よくある質問

「withUnsafeBytesとC相互運用」レッスンは無料ですか?

はい。「withUnsafeBytesとC相互運用」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「withUnsafeBytesとC相互運用」で何を学びますか?

生バイトを受け取るC APIとの橋渡しを行います。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「withUnsafeBytesとC相互運用」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. MemoryLayoutとアライメント
  2. UnsafePointerとUnsafeMutablePointer
  3. Unsafeバッファポインタ
  4. withUnsafeBytesとC相互運用
← Swift Academyに戻る