3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnityAdvent Calendar 2023

Day 9

FileStreamの正体🤔(UnityAndroidの場合)

Last updated at Posted at 2023-12-09

前書き

この記事は、2023のUnityアドカレの12/9の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい!

はじめに

C#でファイルを扱うときには、System.IO.FileStreamという抽象化された型で取り扱うことができます。C#…のランタイムである.NETは、これをOSレベルのファイルシステムにつなぐことで実際のファイルにアクセスできるということです。

Androidのネイティブ(NDKによるC++)とつなぐ必要があったり、このFileStreamが何者なのかを調べる必要がありました。本記事ではそれを追跡する過程をご紹介します。

とりあえずC#で書いてみる

適当にFileStreamを使ったC#コードを記述します。

public class FileAccessTest : MonoBehaviour
{
    void Start()
    {
        var fs = new FileStream(
            Path.Combine(Application.persistentDataPath, "test.txt"),
            FileMode.Create);
        fs.Write(Encoding.UTF8.GetBytes("Hello World!"));
        fs.Dispose();
    }
}

FileStreamは、BCL(.NETのコアライブラリ)の中にあるクラスです。

UnityのBCLのFile

UnityのBCLはMono実装のUnityカスタム版です。

mcs/class/corlib/System.IO/FileStream.cs
var nativeHandle = MonoIO.Open (path, mode, access, share, options, out error);
this.safeHandle = new SafeFileHandle (nativeHandle, false);

MonoIOなるものをOpenし、そのハンドルをFileStream.safeHandleに突っ込んでいます。つまり、FileStreamの裏にはMonoIOなるものがあるようです。まだ抽象層でした。MonoIO.Openを追ってみます。

mcs/class/corlib/System.IO/MonoIO.cs
[MethodImplAttribute (MethodImplOptions.InternalCall)]
private unsafe extern static IntPtr Open (char* filename,
    FileMode mode,
    FileAccess access,
    FileShare share,
    FileOptions options,
    out MonoIOError error);

InternalCall、つまり、BCLではなく、VM側に実装が移るということです。

UnityのVMのFile

mono/metadata/icall-def.h
ICALL_TYPE(MONOIO, "System.IO.MonoIO", MONOIO_39)
...
NOHANDLES(ICALL(MONOIO_16, "Open(char*,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare,System.IO.FileOptions,System.IO.MonoIOError&)", ves_icall_System_IO_MonoIO_Open))

BCLのSystem.IO.MonoIO.Openは、VMのves_icall_System_IO_MonoIO_OpenにBindingされていることがわかります。

mono/metadata/w32file.c
HANDLE 
ves_icall_System_IO_MonoIO_Open (const gunichar2 *filename, gint32 mode,
				 gint32 access_mode, gint32 share, gint32 options,
				 gint32 *error)
{
    ...
    ret=mono_w32file_create (
        filename,
        convert_access ((MonoFileAccess)access_mode),
        convert_share ((MonoFileShare)share),
        convert_mode ((MonoFileMode)mode), attributes);
    ...
}

w32file系は、プラットフォームごとにファイルが分かれており、mono_w32file_createも複数ありますが、今回はAndroidを狙うので、w32file-unix.cを見ます。

image.png

mono/metadata/w32file-unix.c
gpointer
mono_w32file_create(const gunichar2 *name, guint32 fileaccess, guint32 sharemode, guint32 createmode, guint32 attrs)
{
    ...
    fd = _wapi_open (filename, flags, perms);
    ...
    filehandle = file_data_create (type, fd);
    ...
    return GINT_TO_POINTER(((MonoFDHandle*) filehandle)->fd);
}

次は、_wapi_openです。

mono/metadata/w32file-unix.c
#include <fcntl.h>

static gint
_wapi_open (const gchar *pathname, gint flags, mode_t mode)
{
    ...
    fd = open (pathname, flags, mode);
    ...
    return(fd);
}

open関数でどん詰まりになりました。これは、fcntl.hに宣言されたPOSIX関数です。

ということで、UnityのAndroidにおける、FileStreamはPOSIXのopen関数で得られるファイルハンドルをラップしたものでした。

C#からファイルハンドルを取得する

FileStreamには、FileStream.SafeFileHandleという尤もらしいプロパティがあります。

mcs/class/corlib/System.IO/FileStream.cs
var nativeHandle = MonoIO.Open (path, mode, access, share, options, out error);
this.safeHandle = new SafeFileHandle (nativeHandle, false);

FileStream.Openで、MonoIO.Openの返り値をSafeFileHandleクラスに包んで控えていました。

MonoIO.Openの返り値を遡って考えていきましょう

FileStream.safeHandle = new SafeFileHandle(MonoIO.Open(...), false);
    => ves_icall_System_IO_MonoIO_Open (...);
    => mono_w32file_create(...);
    => GINT_TO_POINTER(((MonoFDHandle*) filehandle)->fd);
filehandle = file_data_create (type, fd)
filehandle->fd = fd
fd = _wapi_open(...)
    => open(pathname, flags, mode);

ということで、

FileStream.SafeFileHandle = new SafeFileHandle(fileHandle_of_Posix);

であるということがわかりましたね。SafeFileHandleの中身はSafeFileHandle.handleでIntPtr型として取りだすことができます。

まとめ

このように、C#のFileStreamが何者なのかがはっきりしました。例えばC#でFileStreamを開き、Androidのネイティブに渡したいときなどには、FileStream.SafeFileHandle.handleを渡し、ネイティブの処理が終わったらCloseすればよいということです。(protectedなので適当な方法で取り出す、または、非推奨なHandleフィールドへアクセスする必要があります)

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?