OriosのActiveBasic講座(1-5): ファイル操作

3.バイナリファイルの読み書き

【目標】バイナリファイルを扱えるようになることでさまざまなファイルの処理ができるようになりましょう。

前回と前々回でファイルの読み書きについてやってきましたが、これではまだ全てのファイルが扱えるわけではありません。 あなたはバイナリファイルというのを見たことがありますか? テキストエディタで開くとわけの分からない文字ばかりが並んでいるあのファイルのことです。 これには画像ファイル、音声ファイル、動画ファイル、EXEファイル、圧縮したファイルなどいろいろなものがあり、実際にソフトを作るようになるとそれらのファイルを扱うことが多くなります。 なので今回はそのようなバイナリファイルの読み書きについて学習しましょう。

今回はバイナリファイルの読み書きの練習として、簡単な暗号化プログラムを作ってみましょう。 サンプルを実行する前に、暗号化したい適当なテキストファイルを用意してから、下のプログラムを実行してみましょう。

'lesson1-5-3
#console
Dim key As Integer
Dim filename As String
Dim filedata As String
Dim crypt As String
Dim i As Long
Input "パスワード(0〜255)を入力してください ==>", key
Input "ファイルパスを入力してください ==>", filename
' 読み出し
Open filename As #1
Field #1, Lof(1)
Get #1, 1, filedata
Close #1
' 暗号化
crypt = String$(Len(filedata), Chr$(0))
For i = 0 To Len(crypt) - 1
    ' 文字コードにパスワードの数字を足して暗号化データを作る
    crypt[i] = (filedata[i] + key) Mod 256
Next
' 保存
MkDir "crypt"
' 既に同じファイルがあってはいけないので削除
Kill "crypt\Lesson1-5-3.txt"
Open "crypt\Lesson1-5-3.txt" As #1
Field #1, Len(crypt)
Put #1, 1, crypt
Close #1
Exec "crypt\Lesson1-5-3.txt"
End

実行すると下のような感じになります。

C:\ActiveBasicCourse\lesson1-5-3.abp
パスワード(0〜255)を入力してください ==>100
ファイルパスを入力してください ==>test.txt
※ ファイルパスには、暗号化したいファイルのパスを入力してください。

この後、このプログラムがあるフォルダの中に「crypt」というフォルダが生成され、その中に入力されたファイルを暗号化した「Lesson1-5-3.txt」が作成されます。 このプログラムが終了すると同時に暗号化されたファイルが開かれますが、その内容を見てください。 分けがわからない文字が並んでいますよね。 これは暗号化したファイルが「バイナリファイル」だからです。

余談ですが、暗号化したファイルを元に戻す場合は、同じプログラムでパスワードにマイナスをつければできます。 例えばパスワードが「4」なら「-4」とします。

では今回の説明に入りましょう。

