以前に FOMA M1000 のことを投稿したことがありますが、なぜ FOMA M1000 が不安定なのか、思っていることを書くことにしました。
実際に Symbian OS でネイティブアプリをプログラミングしてみると、バグを作りやすいところがあるんです。
Symbian OS でのプログラミングには特徴があります。I/O の API が非同期です。
多くの方が想像する API 呼び出しは、関数を呼び出すと処理結果が戻り値で返ってくるものでしょう。
/* 自分のコードで引数を生成します */ arg1 = myCode1(); arg2 = myCode2(); /* API を呼び出します */ err = osApi(arg, &arg2); /* API の処理が終了したので、 自分のコードでエラーチェックを行って、後処理をします。 */ if (SUCCESS == err) { myCode3(&arg2); }
I/O の API が非同期になる場合、API の関数はリクエスト受付だけを行い処理を行わず即座に終了します。I/O が終了すると OS がアプリの関数を呼び出します。アプリは呼び出された関数の中で後処理をします。関数が呼び出される場合、その関数のことを一般に「コールバック関数」と呼びます。
ここではコールバック関数を用いる環境での擬似コードを紹介します。
/* OS から呼び出してもらうコールバック関数を用意します */ void myCallback(int err) { if (SUCCESS == err) { /* 下記の arg2 はインスタンス変数等に格納しておきます */ myCode3(&arg2); } } /* ここからが、上のコードでの先頭にあたります */ arg1 = myCode1(); arg2 = myCode2(); /* * API を呼び出します * ここでの戻り値は「リクエストを受け付けた」という意味です * I/O の時間のかかる処理は後から行われます * 処理が終わった後に OS が呼び出すコールバック関数も登録します */ err = osApi(arg, &arg2, myCallback); /* * API 関数は即座に終了するので、 * I/O がまだ終わっていない段階で次の行のコードが呼び出されます * そのことを念頭に置いて API 呼び出しの後のコードを作ります */ myCode4();
「何か複雑だなあ」と思われたなら、話の要点はつかんでいらっしゃいます。
「Symbian OS ではコールバック関数は使わない、アクティブオブジェクトを使う」とおっしゃるなら、その通りです。Symbian OS では、コールバックは関数を登録するではなく、C++ オブジェクトを登録してインスタンスメソッドを呼び出してもらいます。この C++ オブジェクトを「アクティブオブジェクト」と呼びます。
アクティブオブジェクトを使った擬似コードは紹介しません。C++ クラスを定義しなければならないので、面倒なんです。
アクティブオブジェクトには利点がいくつかあります。アプリ内でスレッドを使わなくても OS が別スレッドを持っていれば並行処理が可能になるので、システム全体のスレッドの数を減らせます。I/O が終わったタイミングで OS がメソッドを呼び出すので、アプリはポーリングなどの待ちをしなくてもよくなります。関数ではなくオブジェクトを作成すると、キャンセルなどのエラー処理をカプセル化できます。
アクティブオブジェクトの総合的な解説は次の記事を参照願います。
ただ、実際にアクティブオブジェクトを作成してプログラミングしてみると、バグを作りやすいです。
オブジェクトの状態遷移機械の設計を間違えると、後からコールバックが呼び出されたときに適切に反応できなくて、意図的ではないにせよ OS からの通知を無視してしまうこともあります。すると実質的には API が戻ってこなかったのと同じになるんです。
API からの戻りをアプリが無視してしまうバグが発生しやすく、しかもバグが存在するのがアプリ側である、それが FOMA M1000 が不安定な理由です。アプリ側のバグなので、ドコモ/Motorola/Symbian が解決できません。それもネイティブアプリ・サードパーティアプリの各々が不安を抱えているわけです。これでは安定しません。
あと、アクティブオブジェクトでのプログラミングはドキュメントでの説明が難しいことも問題です。SonyEricsson が提供している Bluetooth プログラミング解説のドキュメントの中に、次のようなコードがあります。
// Use RequestStatus to indicate asynchronous completion of the service registration. TRequestStatus requestStatus; // Register the service with the Security Manager to become “visible”. securityManagerSubSession.RegisterService(btServiceSecurity, requestStatus); // Wait for the asynchronous request to complete. User::WaitForRequest(requestStatus); // Close the security settings subsession securityManagerSubSession.Close(); // Close the security manager session btSecurityManager.Close();
このコードは実際に走らせると確実にフリーズします。1行目で TRequestStatus オブジェクトをローカル変数に確保してから2行目で API を呼び出して3行目で戻りを待っていますが、このパターンでプログラミングするとフリーズします。これはコラム "Common mis-use and abuse of Symbian OS" の1項目目にあります。
どうして動かないサンプルコードが載っているのかを想像すると、おそらくは動くコードではサンプルにならなかったのでしょう。C++ クラスを定義しなければならないのでコード量が多くなりますし、実行がシーケンシャルでないので状態遷移を把握しないと動作を把握できません。
とはいえ、動かないコードを読んで勘所を理解した後で動くコードに置き換えるというのはプログラマにとってはつらい作業です。私は上のコードで実際につまずいて、アクティブオブジェクトがなんたるかを知りました。なので、恨みの発散も兼ねて、この文書を書いています。
なお、この話は Symbian OS のプログラミングモデルの話なので、Nokia の S60 端末でも同様のはずです。