無料ブログはココログ

2018年4月22日 (日)

スタックコンピュータとFORTH(2)

Dsc_0006_trim_2

 私のCPUについての知識は、その昔マイコン時代のエンジニアが皆そうだったようにCPUについて多少の知識があった(特にプログラムをアセンブラで書く場合)という程度のもので、この記事で間違ったことを書いていたらご指摘いただけるとありがたいです。

 取り上げる書籍が古いのはもちろんですが私のハードウェア知識も古いままなのでそのあたりはご容赦ください。

---

 前回と重複するが書籍「スタックコンピュータ」の情報から。

 写真は 1994年、「スタックコンピュータ」、Philip J. Koopman, Jr.著、田中清臣監訳/藤井敬雄訳、共立出版である。中古本がアマゾンで売られている。

 本書ではスタックコンピュータの歴史から始めてスタックマシンの特徴の解説や、実際に使われたマシンの解説(ニーモニックのリストを含む)など、実に多義かつ詳細にまとめている。
 古い話が多いが現在でも参考になることは多く、スタックマシンを系統立ててじっくり勉強するのに向いていると思う。
 値段(5千円超)もさることながら、ボリュームもあり、正直に言うと私は購入時にざっと斜め読みしただけでしばらくそのままにしていた。その後2、3回ほど読み直し、今回この記事を書くために改めて読み直したが、それでも末尾の付録を除いた本文として6割ぐらいしか読めていない。

 FORTHの綴りについて。
 古くから FORTH と全部大文字で書いていたのだが、本書の書き方や、FORTH社のHPを見ても Forth と書いているようなのでこの2回目の記事ではそのようにした。全部大文字は(社名の)FORTH社を指すときぐらいなのかも知れない。

 なお本書とは別だが、ごく手短にスタックコンピュータのことを知りたい場合はウィキペディアの「スタックマシン」の解説を参照されたい。

 時代背景について。
 訳書は1994年発行だが原書の時期が気になったのでWebで調べるとインターネット上にデジタルコンテンツが見つかり1989年と分かった。

 Stack Computers: the new wave -- an on-line book
 https://users.ece.cmu.edu/~koopman/stack_computers/index.html
 (カーネギーメロン大学)

 訳書で冒頭の「原著序文」にはこのようなことが書かれている。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 この本は Novix の NC4016 の開発により口火が切られたスタックコンピュータの新しい流れに関して書かれている。読者のなかには勘違いして Burroughs や HP のスタックマシン類を想定するかもしれないが,この本の焦点はまったく異なる。こられの新しいスタックコンピュータはまったく異なった技術とアプリケーション分野のトレードオフを含み,旧世代のスタックコンピュータとはまったく似ていないマシンになっている。
\___________________________/

 私はだいぶ昔、誰かから聞いた話として、スタックを明に使うコンピュータとしてはバローズのメインフレームあたりが最初でずっと連綿と続いていると思っていたのだが、上記を読むと世代が2つに分かれるようだ。

 私はこの本によく登場する Novix NC4016 とか、Harris RTX 2000 などはまったく知らず、時代的にピンと来ないのだが、本書の中で(インテルの)80386、(モトローラの)68020 といった32bitプロセッサの話が登場するので、そのあたりの時期に書かれたのだと理解できた。ミニコンでは VAX という言葉も登場する。

 またコンピュータ・アーキテクチャの議論として昔、CISC 対 RISC の話がよく登場したころがあったが、この用語も本書で随所に登場する。ちなみに著者は RISC にからめて次のようなことを言っている。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 この意味において,スタックマシンは RISC の '縮小命令セットコンピュータ' に対して '縮小オペランドセットコンピュータ' であるといえる。