-*-*-*-*-説明-*-*-*-*-
【使い方】
( ) Open 開くファイルのパス  As #ファイル番号 
【意味】
( ) 開くファイルのパス を読み書きモードでファイル番号 として開きます。
【解説】
ファイルの読み書きオープン
(i) 前回前々回でファイルを読み込みモードで開く方法、書き込みモードで開く方法をそれぞれ学習しましたが、今回は「読み書きモード」を学習します。 これはファイルを読み込むことも書き込むこともできるモードです。 普通はそれぞれ読み込むときは「読み込みモード」、書き込むときは「書き込みモード」で開くのですが、今回のように「バイナリファイル」を扱うときなどでは「読み書きモード」で開く必要があります。
(ii) 「読み書きモード」で開くためには今まで書いていた「For モード 」を書かないようにします。 これで「読み書きモード」で開けます。
※1 「読み書きモード」ではファイルを読み込むわけですから、開くファイルのパス が既に存在した場合にも「書き込みモード」のときのようにそのファイルの内容が消えてしまうことはありません。
※2 また「読み書きモード」は同時に書き込みためにも開くわけですから、開くファイルのパス が存在しなかったときには新しくそのファイルを作成します。
【使い方】
(1) Field #ファイル番号 , フィールドのサイズ 
(2) Lof(ファイル番号 )
【意味】
(1) ファイル番号 のファイルにフィールドのサイズのフィールドを設定します。
(2) ファイル番号 のファイルのサイズを返します。
【解説】
(1) Field命令語
(i) バイナリファイル
以前コンピュータでは文字は文字コードで表現するということを学習しました。 そして今まで読み書きを扱ってきたファイルというのは「テキストファイル」といって、ファイルの内容は「文字」の方に重点を置くものでした。 つまりコンピュータの内部ではファイルの内容は「79」(文字コード79→"O")として扱われるとしても私たちはそのファイルを「79」ではなく"O"という内容を持つファイルとして扱います。
しかし「バイナリファイル」というのは、コンピュータの内部でファイルの内容が「79」として扱われているのであれば、そのファイルの内容は"O"ではなく「79」という数字であると扱うというものです。 「バイナリファイル」ではファイルの中の数字が何という文字を表すかということは気にしないのです。
(ii) Field命令語を使う理由
「バイナリファイル」というのは文字コードの方に重点を置いて、その文字コードが何という文字を表すかを気にしないものということを学びました。 なのでファイルの中には「,」や改行を表すために使われるコードなどが大量に出てきます。 そのためこれを「Print #」や「Input #」で書き込もうとすると「,」の部分や改行を表すコードの部分を区切りとして認識されてしまい、この2つは読み書きできなくなります。 これでは「バイナリファイル」は扱えません。 なので「バイナリファイル」を扱うためには別の命令語を使う必要がでてきます。 それが「Field命令語」です。
(iii) Field命令語のときの読み込み方
今までのファイルの読み込み方は「,」か改行のところで区切りながら読んでいきました。 しかし、Field命令語で使うのは別の読み込み方です。 Field命令語を使うときの読み込み方は、ファイルを何バイトかごとに区切って、それの何区切り目を読みこむか指定して読み込むという方式です。 また後で書き込みもしますが、書き込むときもField命令語を使い、そしてこの「ファイルを何バイトかごとに区切る」方式で書き込む場所を準備して書き込みます。
(iv) Field命令語
 前置きが長くなりましたが、実際にField命令語の使い方を説明します。 このField命令語では(iii)で説明した読み方のうち「ファイルを何バイトかごとに区切る」の部分をします。 つまり読み込みの準備をするわけです。
 まずファイル番号 のところで区切るファイルを指定します。 区切るといっても実際にファイルを加工するわけではないので安心してください。 あとファイル番号 の前の「#」を忘れないようにしてください。
 次にフィールドのサイズ の部分で「何バイトごと」に区切るのかを指定します。
 Fieldのイメージは下のようなものです。 「Field #1, 10」の場合は、#1のファイルを10バイトごとに区切ってファイルを読み込む準備をします。 Field命令語のイメージ図
