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. デスクトップにファイルを出力する

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

著書紹介

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Akira Hayashi (林 晃)のアバター Akira Hayashi (林 晃) Representative(代表), Software Engineer(ソフトウェアエンジニア)

アールケー開発代表。Appleプラットフォーム向けの開発を専門としているソフトウェアエンジニア。ソフトウェアの受託開発、技術書執筆、技術指導・セミナー講師。note, Medium, LinkedIn
-
Representative of RK Kaihatsu. Software Engineer Specializing in Development for the Apple Platform. Specializing in contract software development, technical writing, and serving as a tech workshop lecturer. note, Medium, LinkedIn

目次