Cのバイナリデータを確保する処理をSwiftで書く

昔作ったプログラムをSwiftで書き直す過程で、ポインタを使った処理の書き換えが必要になりました。このときに、どのように書いたかをまとめてみました。今回はその中からバイナリデータを確保する処理についてです。

なお、作って見て思ったのはSwiftで書き直す必要がなければ、ポインタをゴリゴリ使うようなコア部分はObjective-Cのクラスで包んで、Swiftからは呼び出すだけの方が幸せな感じもしました。(画像処理とか音声処理とかは特に)

構造体のメモリ確保

構造体を使った処理ですが、Cではmalloc関数を使ってメモリ確保したり、スタックに変数で確保し、そのポインタを関数に渡したりします。例えば、次のようなコードです。

#include 
#include 
#include 

struct Rectangle {
    int32_t     x;
    int32_t     y;
    int32_t     width;
    int32_t     height;
};
typedef struct Rectangle Rectangle;

Rectangle * newRect(int32_t x, int32_t y, int32_t width, int32_t height) {
    Rectangle *rect = (Rectangle *)malloc(sizeof(Rectangle));
    if (rect) {
        rect->x = x;
        rect->y = y;
        rect->width = width;
        rect->height = height;
    }
    return rect;
}

void printRect(Rectangle *rect) {
    printf("{%d, %d, %d, %d}\n", rect->x, rect->y, rect->width, rect->height);
}

int main(int argc, const char * argv[]) {
    Rectangle *rect = newRect(0, 0, 100, 100);
    if (rect) {
        printRect(rect);
        free(rect);
    }
    return 0;
}

Swiftでは、Swiftの構造体を定義して、単純にインスタンスを確保するようにしました。関数は構造体そのものに関するものは、構造体のメソッドにして、その他は、関数や他のクラスのメソッドにしました。このCのコードは、構造体のメソッドでも良いと思います。

// Rectangle.swift
import Foundation

public struct Rectangle {
    var x: Int32
    var y: Int32
    var width: Int32
    var height: Int32

    public func printRect() {
        print("{\(self.x), \(self.y), \(self.width), \(self.height)}")
    }
}
// main.swift
import Foundation

let rect = Rectangle(x: 0, y: 0, width: 100, height: 100)
rect.printRect()

バイナリの塊が必要なとき

バイナリの塊が必要で、そのために構造体を使っているようなケースのときは、そのバイナリの塊を作る必要があると思います。例えば、ファイルフォーマットやプロトコルのデータ、デバイスとの通信で使用するバイナリデータなどです。

アライメントを考慮せず単純に出力するとき

例えば、上のCで書かれているRectangle構造体のバイナリデータを作るには、次のようにします。

// main.swift
import Foundation

var rect = Rectangle(x: 1, y: 2, width: 3, height: 4)
let rawPtr = UnsafeRawPointer(&rect)
let data = Data(bytes: rawPtr, count: MemoryLayout.size)

do {
    if let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first {
        let url = desktopURL.appendingPathComponent("Rectangle.dat")
        try data.write(to: url)
    }
} catch let error {
    print(error)
}

UnsafeRawPointerはC/C++ではvoid*に相当するタイプです。DataはObjective-CではNSDataに相当するタイプでバイナリデータを格納できます。rectへのポインタとサイズを指定してrectの内容を格納したバイナリデータを作っています。アライメントの処理はSwiftの使用任せです。

アライメントを考慮するとき

一般的に、プロトコルやファイルフォーマットはアライメントなども考慮して、バイト単位で格納場所が定義されています。そのため、構造体をそのままバイナリ化するよりも、メンバー単位で出力することの方が無難です。今回のRectangleでは、32bit符号付き整数が4つ並んでいるだけなので、次のように書けます。

// main.swift
import Foundation

var rect = Rectangle(x: 1, y: 2, width: 3, height: 4)
var data = Data(repeating: 0, count: MemoryLayout.size)

var offset = data.startIndex
data.replaceSubrange(offset ..< offset.advanced(by: 4),
                     with: &rect.x, count: 4)

offset = offset.advanced(by: 4)
data.replaceSubrange(offset ..< offset.advanced(by: 4),
                     with: &rect.y, count: 4)

offset = offset.advanced(by: 4)
data.replaceSubrange(offset ..< offset.advanced(by: 4),
                     with: &rect.width, count: 4)

offset = offset.advanced(by: 4)
data.replaceSubrange(offset ..< offset.advanced(by: 4),
                     with: &rect.height, count: 4)

do {
    if let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first {
        let url = desktopURL.appendingPathComponent("Rectangle.dat")
        try data.write(to: url)
    }
} catch let error {
    print(error)
}

次のような順序で処理を行っています。

  1. 必要なメモリバッファを確保する
  2. offsetに書き込み先を入れるため、dataの先頭を代入する
  3. replaceSubrangeで、offsetから4バイトをrect.xで置き換える
  4. 書き込み位置を4バイトずらすため、advancedで4バイトずらした位置をoffsetに代入する
  5. replaceSubrangeで、offsetから4バイトをrect.yで置き換える
  6. 書き込み位置を4バイトずらすため、advancedで4バイトずらした位置をoffsetに代入する
  7. replaceSubrangeで、offsetから4バイトをrect.widthで置き換える
  8. 書き込み位置を4バイトずらすため、advancedで4バイトずらした位置をoffsetに代入する
  9. replaceSubrangeで、offsetから4バイトをrect.heightで置き換える
  10. デスクトップにファイルを出力する

バイナリデータの作り方は他にもあります。場所によって適切な方法を採用すれば良いと思います。

投稿者プロフィール

林 晃
林 晃macOS/iOSアプリ/SDK/ミドルウェア開発が専門の開発者
アールケー開発代表。macOS/iOSアプリ/SDK/ミドルウェア開発が専門の開発者。ObjC/Swift/C++使い。豊富な開発実務経験を基に、教育コンテンツ開発、技術書執筆、技術指導、技術セミナー講師、企業内研修講師、行政・自治体職員研修講師も行います。

基礎から学ぶMetal


「基礎から学ぶMetal」を執筆しました。本書はMetalを使ってGPUプログラミングを行うための最初のステップを解説するMetalの解説書です。

私が初めてGPUプログラミングを行ったとき、どこから手をつけて、学んでいけば良いのか分からず呆然としました。もし、あのとき、これを教えてくれればという部分を解説しました。本書で解説している部分はMetalの基礎となる部分で、Metalを使うときに必ず触れることになる部分です。

詳細

基礎から学ぶSwiftUI


「基礎から学ぶ SwiftUI」というタイトルの本を執筆しました。

SwiftUIの入門書です。

SwiftUIのコンセプトは「ユーザーインターフェイスを作るための最短パスを提供する」「一度学べば(Appleのプラットフォームの)どこにでも適用できる」です。

SwiftUIの概要から始まって、一つ一つのテクノロジートピックに注目しながらSwiftUIとは何か?どんなことができるのか?どのようなコードを書けば良いのかなどを丸々一冊使って解説しています。

詳細

関連記事

  1. OpenCVのセットアップ方法(iOSアプリ用)

  2. ガワアプリには制限と条件がある

  3. M1 Mac への JDK のセットアップ

  4. MacアプリのUIテスト – ダイアログのテスト

  5. Notarization Service の条件の緩和終了

  6. Combine入門 | CombineでTimer処理を行う方法

最近の著書

  1. 基礎から学ぶ SwiftUI

最近の記事