\___________________________/

 上記にある「オペランド」は、本書でスタックコンピュータの特質をあらわすのに重要な意味を持っている。

 さらに時代背景としては当然、プログラム言語 Forth との関係が気になるところだ。
 Forthの歴史はだいぶ古く、チャールズ・ムーア氏が1968年に開発したとされている。本書(原書)が書かれるより12年前である。

 ( Forthの歴史については本家の FORTH, Inc. のサイトで以下が詳しい
     Forth programming language, history and evolution
     https://www.forth.com/resources/forth-programming-language/ )

 また、本書冒頭で「Novix の NC4016 の開発により口火が切られた」と書かれたこのマシンはWebで調べると1985年らしく、NC4016 の開発はForth誕生から17年後ということになる。本書でも書かれているように、多くの(新世代の)スタックマシンは、Forthをより効率的に走らせることを主な目的としたようだ。

 本書の趣旨からしてスタックコンピュータの有意性は随所に書かれているのだが、いくつか例を挙げる。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 スタックマシンが最初に成功したアプリケーション分野は、リアルタイムの組み込み制御環境である。スタックマシンが、他のシステム設計アプローチよりずっと性能が優れている。

 ~略~

 スタックマシンをプログラムすることは、従来のマシンよりもたやすく、スタックマシンのプログラムは他のプログラムより確実に走るとされている。
 スタックマシンは、コンパイラがより簡単に書ける。なぜなら、コンパイラを複雑にする例外的ケースがより少なくなるからである。

 ~略~

 (他の文献について触れたあと)この本を見ていくにつれ、スタックマシンはまた、レジスタマシンよりある種のプログラム、特によくモジュール化されたプログラムを実行されることでもずっと優れていることがわかる。また、他のマシンよりシンプルであり、小さなハードウェアで大変優れた処理能力をだす。スタックマシンの特に有望なアプリケーションは、リアルタイムの組み込み制御アプリケーションである。これには、小さなサイズ、高い処理能力、そして割り込みハンドリングの優れたサポートの組み合わせが必要とされ、スタックマシンのみがこれらすべてを待たすことができるのである。

 ~略~

 注目すべき興味深い点は、これらのマシンのいくつかが、まずForthを実行するよう設計されたにもかかわらず、一般言語をはしらせるにも適していることである。
\___________________________/

 上記で言われているような有意性にもかかわらず、残念ながら今現在、スタックマシン(CPUのレベルからスタック指向という意味で)が組み込み制御環境で広く使われるようにはなっていない(私が知らないだけかもしれないが)。昔、マイクロプロセッサの 32bit化が始まったとき、性能的に優れていた Z8000 や 68000 ではなく、どちらかというと泥くさいアーキテクチャの8086が結果的に普及したことを思い起こさせる。

 次のものはスタックコンピュータという呼び方にはなじまないかも知れないが、プロセッサのレベルからのスタック指向で、かつ、組み込み用途と考えられなくもない。

 ・80x87(浮動小数演算コプロセッサ)

 さらに少しややこしいが、「スタックマシンではない組み込み環境でのスタック指向プログラムの使われ方」(つまりCPUはスタック指向ではないがプログラムはスタック指向)というケースでは下記のように意外と多く使われている。

 ・プリンタ制御言語の PostScript
 ・通信用スクリプト言語の TeleScript の下位レイヤ
 ・Java の下位レイヤ(Java Virtual Machine)
  (記:AndroidのJava VMはスタックマシンではないらしい)

 前の引用の中で触れられている組み込み制御分野での有意性の根拠の一つ「割り込みハンドリングの優れたサポート」の具体的なものは以下のようだ。

 割り込み発生と同時に、一般的なCPUではレジスタやフラグをメモリに退避する必要かあるが、スタックマシンではレジスタもフラグも無いのでその分高速に処理できる。スタックマシンでは、演算中のデータはスタックにあるため、‥割り込み処理が主処理のスタックの続きを使うのであれば‥データの退避の必要が無いということである。

 ただし、マルチタスク(マルチスレッド)あるいは、割り込みが完全な入れ子になっていない場合、「スタックの続き」を使うことができないため、スレッド毎に割り当てたスタック領域を切り替える必要がある。

 本書では「6.3.5 コンテキスト切り替え」という独立項を設け、実験をふまえてそのコストについて論じている。そこで次のくだりは少し意外であった。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 コンテキスト切り替えのオーバーヘッドはスタックマシンがなぜマルチタスキングに向かないのかという理由としてよく引き合いに出される。膨大なスタックバッファ空間をプログラムメモリに保存しなければならないというのがこの論拠になっている。しかしマルチタスキング処理においてスタックマシンが他のマシンより幾分劣っているというこの意見は明らかに誤りである。

~略~

このような低いオーバーヘッドはどのように達成されるのだろうか。理由の一つにはこれら三つの重い再帰プログラムを実行している間の平均的なスタックの深さはたったの12.1要素であることだ。ころさはスタック上にあまり情報が存在しないので、コンテキスト切り替え時に保存しなければならない情報がほとんどないことを意味する。

~略~

\___________________________/

 私の推測だが、新世代のスタックマシンではスタック領域をオンチップの高速な場所に置いているため、タスク切り替え時に「退避」する必要がある‥ということのようだ。

 私自身はいままでスタックマシンを触ったことがなく、あくまでプログラム言語としてのForthを通してスタックマシンをいわばエミュレートした環境で動かしていたにすぎなかった。そのため、オンチップの高速なスタック領域については知らず、上記冒頭の書き出しを意外に思った(しかし著者はそれについても上記のように反論しているが)。

 考えてみれば、レジスタに匹敵する高速なアクセスをスタックに持たせるのだろうからスタックはオンチップになり、そのサイズはメインメモリに比べればあまり大きくないということは理解できた。

 脇道に逸れるが、スタックマシンは、というより、スタック指向のプログラムが、スタックを全面的に使うわりにはスタックはそれほど深くはならない‥ということは私も経験上分っていたので、前記実験でのスタック深さの平均が12要素という説は納得した。

 本書の目玉は、「第2章 ハードウェアスタックの分類」である。スタックコンピュータを三つの座標軸で分類するというものだが、実は多くのコンピュータ(CPU)をこれで分類できる。分類のための評価軸は以下となるという。

 スタックの数       = 単一または複数
 スタックバッファのサイズ = 小または大
 オペランドの数      = 0,1または2

 「スタックバッファのサイズ」については説明を要する。ここで言うスタックバッファとはスタック専用のメモリのことを指す。

 「小さなスタックバッファ」は、たとえばスタックTOPと2NDをすばやく(レジスタのように)アクセスできるよう工夫しておき、それより奥のスタック領域は普通のプログラムメモリ空間を使うもの‥などを指すようだが、そのような小さなバッファさえ持たないマシンでも分類上は「小」扱いとする。

 「大きなスタックバッファ」とは、スタック領域全体をオンチップに収容するものである。

 私が思ったのは、筆者の命名とはうらはらに、「小さなスタックバッファ」のほうが、普通のメモリ空間にスタックを確保することからサイズの制限が無い、つまり大きいな領域が取れるという意味で、「小さい」/「大きい」が逆にも思えて妙な感じがした。
 どちらも長所・短所があり、どちらが優れているというものでもなさそうだ。

 次は本書の分類表の中で私が知っているわずかのマシンについて(いずれもスタックマシンではなく汎用CPUだが)ピックアップしたものである。

 SS2   Intel 80x86
  MS1   PDP-11
  MS2   Motorola 680x0

  上記で左端の文字はスタックの数(Single/Multiple)を示し、2番目の文字がスタックバッファのサイズ(Small/Large)を、3番目の文字がオペランド数を示す。

 たとえば 80x86 は Singleスタック、Smallサイズ、2オペランドのマシンとなる。

 680x0 は Multipleスタックとして分類されている。80x86との違いはスタックの数である。68000ではどのレジスタにもスタックポインタの役割を持たせられるので Multiple となったのだろう

 本書ではスタックコンピュータの本質をこの分類上、「複数スタック、0オペランドのマシン」と位置付ている。この「0オペランド」という言い方は私の興味を引いた。

 オペランド数からCPUを分類し、仮にアセンブラ表記で書いてみると次のようになる。

 2オペランドマシン
    ADD  R1, R2

 1オペランドマシン
    ADD  R1

 0オペランドマシン
    ADD     ←オペランドが無い!

 上記で「1オペランドマシン」は、加算値は書かれるが、被加算値が省略される。この場合アキュムレータと呼ばれる暗黙のレジスタが演算対象となる。
 「0オペランドマシン」は、加算値と被加算値が共に省略される。暗黙のデータはレジスタではなくスタックとなる。

 確かにスタックマシンでは演算対象は スタックTOP とか TOPと2ND などに決まっているため、オペランドの指定は不要であり、したがって「0オペランド」と言うことはできる。当たり前のことなのだが、一般のマシンと同列に比較したことが私には面白かった。

 それで、0オペランドだと何が良いのかが重要らしい。(Forthを効率的に動かせるという話は置いて)。
 まずオペコードの数が極端に減る。オペコードの数は乱暴に言えば「命令の種類」×「オペランドの組み合わせ数」となるのだが、これが「命令数」だけになるのだから減るのは当然である。本書の中には次のくだりがある。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 もう一つの利点は、256の異なったオペコードのために8ビット幅の命令フォーマットで足りるというように、個々の命令がきょくたんにコンパクトになることである。
\___________________________/

 これよにり、オペコードが単純化され、それをデコードする(オペランドが指すアドレシングも含めて)ための仕組みも単純となる。トランジスタ数も減って平均故障率も減り高速化されるという仕組みである。昔、CISC対RISCの議論があったが、RISCよりもっとRISCらしい構造と言える。

 もちろんいまどきのCPUでは、たとえば8ビットというごく短い命令フォーマットが嬉しいというシーンはあまり無さそうだが(制御環境ではあるかも知れない)、私がこれで思い出したのがインテルの 8087 だった。(なぜか本書は 8087 には触れていない)

 8087(もしくは 80x87)は 8086 と協調して浮動小数演算を行うチップ(コプロセッサ)である。一時データはレジスタではなくスタックに保持するものでスタックマシン風の形態をとっている。
 8087は8086のバスを流れる命令を常にウォッチしており、ESC命令を検出するとメインCPUである8086はバスを解放し、代わりに8087が動作を始める‥という、まさに両者は協調した動作をおこなう。
 8087向けのオペコードは8086向けオペコード空間のわずかな空き領域(ESC命令がそれ)を使っており、当然ながら8087が使えるオペコードはかなり少ない。少ないオペコードでなんとかやりくりする必要がある。結果として8087がスタックマシン風になっているのはインテルの設計者の好みではなく、必然だったと言えるだろう(私の勝手な推測として)。

 ただ、8087 の命令体系を見るときれいなスタックマシンではない。たとえば「0オペランド」命令は少なく、1つ/2つのオペランドを持つことがほとんどだし、比較演算の結果はフラグに入る(本来のスタックマシンではフラグは使わない)など、相当変則的である。また演算に特化しているため、分岐命令などプログラムとして必須の命令も無い。本書が 8087 を扱わなかったのはそのあたりが理由かも知れない。

 8086/8087 が使われていた時代、私がやっていたリギーコーポレーションではForth系で8086向けの Fifth86 というコンパイラの浮動小数ライブラリでは浮動小数のForthワードをほぼ1対1で8087の命令にダイレクトに割り当てていた。たとえば、

 F+

というForthワードは 8087 の

 FADD

にそのまま対応させた。通常のデータスタックと別に8087のスタックを併用するために(浮動小数演算については8087スタックをそのまま使う)、スタック間移動ワードを設ける必要はあったが、浮動小数演算については大変能率の良いコードを生成できた。

 8087が「スタックマシンもどき」でしかないにもかかわらずだいぶ書いてしまったが、8087が私がいままで唯一、身近に接することのできたスタックマシン(?)だからである。本書で紹介されているスタックマシンはまったく知らないものばかりなので‥。

 本書では「第4章 16ビットシステムのアーキテクチャ」から、いくつかの実際のスタックマシンについて解説をおこなっている。この章のはじめで筆者は次のように言っている。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 ここで述べるシステムは16ビット幅にする第一の動機は、Forthプログラミングモデルが伝統的に16ビットであったことである。これは32Kバイト以下というForthプログラムの平均サイズと、64Kバイトのアドレス領域をもつマイクロプロセッサ上に初期のForthコンパイラが構築されたこととも符号する。
\___________________________/

 最初で解説しているマシンは WISCというメーカーの CPU/16 である。IBM PC互換機のスロットに差し込むボードとしてリリースされたようだ。ディスクリート部品で作れらている。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 このプロセッサはコンピュータ設計コース用の指導教材として役立っている。

 ~略~

 CPU/16 は、従来の(人によっては、時代遅れの、というかもしれないが)74LS00 シリーズチップと、スタックとプログラムメモリ用に比較的低速な150nsスタティックRAMを用いて構成される。
\___________________________/

と書かれている。74LS00シリーズのくだりは少し驚いた(ALUとして74LS181)。私は長いことハードの仕事から遠ざかっているので、この 74LSxx 時代が知っている最後で少し親近感が沸いた。

 一つ飛ばした次に解説されているのが以下である。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 Novix 社の NC4016 は、かつては NC4000 と呼ばれており、Forthプログラム言語の基本命令を実行するために設計された、16ビットのスタックベースのマイクロプロセッサである。これは最初に作成されたシングルチップのForthコンピュータであり、以降続く設計に見られる機能の多くが初めて使用された。対象となるアプリケーションは、リアルタイム制御と、汎用プログラミングのためのForth言語の高速実行である。
\___________________________/

 4NC4016 は、4000ゲート以下、121ピンのPGAにパッケージされているとのこと。
 タイマ/カウンタもオンチップで用意されているようだ。
 たぶんこれはインテルの8086のように、スタックマシンのその後の基礎となるようなプロセッサなのだろう。

 次に解説されている RTX 2000 は、前記 NC4016 の派生らしい。

/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄\
 Harris Semiconductor 社の RTX 2000 は、Novix NC4016 から派生した、16ビットスタックプロセッサである。RTX 2000 はたいへん高度に集積されており、核となるプロセッサだけでなく、スタックメモリ、ハードウェア乗算器、カウンタ/タイマを単一チップ上に含んでいる。

 ~略~

 RTX 2000 はハイエンドな16ビットマイクロコントローラ市場がターゲットである。RTX 2000 はセミカスタム技術で構成されるので、プロセッサの特殊バージョンを特定の設定アプリケーションのために作成することができる。
\___________________________/

 RTX 2000 は、84ピンのPGAにパッケージされているとのこと。

 (このあと第5章で32bitのスタックマシンについても言及があるが省略する)

 さて上記のようなスタックマシン群で、ニーモニックに注目したい。
 当然のことだが、Forthを走らせることを意識したスタックコンピュータのニーモニック(アセンブリ言語?)はForth言語とだいたい重なる。さらに本書で紹介されている新世代のスタックマシンのニーモニックはForthのワード名そのものである。(多くのスタックマシンではさらに拡張した命令を装備しているようだ)

 たとえば整数値を二倍にする処理を * を使わずに書くとして、アセンブリ言語として

 DUP
 +

と書いたものと、Forth言語として

 DUP  +

と書いたものは効果として同じである。縦に書けばアセンブリ言語で横に書けば高水準言語となるわけで、大変わったマシンということになる。

 ただ、上記は私があえて言ってみただけで、本書で紹介されているスタックマシンについて、そもそもアセンブリ言語なるものは存在しないかも知れない(意味が無い?)。残念ながら私はこれらのマシンに触ったことがないので本当のことは知らない。

 最後に、本家のFORTH社(FORTH, Inc.)のホームページについて(URLは鎌倉さんに教わった)。

 https://www.forth.com/

 トップ画面に電波望遠鏡らしき写真があり同社が制御分野を主力にしていることが分る。同社のForthは以前は PolyFORTH だったが今は SwiftForth になったようだ。(アップル社の Swift とは関係がない旨コメントがある)

2018年4月20日 (金)

スタックコンピュータとFORTH(1)

Dsc_0006_trim_2

MindはFORTHというプログラム言語から派生して開発された。Mindのルーツをたどる意味でFORTHと逆ポーランド記法についてまとめてみた。

またCPUとしてのFORTHについて良い書籍があるので紹介したい。写真は「スタックコンピュータ」、Philip J. Koopman, Jr.著、田中清臣監訳/藤井敬雄訳、共立出版である。1994年発行だがアマゾンでまだ売られているようだ。


最初に逆ポーランド記法について。通常のプログラム言語ではたとえば、

  2 + 3

のように記述するが、FORTH言語では、

  2 3 +

のように書く。語順が特殊で逆ポーランド記法と呼ばれている。最初に演算対象となる値を書き、最後に演算名を書くことになっている。

私は昔、FORTHの講習会をやっていたころ、「これは日本語として・・2に3を加える‥と読めば分かりやすいです」と教えていた。(のちに、本当にそのように日本語で書くMindを開発することになったのだが)

話を戻すと、通常のプログラム言語での

  2 + 3

につては、「+」の両側に必ず数値を書かなければならない。つまり、

  + 2 3

  2 3 +

のように書くと文法エラーになってしまう。逆にいえばそのような文法が規定されていてプログラマはそれから免脱しないよう書かなければならない。

一方、FORTHでの書き方である、

  2 3 +

は、なぜこのような書き方になるかというと、FORTHがスタック指向の言語だからである。どのようなプログラム言語もスタックは内部的には使っているのだが、FORTHのそれは少し意味合いが違って、プログラマがスタックを明に意識して使うことが特徴である。

上記 「2 3 +」 は、

  2をスタックに積み、 3をスタックに積み、 +を実行する

という意味になる。「+」というワードはスタックから2つの値を取り出し、加算を行い、結果の5を再びスタックに積む。FORTH言語の設計者が文法としてこの書き順を発明したというよりは、スタック指向のプログラムというのは、その動作原理上、必然的にこのような書き方になったのだろう。

次のFORTHプログラムはどうだろうか。

  3 +

先と同様に、上記は

  3をスタックに積み、 +を実行する

という意味合いになる。演算対象となる片方の数値が書かれていないが、間違いとは言えない。この処理が開始する以前に既にスタックに何らかの値が積まれていれば、「それに3を加える」ことになるからまったく普通の計算である。

逆ポーランド記法だからといって 「2 3 +」 のスタイル‥つまり、「+の直前に2つの数値を書く」といった「表記上の形」にこだわると本質が見えにくくなる。

極端なケースを書けば、FORTHのプログラムで

  2 + 3

  + 2 3

あるいは

  + + -

と書いても(正しい演算がおこなわれるかどうかは別として)コンパイラは何も言わない。FORTHのプログラムとして間違いではないからである。乱暴なことを言えば、まるでFORTHには文法が無いように見えるくらいである。

通常のプログラム言語の「+」とFORTHの「+」は、同じ記号を使っているが、だいぶ意味が違う。通常の言語ではコンパイラは「+」記号の左右に書かれた値とワンセットで解釈し、しかるべき計算をする。つまり間接的にプログラマからコンパイラに意図を伝えることになる。一方、FORTHプログラム中に現れる「+」は、単に「この時点で+を実行する」というもので、よりダイレクトで独立したものである。現実のFORTHプログラムでは「+」の直前に値が書かれることは多いのだが、コンパイラはそんな前後関係はまるで関知しておらず、単に「+」が書かれていたら「+」を実行する(ようなコードを生成する)だけである。

FORTHでは「+」に必要な2つの引数についてコンパイラは興味は無い。「+」が呼び出されたときスタックに引数が積まれているはず‥と期待して動作することになる。つまり、「+」の引数はソースコードに記述されているものではなく、実行時にスタックに積まれているものが引数である。

FORTHのソースコードとして書かれたワードの羅列が、その書かれた通りに1つつ順に実行されることは1つ大きな利点がある。制御系システムのように、細かな処理のタイミングや順序が非常に重要となる分野で有利なことである。私がFORTHにかかわっていた時期の経験でも、ユーザのほとんどが計測・制御分野であった(もちろん、そのほかにROM化が容易だったとか、言語の習得が容易だったなどの理由もある)。

さらに言えば、ソースコードとして書いたものが1つ1つ順番にCPUで実行される‥という点で、FORTHはアセンブリ言語にも似ている。いままで何回も示してきたプログラム、

  2 3 +

は、アセンブラで書いたとすれば以下に相当するだろう。

    PUSH  2

    PUSH  3

    CALL  +

このようにFORTHは高水準言語と低水準言語の両方の側面を持つ特異なプログラム言語と言える。

ここまでを前置きとして、冒頭の書籍「スタックコンピュータ」の紹介をしたい。

2013年9月26日 (木)

プリファレンスアクティビティ

Preferenceact3gattai_2
 先の「プリファレンス」に関連するものとして「プリファレンスアクティビティ」も実装した。
 Androidにはユーザと対話してプリファレンスのデータを編集し結果を記入してくれる高機能なアクティビティがあり、それを使うためのAPIが用意されている。プログラマとしては設定ファイルを用意せずに済み、ユーザインターフェースのコードも大幅に削減されるのでおおいに助かる。ただ高機能な分、Mindに実装するには手間がかかった。

 まず呼び出し元から。
 呼び出し先である本機能はダイアログ的に見えるがダイアログではなく独立したアクティビティなので本機能を呼び出すには一般的なアクティビティ起動と同じ手順を踏む。

   (Mindソース例)
    私のパッケージ名と "PreferenceActSub"で インテントを実行し _結果に 入れ

 次に本機能を実行するアクティビティについて。
 通常のアクティビティとは異なり、プリファレンスのGUIパーツの描画のみを記述する。
 Androidの標準的な開発手法では、設定画面のフォーマットをXMLファイルで記入するらしい。このあたりはレイアウト系の実装と同じ事情になるが、Mindでそのようなファイルを扱うことは避けたかったのであくまでJavaのコードで設定画面を作る方式をベースとし、そのJavaのコードをMindから駆動するようにした。
 書籍やWeb上の情報にはJavaコードでデザインをおこなう例が少なく困ったが、コードでの駆動方法を把握するのに以下のWebページは大変参考になった。

    「Preference(3)-Preference画面に関するTips - 愚鈍人」
    http://ichitcltk.hustle.ne.jp/gudon/modules/pico_rd/index.php?content_id=96#use_code

 上記Webページにおいて、スクリーンの設定は以下のようになっている。

    public class MyPreferenceActivity extends PreferenceActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
                        ~略~

        PreferenceScreen pScreen =
             getPreferenceManager().createPreferenceScreen(this);
        pScreen.setTitle("PreferenceScreenのタイトル");
        setPreferenceScreen(pScreen);

 これをMindで書く場合は以下のようになる。

    メインとは
                _スクリーンは 変数

        プリファレンススクリーンを生成し   _スクリーンに 入れ
        "PreferenceScreenのタイトル"を  _スクリーンに プリファレンスにタイトルを設定し
        _スクリーンで プリファレンススクリーンを有効化し

 同様に上記Webページにおいて、チェックボックス・プリファレンスを表示するJavaのコードは以下のようになっている。(依存関係の指定は省略)

        CheckBoxPreference pCheckBox = new CheckBoxPreference(this);
        pCheckBox.setKey("checkKey");
        pCheckBox.setTitle("CheckBoxPreference");
        pCheckBox.setSummary("チェックボックスの例");
        pCheckBox.setDefaultValue(true);
        pCategory1.addPreference(pCheckBox);

 Mindで書く場合は以下のようになる。(以下ではカテゴリではなくスクリーンに追加)

                _チェックボックスは 変数

        チェックボックスプリファレンスを生成し _チェックボックスに 入れ
        "checkKey"を              _チェックボックスに プリファレンスにキーを設定し
        "CheckBoxPreference"を _チェックボックスに プリファレンスにタイトルを設定し
        "チェックボックスの例"を   _チェックボックスに プリファレンスにサマリを設定し
        真を                        _チェックボックスに プリファレンスにデフォルト値を設定し
        _チェックボックスを _スクリーンに プリファレンスを追加し

 上記のようにプリファレンスアクティビティ系のMindの処理単語はおおむねJavaのメソッドと一対一になった。例外はリストプリファレンスで、項目名と項目値はJavaでは別々に与えているがMindでは一度に与える仕様とした。

 前記事に書いた通り、(単なる)「プリファレンス」の実装ではJava側インスタンスの管理はあえて行わなかったが、こちらでは行なっている。スクリーン、カテゴリをはじめ、チェックボックスなどGUIパーツすべてについてJavaで生成したオブジェクトは大域配列に格納し、その要素番号でJavaとMindを結びつけるようにした。

 現段階で実装したMindの処理単語は以下の通りである。(数が多いので入出力パラメータは記載せず単語名だけとした)

        プリファレンスファイルを指定

        プリファレンススクリーンを生成
        プリファレンスカテゴリを生成
        チェックボックスプリファレンスを生成
        エディットテキストプリファレンスを生成
        リストプリファレンスを生成
        リングトーンプリファレンスを生成

        プリファレンススクリーンを有効化

        プリファレンスにキーを設定
        プリファレンスにタイトルを設定
        プリファレンスにサマリを設定
        プリファレンスにデフォルト値を設定
        プリファレンスにダイアログタイトルを設定
        プリファレンスにダイアログメッセージを設定
        プリファレンスにリスト項目を設定

        プリファレンスを追加


 プリファレンスが変更された時のイベント処理は現時点では未実装である。




参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年9月25日 (水)

プリファレンス

Preference アプリ固有の設定情報を不揮発性で記憶するのに、簡単なものだったら「プリファレンス」という(正確には「共有プリファレンス」らしい)機構が使える。Windowsだったら自前で*.iniファイルあるいはレジストリに記録するところだが、この点Androidは便利だと思った。
 さらには、ダイアログを開いてユーザと対話してプリファレンスのデータを編集し結果を記入してくれるAPIも用意されていて驚いた(そちらは次の記事に)。

 プリファレンス(Javaクラス名は「SharedPreferences」)が最終的に記入される先はファイルのようだ。最初に(Javaレベルでは)インスタンスを生成する必要があり、このときファイル名とアクセスモードを指定する。このあたりはMindでの手順も同様とした。

 今までの記事でも書いたことだが、Javaのクラスインスタンス(オブジェクト)をMindで得てもあまり意味がない。レイアウトなどでは生成したオブジェクトを大域配列に記憶し、Java←→Mindはその配列要素番号で連携をとったたが、今回の場合、複数のプリファレンスを同時に処理する場面は想定しずらいと考え、オブジェクトは配列にはせずJava側で単一の大域変数に格納、それを暗黙の了解で扱うようにしてJava←→Mindのインターフェースを単純化した。

プリファレンスの生成

 Mindでの記述は以下の通りである。

    (文法)
        <ファイル名>と <パーミッション>で プリファレンスを獲得
                                                         → [ErrorMessage]、真偽


    (ソース例)
        "testdb"と MODE_PRIVATEで プリファレンスを獲得し 偽?
                ならば 重大エラー
                つぎに


 上記にある「パーミッション」は、同じアクティビティ内であるいは同一パッケージ内アクティビティ同士で共用すれば済む場合は定数「MODE_PRIVATE」を指定する。(定数を日本語名にするか考えたがとりあえずJavaと同名の英語にした)
 なお、プリファレンスの生成のあと書き込みをおこなうのか、読み出しをおこなうのかはこの段階では区別する必要はない。(このあと書き込み処理をおこなえば書き込みとなる)

エディタの割り当て(書き込み時)

 書き込みを行うには、一連の書き込みの前に一度だけ、プリファレンスに対して「エディタ」(Javaでは SharedPreferences.Editor クラス)を割り当てる必要がある。プリファレンス本体のオブジェクト維持と同様にエディタのクラスインスタンスもJava側大域変数に格納するためMindからエディタを特定する情報は無い。

    (文法)
        プリファレンスのエディタを割り当て → [ErrorMessage]、真偽

    (ソース例)
        プリファレンスのエディタを割り当て 偽?
                ならば 重大エラー
                つぎに


データの書き込み

 プリファレンスに記入するデータは必ず<キー>と<値>の対である。キーは常に文字列だが、値は論理値/整数/文字列のように種類がある。Mind側でも値のデータ型に応じて複数の処理単語を設置した。またJavaでは書き込み時にエディタを指定するのだが前述の通り暗黙のオブジェクトとしたためMindでは指定しない。

    (文法)
        <キー>と <値(1/0)>を プリファレンスに論理値を書き込み
                                                         → [ErrorMessage]、真偽
        <キー>と <値(整数)>を プリファレンスに整数を書き込み
                                                         → [ErrorMessage]、真偽
        <キー>と <値(文字列)>を プリファレンスに文字列を書き込み
                                                         → [ErrorMessage]、真偽

        <キー>と <デフォルト値(1/0)>で プリファレンスの論理値を読み出し
                                                → <値(1/0)|ErrorMessage>、真偽
        <キー>と <デフォルト値(整数)>で プリファレンスの整数を読み出し
                                                → <値(整数)|ErrorMessage>、真偽
        <キー>と <デフォルト値(文字列)>で プリファレンスの文字列を読み出し
                                                → <値(文字列)|ErrorMessage>、真偽


    (ソース例(書き込み))
        "age"と 62を プリファレンスに整数を書き込み 偽?
                ならば 重大エラー
                つぎに


    (ソース例(読み出し))
        "name"と 空列で プリファレンスの文字列を読み出し 偽?
                ならば 重大エラー
                つぎに
        氏名に 入れ


 論理値の書き込みは、Mindで論理値は「ゼロ/非ゼロ」で判定される整数として扱っているがJavaでは論理値自身がデータ型なので両者間で型変換をおこなっている。
 Mindへの実装では当初は論理値の読み書きは含めず「~に整数を書き込み」「~の整数を読み出し」で代用すれば可と思っていた。しかし後述するプリファレンス・アクティビティにおいて、論理値を扱うAPIがあるため仕方なく設置したものである。
 読み出し操作で指定するデフォルト値は、指定したファイルが存在しないか、指定したキーが存在しない場合、ここで指定した値を読み出し値とみなして返すために使われる。

コミット

 これはデータベースの用語だが手続きもデータベース流になっている。一連の書き込みのあと、コミットをおこなうことで実際にファイルに書き込まれる。(割り当てたエディタはその後明に開放する)

    (ソース例)
        プリファレンスをコミットし 偽?
                ならば 重大エラー
                つぎに


プリファレンスとエディタの開放

 Java側にそのようなAPIは無いがあえてMindに実装した。以下を実行することでJava側の大域変数にnullを記入することでメモリ開放するためである。(後者はアプリ終了時にも自動実行される)

    (ソース例)
        プリファレンスのエディタを開放し 偽?
                ならば 重大エラー
                つぎに
        プリファレンスを開放し 偽?
                ならば 重大エラー
                つぎに



参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年8月24日 (土)

ピッカーダイアログによる日時の入力

Datepicker5gattaitrimed

 ダイアログを経由して日付や時刻を入力するクラスとして DatePicker, TimePicker というクラスがあり、これをMindから使えるようにした。Javaの側の使い方が単純なので、Mindへの実装も他の機能に比べて容易だった。
 Mindの文法は以下のようになる。

        <日時構造体>をつかい 日付入力ダイアログを開く → ・

        <日時構造体>をつかい <0/1>で 時刻入力ダイアログを開く → ・
                                       ↑
            1=24時間制 : 定数「十二時間制」「二十四時間制」が使える

 日時構造体はダイアログが開いたとき初期表示する日付または時刻を指示するのに使われる。
 Javaでは年、月、日などを個別値で渡しているのだが、Mindでは日時構造体を1つ渡すようにした。多くの場合、セットで使うと思われる「日時を得る」との整合を考えたものである。
 これに関連するが、このダイアログを開く前におこなうであろう現在の日時取得にJavaのCalendar機能は使う必要がない。もちろんCalendar版の処理単語「システム日時をカレンダーで得る」にて取得しても良いのだが、Mind標準の「日時を得る」のほうがJava呼び出しが無い分だけ軽く動作する。
 なお時刻の入力ではJava側仕様と同様に時間制をパラメータとして与える仕様としているが、これは積まずにたとえば「十二時間制で時刻入力ダイアログを開く」という別の処理単語を設置することでパラメータを無くしてしまうことも考えた。今後の検討課題とする。

 以下は利用例である。

        ボタンクリック処理は    本定義  (ボタン情報 → ・)
                        ~略~
                        _日時情報は  構造体 日時型
                ~略~
                _日時情報をつかい 日時を得て
                _日時情報をつかい 日付入力ダイアログを開く

 ダイアログが開くと処理単語「・・ダイアログを開く」は直ちにリターンするがこの時点ではまだ値は入力されていない。一方、ダイアログ内の「設定」ボタンを押すと、イベントが発生し、以下の処理単語がコールバックされるようになっている。このあたりはJava側の仕組みと同じである。

        日付設定時処理とは 本定義 (年、月、日 → ・)

        時刻設定時処理とは 本定義 (時、分 → ・)





参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年8月21日 (水)

アラームマネージャとCalendar

Alarmmgrtop_2

 Android APIにはアラームマネージャ(AlarmManager)というクラスがあり、指定した時刻や指定した時間間隔で他のアクティビティやサービスを起動することができる。
 アラームマネージャでは時刻を表すオブジェクトを扱うことになるが、それは別途、Calendarクラスの機能であるため、まず先にCalendarの機能を実装した。

 以下はJavaレベルでCalendarクラスの使い方の例である。

    1. Calendarクラスのインスタンスを生成
       →例: Calendar cal = Calendar.getInstance();

    2. カレンダー要素を取り出すメソッドを使って値を得る
       →例: int year = cal.get(Calendar.YEAR);

  Mindから利用する場合、1と2は一体処理するのが良い。1で得られたJavaのインスタンスをMindに返しても使いように困るからである。さらに、2において年だけ、月だけ・・をMind→Javaの呼び出し毎に返す仕様ではやはりCalendarインスタンスの維持の問題が生じる。

  注:レイアウト/ビュー/メディアプレイヤーなどではJavaのインスタンスを
        Mindもかかわって維持管理している。これらは駆動が複雑なので一体処理
    することができないという理由からである。

 これを解決するのに、前の1と2を一体処理しかつJava側で年月日時分秒をまとめて得てからC(Mind)側に返す方法があり、Mind→Javaの呼び出し回数も1度で済む。しかし「Java側で年月日時分秒をまとめたオブジェクト」とC(およびMind)での構造体との間でフォーマット変換が必要で、これがオーバーヘッドになるのが気になった。
 今回はもう1つの方法として、年月日時分秒をパックして整数値(Javaで言うlong)を1つ返し、Mindのライブラリ内で分解して日時構造体に格納することにした。整数はJava←→Cでダイレクト引渡しのためフォーマット変換が不要になる。それに比べたらJavaでのパックとMindでのアンパックの処理時間などわずかであろう。

 余談になるが、Javaも含め関数型言語はこのようなとき不便だ。MindやForthといったスタック型言語であれば単純に年月日時分秒を表す6個の整数を積んで返せば済むのだが、関数型言語では戻り値は1つなので、6個のデータを返すのに(オーバーヘッドの少ない)整数などプリミティブ要素を使って返すことができない。結果的に「戻したい値の数」が1個なのかあるいはそれより多いのかで文法が大きく変わってしまう。

 さて日時情報を得る手段は次のものが標準Mindに元から備わっていてAndroid環境でも動作する。

                      (Mind標準仕様のもの)

        <日時構造体>をつかい 日時を得る → ・

          (上の余談と矛盾するがMind仕様で日時情報は構造体に格納し返す。
                もちろん6個のデータをスタック積みで返す方法も考えられる)

 上を実行すると指定した構造体の各要素に年月日が格納される。
 Android API(Calendarクラス)を使う場合も最終目的は同じなので、Mindでの仕様は標準仕様と似させて以下のようにした。。

                      (Android仕様のもの)

        <日時構造体>をつかい システム日時をカレンダーで得る → ・


 上記両者共通に使う日時構造体(型紙)は標準Mindに元からあるものを少し訂正し以下になった。

        日時型は   型紙
                        年は   変数
                        月は   変数
                        日は   変数
                        曜日は  変数
                日付情報は 年と 月と 日と 曜日
                        時は   変数
                        分は   変数
                        秒は   変数          ←この次にミリ秒を含めるか要検討
                時刻情報は 時と 分と 秒
          全体は 日付情報と 時刻情報。

 以上のように、Android API(Calendarクラス)を使った日時の参照と、LinuxのAPIによる日時の参照は機能として類似のものとなる。細かなことを言えば、前者はミリ秒の情報が得られるなど違いは有るのだが、おおむね似た機能と言える。
 今回の実装でこの二つの似た機能をどのように扱うか考えたが結局、両手段をともに設置することにした。当然だが Linux API版であってもAndroid API版であっても同じ値が得られる。

 日時を年月日時分秒の要素で得る方法ではなく、単一値として得る手段もある。こちらも標準Mindで(つまり Linux API)元から有り、以下のようなものでもちろんAndroid環境でも動作する。

        日時を値で得る → <秒>
        日時とミリ秒を値で得る → <秒>、<ミリ秒>      ←これは最近の拡張

 AndroidネイティブAPIを使う機能は以下の文法とした。

        システム日時を値で得る → <通算ミリ秒>

 上記両者は目的は同じようなものだが計算手段が異なるので、返されるミリ秒の値については互換性は無い。
 なお年月日時分秒を個別に取得するほうの「システム日時をカレンダーで得る」であるが、Android APIで本来得られるミリ秒値をMind側の都合で無視してしまった。秒未満の値については上記で代替できるのではないかと思っているのだが今後の開発で変更する可能性もある。

 Calendarクラスにはこの他、日時要素への加算・減算や全体として整合をとる機能などがある。たとえば「日」3だけ増加させたとき31日を超えてしまうことがあるが、次に新しい要素値を参照したとき自動的に正規化(ここでは「月」への繰上げ)してくれる。
 しかしこの機能もまた標準Mind(Linux API)にほぼ類似のものが有る。以下のものである。

        <日時構造体>を 日時を正規化 → <通算ミリ秒>

 上記により日時構造体に格納された要素が正しい値に補正されるので、手順は異なるもののAndroid APIとほぼ同じ機能と言える。これがあるため、Android APIが提供する要素の加算・減算(いずれも正規化を伴う)は今回はMind側に実装しなかった。当面様子を見ることにする。
 ちなみに、Android APIでは日時要素への加算・減算をおこなうメソッドが多数定義されているが、標準Mind(Linux API)では日時要素が構造体メンバーであり直接アクセス可能のため、加算・減算をおこなうための手段は必要無い。(正規化をおこなう処理単語のみが提供される)

        注:ここまでローカルタイムの話である。タイムゾーンは追って実装する必要がある。

 次にアラームマネージャについて。
 Mindレベルでは以下の処理単語を用意した。単語名はあちこちにあるAndroidの日本語解説で登場する呼称そのままなので、これが何なのかは理解しやすいと思う。

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                電源ONからの経過時間でアラーム開始・スリープ解除 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                時刻指定でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                時刻指定でアラーム開始・スリープ解除 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>、<間隔ミリ秒>
                正確な一定間隔でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>、<間隔ミリ秒>
                一定間隔でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>
                アラーム停止 → 真偽

  指定した時間になったらアクティビティやサービスを起動するのだが、これら外部プログラムの起動周りのことは記事「画面遷移(他のアクティビティの起動)」と同様の考えなのでそちらも参考にして欲しい。

 アラームマネージャの単語群のうちひとつの利用例は以下の通り。(一定間隔でインテントサービスを起動し、そちらで音を鳴らすというもの)

                _開始ミリ秒は 数値変数

        ~略~

        システム日時を値で得て
                 10000ミリを ミリ秒を加え _開始ミリ秒に 入れ

        私のパッケージ名と "MyIntentService"と 0をつみ (=UniqueID)
         _開始ミリ秒をつみ (=開始時刻)
          10000ミリをつみ (=間隔)
            一定間隔でアラーム開始し






参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年7月12日 (金)

インテントサービスで効果音を再生

 

PhotoPhoto_3
 サービスの一つである「インテントサービス」を実装した。
 インテントサービスは「サービス」という名前を冠しているため、サービスと同様に当初はWindowsのサービスのようなものだろうと想像していたのだが、実はそのようなものとはかなり異なると後になって分かった。
 要するにAndroidの「インテントサービス」は多少時間がかかる処理をバックグラウンドで実行することが目的らしい。そのため処理が終わると共にサービスは終了する(当初は「なぜサービスなのに勝手に終了するのか?」と疑問に思ったものだった)。つまり常駐することが目的なのではなく、時間のかかる処理をバックグラウンドで行うことが目的なのである。

 実は当面の開発テーマとして「薬の飲み忘れアプリ」を考えている(歳がバレるが‥)。アラームマネージャから定期的にプログラムを起動し、アラームを鳴らすべきタイミングの判定や音を鳴らす処理をインテントサービスにしようと思っている。そのため、インテントサービスの処理内容としては音を鳴らしてみることにした。

 まず元となるアクティビティを普通に作成する。アクティビティ側での起動処理は前回投稿の「画面遷移」での子のアクティビティの起動と良く似て、Mindで以下のように記述する。(サービスへのデータ渡しはまだ実装しておらず単純起動とした)

 <パッケージ名>と <クラス名>で インテントサービスを起動

 ちなみに子アクティビティの起動の時は以下であった。

 <パッケージ名>と <クラス名>で インテントを実行

後者では「・・を実行」だったものが前者では「・・を起動」となっており、呼び出し元は子の終わりを待たないことが想像できるようにした。

 処理単語「インテントサービスを起動」が実行されると Mind→C→Java へと制御が移行し最終的に以下のJavaコードが動作する。(これまた子アクティビティの起動と似たような記述となっている)

   intent = new Intent();
   intent.setClassName( pkgname, classname );
   this.startService(intent);

 1つのパッケージに主(アクティビティ)・副(インテントサービス)の二つを含めることになるのも画面遷移の時と類似であり、C記述の共有ライライブラリ(DLL)のAndroid環境下での構成は以下のようになる。

        (パッケージ化する前の元ファイル状態のうち主要なものだけを抜粋)

  assets
  |-- bin
  |   |-- euc-uni-encode.bin          ←Mindが使うUnicode変換テーブル
  |   `-- uni-euc-decode.bin          ←Mindが使うUnicode変換テーブル
  `-- mco
      |-- MyIntentService.mco         ←インテントサービスのMindオブジェクトコード
      `-- TestSoundService2.mco       ←メインアクティビティのMindオブジェクトコード

  bin
  |-- classes
  |   `-- jp
          `-- co
              `-- scriptslab
                  `-- TestSoundService2       ←パッケージ名
                      |-- MyIntentService.class   ←インテントサービスのJavaクラス
                      `-- TestSoundService2.class ←メインアクティビティのJavaクラス

  libs
  |-- armeabi
      |-- libdllker_MyIntentService.so    ←C記述DLL (Mindランタイムとディスパッチャ)
      `-- libdllker_TestSoundService2.so  ←C記述DLL (Mindランタイムとディスパッチャ)

  res
  |-- raw
  |   `-- se_maoudamashii_onepoint23.ogg    ←効果音を収めたファイル
                 ↑
                 著作:音楽素材/魔王魂 http://maoudamashii.jokersounds.com/

 JavaからC→Mindへと処理を移行する仕組み自体は前投稿の「画面遷移」と同じなのだが、作成したインテントサービスを実行してみたところ問題が発生した。2度目のサービス起動で落ちてしまうのである。
 原因は Java→C に渡される環境情報 env, thiz の2つの扱いだった。たとえば一番最初にCが呼び出される箇所は以下のようになっていた。

                 Mindカーネルの "dllstartup.c" (まずかったときの記述)

  static JNIEnv*      env_save;            ←単一の大域変数への退避はまずかった
  static jobject     thiz_save;
 
  jstring
  dllStartup_common( JNIEnv* env, jobject thiz, jstring pkgnamej,
                             jstring classnamej, jbyteArray mcobufferj )
  {
        ~略~
    env_save = env;
    thiz_save = thiz;
        ~略~

  JavaからCが呼び出されたとき、上記のように第1/第2引数で環境情報 env, thiz の2つが渡される。このあと延々とMindの処理に入るため、これらを一旦大域変数に格納し、Mind→Java の逆呼び出し時には、この大域変数を参照していたのだが、ボタン押下やサービス要求などのイベント発生時にこれを共用してはいけなかったのだ。
 JNIの本「JNI Java native Interface プログラミング(ロブ・ゴードン著、林秀幸訳、ピアゾン・エデュケーション)」によれば
     「JNIEnvポインタをstatic変数に入れてグローバルとして使用
      したくなるかもしれないが、そんな誘惑に負けてはいけない」
と書かれているのだが、まさにこれをやってしまっていた。
 対策として、この情報はJavaからC側に制御が移るごとにスタックに入れることで解決した(Cランタイムで隠蔽するのでMindからは見えない)。

 次にインテントサービス内でアラームを鳴らす方法だが、クラス SoundPool または MediaPlayer を使う方法がよく取り上げられている。効果音のような短いものは前者が使われるようだが、試しに MediaPlayer を使ってみた。
 MediaPlayerはそのコンストラクタで音源を指定する方法がよく書かれているが、Mindでの記述統一(たとえばレイアウトやビューと同じような割り当てを使いたかった)のため、コンストラクタでは音源は指定せず、MediaPlayer#setDataSource のメソッドで明に指定するようにしたが、この方法では音を出すのに都合3段階が必要となる。
 Mindでの記述は以下のようになる。

  インテントサービス処理とは 本定義 (・ → ・)
                  _音源パスは 文字列定数 「raw/se_maoudamashii_onepoint23」
                  _プレイヤー1は プレイヤー
          メディアプレイヤーを生成し 偽?
                  ならば <~略~>
                          終り
                  つぎに
          _プレイヤー1に 入れ
          _音源パスと _プレイヤー1で メディアプレイヤーにロードし 偽?
                  ならば <~略~>
                          終り
                  つぎに
          100と 0を _プレイヤー1で メディアプレイヤーを演奏し 偽?
                  ならば <~略~>
                          終り
                  つぎに。

 MediaPlayerにローカルファイル(たとえば raw/ 配下のファイル)で音源を指定する時のURIは

        android.resource://raw/se_maoudamashii_onepoint23

のようになるのだが、記述が煩雑であるためMindのライブラリ側で補完を行うことにより、アプリケーション側では短形式として

        raw/se_maoudamashii_onepoint23

で済むようにした。

      ( 注:試した音源は 著作:音楽素材/魔王魂 http://maoudamashii.jokersounds.com/ )





参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年6月19日 (水)

画面遷移(他のアクティビティの起動)

20130619_420130619x2_2

 メイン画面から別の画面に遷移するためにはAndroidで言うアクティビティの起動を使うことになる。
Mindで言うところの「プログラム実行」/「子プロセス実行」、Cなら fork()+exec() の感覚だろうと思っていたのだが、実際にやってみると想像とはかなり異なる部分があり、原理を理解したり実装するのに時間がかかった。

 まずアクティビティの起動はMindで以下のように記述する。

 <パッケージ名>と <クラス名>で インテントを実行

 上が実行されると Mind→C→Java へと制御が移行し最終的に以下のJavaコードが動作する。

  intent = new Intent();
  intent.setClassName( pkgname, classname );
  startActivity( intent );


 Javaでは上記のほかに、Intentクラスを生成するときのコンストラクタにてクラスを直接指定する方法もあるが、MindでJava側のクラスを把握するのは大変なので文字列として扱える上記方法を採用した。
 またMindでは、クラス名を長記述ではなく短形式でも指定できるようにした。本記事冒頭のサンプルでは以下のように起動している。(自身のパッケージ内のサブアクティビティを起動するにはこのような感じとなる)

                              ↓クラス名は短形式で可
  私のパッケージ名と "Subactivity"で インテントを実行し ・・


 このように、アクティビティの起動自体は大したことは無かったのだが、これに付随したことで大きな問題が出た。ポイントはC記述の共有ライライブラリ(DLL)のAndroid環境下での扱われ方である。
 当初実験したものは以下のような構成だった。

        (パッケージ化する前の元ファイル状態のうち主要なものだけを抜粋)

  assets
  |-- bin
  |   |-- euc-uni-encode.bin          ←Mindが使うUnicode変換テーブル
  |   `-- uni-euc-decode.bin          ←Mindが使うUnicode変換テーブル
  `-- mco
      |-- Subactivity.mco             ←サブアクティビティのMindオブジェクトコード
      `-- TestSubactMind2.mco         ←メインアクティビティのMindオブジェクトコード

  bin
  |-- classes
  |   `-- jp
          `-- co
              `-- scriptslab
                  `-- TestSubactMind2       ←パッケージ名
                      |-- Subactivity.class      ←サブアクティビティのJavaクラス
                      `-- TestSubactMind2.class  ←メインアクティビティのJavaクラス

  libs
  |-- armeabi
  |   `-- libdllker.so                ←C記述対応のDLL (この中からMind記述部を実行)


  C記述のDLLはMindにとってはランタイムライブラリの位置づけであり、アクティビティが違っても中身は同じだからという理由で、1つのDLL(libdllker.so)を2つのアクティビティで共用した。
 元々が共有ライブラリなのだからこれで良いだろうと思ったのだが、実際に走らせてみて分かったことは、たとえアクティビティ毎にそれぞれロード(System.loadLibrary())したとしても、DLLが割り当てられる空間は1組だけでありアクティビティ毎での割り当てではなかった。(恐らく二度目のロードは無視されたのではないか)
 以前に「複数のアクティビティは同一プロセスで走行する」というサイトの情報を見ていたずなのだが、つい アクティビティ=プロセス であるかのような錯覚をしていた。

 しかし中身が同じDLLを2つ生成することに抵抗があったため以下のような細工をしてみた。

  libs
  |-- armeabi      ↓2つのDLLの中身は同じ
      |-- libdllker_Subactivity.so             ←libdllker.soをリネーム・コピー
      `-- libdllker_TestSubactMind2.so         ←libdllker.soをリネーム・コピー


 Java→Cに入る部分の関数名は独特の名称になるのだが2つのアクティビティ向けに次のようなエントリを作成した。

  jstring
  Java_jp_co_scriptslab_TestSubactMind2_Subactivity_dllStartup( JNIEnv* env, jobject thiz, jstring pkgnamej, jstring classnamej, jbyteArray mcobufferj )
  {
          dllStartup_common( env, thiz, pkgnamej, classnamej, mcobufferj );
  }

  jstring
  Java_jp_co_scriptslab_TestSubactMind2_TestSubactMind2_dllStartup( JNIEnv* env, jobject thiz, jstring pkgnamej, jstring classnamej, jbyteArray mcobufferj )
  {
          dllStartup_common( env, thiz, pkgnamej, classnamej, mcobufferj );
  }


 本来であれば前者の関数は前者のDLLにだけ、後者の関数は後者のDLLにだけ存在すべきなのだが、それではやはりDLLを2つ作成することになってしまうため、あえて両方の関数を入れることで同一DLLで済むようにした。
 しかしこの方法はうまくなかった。たとえば Java_jp_~~_Subactivity_dllStartup() は libdllker_Subactivity.so の中にあるそれが呼び出されることを期待したのだが、そうはならず、他方のDLL内関数が呼ばれてしまうことが分かった。
 先にも書いたが、1つのパッケージに複数のアクティビティが存在する場合であってもプロセスは1つであり、その結果、DLLのロードも1系統になっている。たとえば2つのDLLに同じ関数エントリが存在する今回のようなケースでは「どちらのDLLに属するか」の判断はされず、とりあえずシンボル的に見つかった関数が呼ばれているのではないかと想像している。

 前置きが長くなったが結局以下のような方法をとった。

  libs
  |-- armeabi      ↓2つのDLLの中身は異なる
      |-- libdllker_Subactivity.so
      `-- libdllker_TestSubactMind2.so


 ほとんど同じ内容のDLLをアクティビティごとに複数用意することになるが、この煩雑さは自動処理で生成できるからあまり問題にはならないだろう。(そのようなツールを作るという面倒さはあったが)
 繰り返しになるがこれらのDLL群はJavaから呼ばれるエントリ関数の名前だけが異なり、そのほかは同一である。

 次の問題はアクティビティの終了とDLLとの関係である。
 教科書的にはJava内で onDestroy() が呼び出されたことをもってアクティビティの終了とみなすことができるはずなのだが、実はそのような状況でもDLLは開放されないことが分かった。(その先の同一アクティビティの再起動に備えているのかも知れない)
 当初は onDestroy()→C→Mind へと移行してMindのライブラリ終了処理をおこなっていたのだが、それでは次回のアクティビティの起動時にDLLロードのタイミングが入らないためそのあとのイベント発生などでMindの動きがおかしくなることが分かった。
 対策として、DLLロード済みの状態でアクティビティが起動した(JavaのonCreate()が呼ばれた)とき、DLLはロードせず、Mindの初期化単語も呼び出さず、直接「メイン」を実行する(初期画面の描画などをおこなう)仕組みを作ることで解決した。結果的に、Mindの終了処理は呼ばれないことになるが、特に問題は無い。(Mind+Cで獲得したメモリは本当のプロセス終了時に開放されるはず)




参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年6月 3日 (月)

イベントリスナー

20130603eventlistnersample_3

レイアウトの場合と似たことだが、Java側のオブジェクトをMind側に使えても意味が無いので簡単な仕組みで両者間で連絡が取れるようにする。

まずクリックリスナーの登録は以下のように行う。

[Mindのソースコード]
-----------------------------------------------------------
  メインとは
      ~略~
    ボタン1を クリックリスナーを登録し
-----------------------------------------------------------

レイアウトの時もそうだったが、オブジェクトのクラス(ボタンなど)別にそれぞれ登録処理を使い分けるのはMindのソースが煩雑になるため、Mindの処理単語は「クリックリスナーを登録」1つとし、Mind→C→Javaと遷移した後、Java内部で処理を分岐するようにした。以下のようになる。

[Javaでの登録処理] (Mind側から呼び出される)
-----------------------------------------------------------
  public int j_setOnClickListener( int id, int type, int seqno )
  {
        int  retcode;

    retcode = 1;
    switch ( type )
    {
        case typeTextView:
            break; (未実装)
        case typeEditText:
            break; (未実装)
        case typeButton:
            buttonsMind[seqno] = id;
            buttons[seqno].setOnClickListener( new onClickListenerForButton() );
            break;
        default:    /*該当無し*/
            retcode = 0;
            break;
    }
    return retcode;
  }
-----------------------------------------------------------

上記のうち buttonsMind[seqno] = id という処理だが、これはMindで管理しているボタン情報をJava側でも記憶しておきイベント発生時、Mind側に処理が移った際に押されたボタンの識別がMindでもできるようにするためである。(Mindの側にビューオブジェクトを渡されても困るため)

次にイベント発生時の処理だが、まずJavaに入って来るので以下のようにイベント受け取る。

[Javaのイベントリスナー] (Android OS から呼び出される)
-----------------------------------------------------------
          ↓注:これはボタン用。クラス毎に用意する
    private class onClickListenerForButton implements OnClickListener {
    @Override
    public void onClick( View v )
    {
            int    found;
            int    viewno;
        found = -1;
        for ( viewno=0; viewno<buttonsCount; viewno++ )
        {
            if ( v == buttons[viewno] )
            {
                found = viewno;
                break;
                
        }
        if ( found == -1 )
        {
            return;
        }
        /* ----------------------------------------- */
        /* -------- Mindカーネルを呼ぶここから ----- */
        String retcode = calltellButtonClick( ButtonsMind[found] );
        /* -------- Mindカーネルを呼ぶここまで ----- */
        /* ----------------------------------------- */
    }
  }
-----------------------------------------------------------

上記ではMindに通知する情報として buttonsMind[] という配列を参照しているが、前記したようにMind側でボタンを識別できるようにするためである。
一方、Mindでは次のようなソースコードでイベント処理を記述する。

[Mindのイベント処理のソースコード]
-----------------------------------------------------------
色の状態は 2個の 変数。

ボタンクリック処理は    本定義  (ボタン情報 → ・)
        _ボタンは ビュー
    _番号は  変数
    _ボタンに 入れ
    _ボタンの IDからビュー番号を得て _番号に 入れ
    色の状態(_番号)を 一つ増加し
    色の状態(_番号)と 1を ANDし 真?
        ならば 赤色を
        さもなければ
            黒色を
        つぎに
    _ボタンに 文字の色を設定する
    。
-----------------------------------------------------------

上記のようにMindでは「本定義」を使ってイベント処理を実現する。(ランタイム内部で既に仮定義してある)。ほかにも、恐らく TextView については「テキストクリック処理」というような名称にするのだと思う。




参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年5月16日 (木)

レイアウト系

レイアウトやビューなど表示のごく基本のところをまとめておく。

Javaのオブジェクト指向コーディングをMindのコーディングにどうやって対応させるかの問題にいきなり直面した(^^;。
まずレイアウト系のオブジェクトの扱いだが、たとえばJavaでは、

    layout1 = new LinearLayout( this );

などと書いて「layout1」という名前のリニアレイアウトのインスタンスを作るが、このオブジェクト(Java内部ではたぶんポインタ)をMindへ渡されても何も使い道が無い。さらには、Javaレベルの「layout1」なる名前もまたMind側からは見えない/見たくないものである。そもそもMindでプログラムを書くのだから名前はすべてMind側で(たとえば適当な日本語名で)決めるべきものである。
結果として以下のようにした。

  ------------------------------------------------------------------------
  (Mind)
          _レイアウトは   レイアウト
    ~略~
    リニアレイアウトを生成し _レイアウトに 入れ

                                          (注:変数名の頭にアンダースコアを付ける
                                               のはMindのルールではなく単に筆者の
                                               好みである。局所変数と大域変数とで
                                               区別がつきやすいようにしている)
                          ↓
  (Java)
          private  LinearLayout[] linearLayouts = new LinearLayout[20];
    ~略~
    linearLayouts[seqno] = new LinearLayout( this );
  ------------------------------------------------------------------------

 Mindの「リニアレイアウトを生成」でリニアレイアウトのインスタンスを作ることにする。実際には単純に1から始まる整数(通し番号)を生成するだけであり、これで個々のリニアレイアウトを識別する。
 JavaではMindから渡された通し番号(上記で"seqno")を添え字としてリニアレイアウトのインスタンスを配列として管理する。先に書いたようにJava側で個別インスタンスに名前を付けても使い道が無いが配列要素は無名なのでちょうど良い。

 ビューについても同様で以下のようにした。
  ------------------------------------------------------------------------
  (Mind)
         _案内文は       ビュー
    ~略~
    テキストビューを生成し _案内文に 入れ

                          ↓
  (Java)
          private TextView[] textViews = new TextView[20];
    ~略~
    textViews[seqno] = new TextView( this );
  ------------------------------------------------------------------------

 Mind側ではレイアウトの時と同様、「テキストビューを生成」によって通し番号を生成しその番号で管理する。またテキストビューのほかにボタンやチェックボックスなど他のビュータイプも一括して操作できるよう、それらを表すタイプ情報も含めて管理している。(実は先のレイアウトもMindでは複数のレイアウトタイプを番号で識別している)

 レイアウトとテキストビューをそれぞれ1つ扱うMindのプログラムは次のようになる。
    ----------------------------------------------------------------------------
    メインとは
                    _レイアウトは  レイアウト
                    _案内文は      ビュー
      (レイアウト)
            リニアレイアウトを生成し  _レイアウトに  入れ
            垂直を                    _レイアウトに  子の追加方向を設定し
            _レイアウトを  レイアウトをアクティビティに追加し
      (ビュー)
            テキストビューを生成し  _案内文に  入れ
            18を                  _案内文に  文字のサイズを設定し
            「こんにちは」を        _案内文に  文字を設定し
            _案内文を  _レイアウトに  ビューを追加し
            。
    ----------------------------------------------------------------------------

  上のソースコード末尾の「ビューを追加」について補足する。
 引数は <元ビュー> と <先レイアウト> だが、Mind側での型管理が無いとすると、たとえば
    ----------------------------------------------------------------------------------
    _テキスト1を  _リニアレイアウト1に    テキストビューをリニアレイアウトに追加し
    _テキスト1を  _フレームレイアウト1に  テキストビューをフレームレイアウトに追加し
    ・・・
    _ボタン1を    _リニアレイアウト1に    ボタンビューをリニアレイアウトに追加し
  _ボタン1を  _フレームレイアウト1に  ボタンビューをフレームレイアウトに追加し
    ・・・
    ----------------------------------------------------------------------------------

のように、全組み合わせの処理単語(関数)を設置する必要が出て、これはいかにも苦しい。
Mind側に最低限の型管理を導入することできれいなコードになるようにした(1つの「ビューを追加」で済む)



参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

«AndroidでMindをどうやって動かすか(2)