昔作ったプログラムを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) }
次のような順序で処理を行っています。
- 必要なメモリバッファを確保する
offsetに書き込み先を入れるため、dataの先頭を代入するreplaceSubrangeで、offsetから4バイトをrect.xで置き換える- 書き込み位置を4バイトずらすため、
advancedで4バイトずらした位置をoffsetに代入する replaceSubrangeで、offsetから4バイトをrect.yで置き換える- 書き込み位置を4バイトずらすため、
advancedで4バイトずらした位置をoffsetに代入する replaceSubrangeで、offsetから4バイトをrect.widthで置き換える- 書き込み位置を4バイトずらすため、
advancedで4バイトずらした位置をoffsetに代入する replaceSubrangeで、offsetから4バイトをrect.heightで置き換える- デスクトップにファイルを出力する
バイナリデータの作り方は他にもあります。場所によって適切な方法を採用すれば良いと思います。