(2) Lof関数
(i) (1)でField命令語を説明しましたが、ファイルを全部読み込むのが目的のときはわざわざ区切る必要はありません。 そのときには「何バイトごと」に区切るかというところにそのファイルのサイズを指定してやればいいのです。 するとファイルをそのファイルのサイズを区切るわけですから「10÷10」の計算をしているようなもので、ファイルは区切られません。
(ii) Lof関数はその「ファイルのサイズ」を求める関数です。 ファイル番号 の部分にサイズを求めたいファイルのファイル番号を書くことで、そのファイルのサイズが求められます。 ここで注意しないといけないことはファイル番号 の前に「#」が要らないということです。
【使い方】
( ) Get #ファイル番号 , フィールド番号 , ファイルの内容を保存する文字列変数 
【意味】
( ) ファイル番号 からField命令語で準備した区切りの中のフィールド番号 を読み込み、ファイルの内容を保存する文字列変数 に保存します。
【解説】
( ) Get命令語
(i) [2](1)でバイナリファイルを読み込むためには、Field命令語を使って「ファイルを何バイトかごとに区切る」という準備をすると書きました。 そして今から説明する「Get命令語」は、その区切られたファイルから実際に読み込むための変数です。
(ii) 「Get命令語」ではファイル番号 の部分で読み込むファイルのファイル番号、フィールド番号 で何番目の区切りを読むかを指定します。 例えば先ほどField命令語のところで使ったイメージ図で、2番目の区切りである「s@w;2,3el」の部分を読み込みたいときは「2」を指定してやります。 そして読み込んだ内容を保存するための文字列変数をファイルの内容を保存する文字列変数 で指定します。
【使い方】
( ) String(回数 , 繰り返す文字列 )
【意味】
( ) 繰り返す文字列 を回数 分繰り返した文字列を返します。
【解説】
( ) String$関数
( ) String型を使えば、ある文字列を指定した回数繰り返した文字列を得ることができます。 例えば、「String$(5, "abc")」とすれば、"abc"を5回繰り返した"abcabcabcabcabc"が戻り値として返ってきます。 今回なら「Chr$(0)」(=NULL文字)を文字列変数「filedata」の長さ分連ねた文字列をcryptに代入しています。
【使い方】
( ) 文字列変数 [場所 ] = 代入するもの 
【意味】
( ) 文字列変数 の(場所  + 1)文字目の文字コードを代入するもの に変えます。
【解説】
( ) 文字列へのアクセス(2)
(i) 文字列へのアクセスのところで文字列は「str[i]」のようにすることで(i + 1)文字目の文字コードを取り出せることを学びました。 この「str[i]」は(i + 1)文字目の文字コードを表しています。 なので逆に「str[i]」に文字コードを代入することもできます。 例えば「str = "abc"」のときに「str[1] = 66」(文字コード66→"B")とすれば、(1 + 1)文字目、つまり2文字目の文字コードが66に変わって、文字列変数「str」は"aBc"になります。
(ii) 今回ように「str[i]」で文字列を操作するときは、何文字目かを指定するときにその指定したところに文字がある必要があります。 つまり「str = "abc"」のときに「str[5]」なんてことはできないということです。 仮にこんなことをすると、別の変数が使っているところのメモリーにアクセスしてしまうことにもなりかねず、エラーが出ることもあります。
下の図を見て下さい。
場所0123456
内容abcNULL???
これが先ほど例を挙げた文字列変数「str」だと思ってください。 「0」〜「2」までにはそれぞれ"a"・"b"・"c"が、「3」には文字列の終端を表す「NULL文字」が入っています。 しかし「4」以降はstr変数の外なので何が入っているか分かりません。 ひょっとしたら「4」以降は別の変数が使っているかもしれません。 もしそうだったとしたら間違って「4」の部分を操作してしまったら他の変数の値を変えてしまうかもしれません。 なのでここで例を挙げている文字列変数「str」は「4」以降を操作してはいけないのです。
(iii) しかし、今までのように先に文字列があって、それを読み取ったり書き換えたりするのではなく、今回のように新たに文字列を作っていく場合には、先ほど書いたエラーを起こさないようにあらかじめ文字列として使う領域を確保する必要があります(これはcalloc関数のときと同じ考え方です)。 そこで文字列を確保する方法の一つにNULL文字で埋めるという方法があります。 先にNULL文字を必要な分だけ並べて文字列として使う領域を確保するのです。 なので先ほど説明した部分ではString$関数をあらかじめ必要な領域を確保しているのです。
【使い方】
( ) MkDir 作成したいフォルダのパス 
【意味】
( ) 作成したいフォルダのパス を作成します。
【解説】
( ) MkDir命令語
( ) 作成したいフォルダのパス に作りたいフォルダのパスを書くとそのフォルダが作成されます。 例えば「C:\AB」というフォルダを作りたければ「MkDir "C:\AB"」と書きます。 このときフォルダのパスは"C:\AB"と書いても"C:\AB\"と書いても構いません。 もしパスではなくフォルダ名のみを書いた場合は、このフォルダはプログラムがあるフォルダの中に作成されます。 またパスで"C:\AB"フォルダがないのに「MkDir "C:\AB\CD"」としても自動的に"C:\AB"も作ってくれるようなことはなく失敗します。
【使い方】
(1) Kill 削除したいファイルのパス 
【意味】
(1) 削除したいファイルのパス を削除します。
【解説】
(1) Kill命令語
( ) 削除したいファイルのパス に削除したいファイルへのパスを書くとそのファイルが削除されます。 注意していただきたいのは、このとき削除されたファイルは「ごみ箱」に行かず、直接削除されてしまいます。 直接削除された場合は、ファイルを復活させることはほぼ不可能です (加藤高明さんが作られている復元というソフトを使えば復元できる可能性はありますが100%復元される保証はありません)。 なので削除には十分注意してください。
(2) 相対パス
(i) 今までにファイルを指定するために「パスを書く」と書いてきましたが、ここで簡単にパスについて説明します。
(ii) まずは「絶対パス」から説明します。 これは今まで書いてきた「C:\ActiveBasicCourse\lesson1-5-2.abp」のようなものです。 これは「マイ コンピュータ」から開いていったフォルダなどを順々に「\」で区切りながら書いていき、最後にファイル名を書いたものです。 つまり「C:\ActiveBasicCourse\lesson1-5-2.abp」なら「マイ コンピュータ」→「C:」→「ActiveBasicCourse」と開いていき、その「ActiveBasicCourse」というフォルダの中にある「lesson1-5-2.abp」を指します。
(iii) 次に「相対パス」を説明します。 これは絶対パスとはちがい、実行しているプログラムがあるフォルダ(「カレントディレクトリ」と言います)を基準にして考えるパスです。
  • プログラムのあるフォルダの中にあるファイルはファイル名だけを書きます。 今まで「ファイル名だけを書けば……」と書いていたのは、この「相対パス」です。
  • そのプログラムのあるフォルダの中にさらにフォルダがある場合は、「フォルダ名」を「\」で区切りながら書いていき、最後にファイル名を書きます。 例えばプログラムがあるフォルダの中に「AB」フォルダがあり、その中にさらに「CD」フォルダがあり、その「CD」フォルダの中に「test.txt」というファイルがある場合は、「AB\CD\test.txt」と書きます。
  • 一つ上のフォルダを指したい場合は、「..\」という記号を使います。 例えば「C:\AB\CD\EF」フォルダにプログラムがあり、そこから「C:\AB\GH\test.txt」というパスのファイルを表したい場合は、「..\..\GH\test.txt」と書きます。 これはまず最初の「..\」で、一つ上のフォルダである「C:\AB\CD」を指し、次の「..\」で「C:\AB」フォルダを指します。 そして、次の「GH\」で「C:\AB」フォルダの中の「GH」フォルダを指し、最後に「GH」フォルダの中の「test.txt」というファイルを指しています。
