2013-01-10

FILE構造体でメモリを利用する方法  [by miyachi]

久しぶりの技術ネタです。私もオープンソースのライブラリには常日頃お世話になっています。公開してくださっている開発者の皆さんには心から感謝しています。私も今年はオープンソースとライセンスのプロジェクトを開発することで少しでも恩返しができればと考えていたりするのですが…

閑話休題、今日のネタはライブラリの入出力がFILE構造体指定しか無いけどファイルでは無くメモリ上で入出力する方法です。これは実際に先日libpngやlibjpegを使おうとした時にぶつかった問題です。元ファイルを読み込んだ後で画像フォーマットの変換をメモリ上で行いたいのですが、サンプル等は全てFILE構造体になっています。これをメモリのポインタを利用するようにすることも可能だと思いますが面倒で時間もかかりそうなのでサンプル実装をそのまま使いたいと言うことです。更に今回はWindowsとLinuxの両方でこの問題を解決する必要もありました。残念ながらWindowsとLinuxで共通した方法は見つかりませんでしたが、どちらでもFILE構造体を使ってメモリ上で操作する方法が分かりましたので簡単にまとめます。

1)Linux編

Linuxでは簡単です。glibcでPOSIX準拠のfmemopen()やopen_memstream()と言う関数を利用すれば解決です。fmemopen()で書き込みオープンも可能なのですが、fmemopen()で書き込みした場合にはclose()すると解放されてしまいます。そこで書き込みオープンにはopen_memstream()を使っています。open_memstream()はclose()しても勝手に解放しません(もちろん自分でfree()が必要)。閉じるのはどちらも通常のclose()で構いません。

// インクルード(stdio.hだけで良い)
#include <stdio.h>

// 読み込みオープン(バイナリ)
FILE* memropen(const char* ptr, size_t sz)
{
return fmemopen((void*)ptr, sz, "rb");
}

// 書き込みオープン
FILE* memwopen(char** ptr, size_t* sz)
{
return open_memstream(ptr, sz);
}

// 閉じる(通常のクローズで良い)
void memclose(FILE* fp)
{
fclose(fp);
}

// 利用例
void testLin(const char* src, size_t srcSz, char** dst, size_t* dstSz)
{
char buf[1024];
*dst = NULL;
*dstSz = 0;
// オープン
FILE* srcFile = memropen(src, srcSz);
FILE* dstFile = memwopen(dst, dstSz);

// 読み込み(※例なので1024バイト未満と仮定する)
size_t inSz = fread(buf, sizeof(char), 1024, srcFile);
// 書き込み
fwrite(buf, sizeof(char), inSz, dstFile);

// 読み込みクローズ
memclose(srcFile);
// 書き込みクローズ
memclose(dstFile); // クローズするとdstとdstSzが利用可能
// dstは呼び出し元でfree()が必須
// free(*dst);
}

簡単ですねw 上記例で取得したFILE構造体を使いたいライブラリのFILE構造体引数に渡せばOKです。

2)Windows編

さて問題はWindows環境です。残念ながらVC++ではfmemopen()やopen_memstream()はサポートされていません。_pipe()と_fdopen()を使います。_pipe()を使ってパイプをオープンし、_fdopen()を使ってFILE構造体に変換して利用します。つまりパイプをメモリストリームとして利用してしまうことになります。Linuxに比べると美しくないですがとりあえず動作上は問題ありません。

// インクルード(io.hは必須)
#include <io.h>
#include <fcntl.h>
#include <errno.h>

// 読み込みオープン(バイナリ)
FILE* memropen(const char* ptr, size_t sz, int p[2])
{
int rc = _pipe(p, (unsigned int)sz, _O_BINARY);
if(rc != 0)
return NULL;
// まず書き込んでやる
_write(p[WRITE], ptr, (unsigned int)sz);
// 読み込みをFILE構造体に変換
return _fdopen(p[READ], "rb");
}

// 書き込みオープン(バイナリ)
FILE* memwopen(int p[2])
{
int rc = _pipe(p, (unsigned int)0, _O_BINARY);
if(rc != 0)
return NULL;
// 書き込みをFILE構造体に変換
return _fdopen(p[WRITE], "wb");
}

// 閉じる
void memclose(FILE* fp, int p)
{
fclose(fp); // _fdopen()でオープンしたのでfclose()で閉じる
_close(p); // _pipe()でオープンしたので_close()で閉じる
}

// 利用例
void testWin(const char* src, size_t srcSz, char** dst, size_t* dstSz)
{
char buf[1024];
// オープン
int sp[2], dp[2]; // パイプ用の入出力ハンドル
FILE* srcFile = memropen(src, srcSz, sp);
FILE* dstFile = memwopen(dp); // パイプ書き込みオープン

// 読み込み(※例なので1024バイト未満と仮定する)
size_t inSz = fread(buf, sizeof(char), 1024, srcFile);
// 書き込み
fwrite(buf, sizeof(char), inSz, dstFile);

// 読み込みクローズ
memclose(srcFile, sp[WRITE]);
// 書き込み結果の取得(パイプなので書き込んだ結果を読み込むことで取得)
*dst = malloc(inSz);
*dstSz = _read(dp[READ], *dst, (unsigned int)inSz);
// 書き込みクローズ
memclose(dstFile, dp[READ]);
}

ちょっとオープンとクローズや書き込み結果取得方法がLinux版とは異なってしまいますがとりあずメモリを使ってFILE構造体のAPIに渡すことが可能になります。もっとスマートな実装はあると思いますがとりあえず動けば良いと言うソースになっています。お使いになる時はエッセンスだけ取って頂いてもっとスマートに実装してください。以上参考になれば幸いです。
2013-01-10 12:21:34 - miyachi - [プログラミング] -

コメント一覧

コメント無し

コメントを書く

このアイテムは閲覧専用です。コメントの投稿、投票はできません。