【使い方】
( ) Put #ファイル番号 , フィールド番号 , ファイルに保存したい内容が保存された文字列変数 
【意味】
( ) ファイル番号 に、Field命令語で準備した区切りの中のフィールド番号 にファイルに保存したい内容が保存された文字列変数 を書き込みます。
【解説】
( ) Put命令語
(i) Get命令語でバイナリファイルを読み込むためには、Field命令語を使って「ファイルを何バイトかごとに区切る」という準備をすると書きましたが、バイナリファイルを書き込むときにもFieldで準備をします。 このときもFieldの使い方は同じです。 ただ注意していただきたいのは、ファイルを区切るときにはファイルの外の部分も区切られるということです。 例えば3バイトしかないファイルでも「Field #1, 10」と準備して、このフィールドの2フィールド目に書き込むときには、ファイルが3バイトしかないにもかかわらず、4〜10バイト目はNULL文字で埋められ、11バイト目から10バイト分書き込まれます。
(ii) 「Put命令語」ではファイル番号 の部分で書き込むファイルのファイル番号、フィールド番号 で何番目の区切りに書き込むかを指定します。 そして書き込む内容が保存された文字列変数をファイルに保存したい内容が保存された文字列変数 で指定します。 ここでは"abc"のように書きたい文字列を直接指定してもエラーではじかれます。
(iii) 既に中身があるファイルに書き込むときはフィールド単位で上書きされます。 例えばField命令語のところで例に挙げたファイル(10バイト区切り)の2フィールド目("s@w;2,3el"の部分)に書き込む場合は、書き込む内容が10バイトに満たない場合でも無理やり10バイト書き込まれます。 なので「str = "abc": Put #1, 2, str」と書いた場合は"s@w;2,3el"の部分が"abc.ォォォォォォ"という感じに無理やりにでも10バイト分書き込まれます(注:".ォォォォォォ"の部分は管理人が実験した結果こうなったので書きましたが、使っているパソコンによってどうなるか分かりません)。 また逆に書き込む内容が10バイトを超えてしまった場合は、10バイトを越える部分がカットされます。 このようにフィールド単位で書き込みは行われます。

今回の内容はどうでしたか? 「バイナリファイル」といってもなかなか馴染みがないかもしれませんが、冒頭にも書いたとおり、いろいろなファイルがこの「バイナリファイル」です。 今後プログラミングをしていく中で「バイナリファイル」を扱うことがきっと出てくると思います。 そのときに備えてしっかりと今回学習したことを定着させてください。

今回をもって「OriosのActiveBasic講座」の基本編は終わりです。 今までこの講座をご利用されてきた方、ありがとうございました。 この後は、いくつか番外編をはさんで、その後にいよいよ「ウィンドウプログラム編」をします。 今後とも「OriosのActiveBasic講座」をよろしくお願いします。

では基本編最後の「やってみよう」です。 今回は基本編の最後ということで少し難しくなっていますが、ぜひ学習した内容を確認するためにトライしてみてください。

-*-*-*-*- -*-*-*-*-
【数当てゲームを作ろう】

乱数を用いて毎回1〜3の数字を用意し、それをユーザに当ててもらうゲームを作りましょう。 条件は次の通りです。

  • ゲーム結果を記録するためにバイナリファイルを用います。 このバイナリファイルは勝数と敗数を文字コードを使ってそれぞれ表して並べたものです。 例えば「3勝2敗」なら文字コード「3」と文字コード「2」を並べて表します。 右のファイルが今例を挙げた「3勝2敗」のファイルです。[ LetsTry1-5-3.txt ]
  • ゲームの前に上の対戦結果を記録したバイナリファイルを読み込んで今までの結果を表示するようにしましょう。
  • ゲームは一回終わるごとに「続けてやりますか?」と聞き、「N」もしくは「n」が入力されたときにプログラムを終わるようにしましょう。
  • プログラムを終了するときに累計の対戦結果(前回の結果も含めた結果)を表示して、さらにその結果を対戦結果を記録するバイナリファイルに保存しましょう。

《ヒント》
  • 対戦結果が記録されたバイナリファイルを読み込むと、その文字列の[0]が勝数、[1]が敗数になります。
  • もしファイルがないときは文字列が0バイトになるので[1]をするとエラーが起こるかもしれません。どうしましょう?
  • 正解の数字を決めるには乱数を使いましょう。

ご質問・ご意見・ご感想・苦情等はメールまたはメイン掲示板のActiveBasic板などでお気軽にどうぞ。

前に戻る - 次に進む

ホーム > OriosのActiveBasic講座(トップページ) >