あなたの天然記念物
ホーム更新雑談Perl鉄ゲタランドナーコースガイド自転車Linuxリンク経歴連絡先

メインのソースに手を入れず処理を追加する方法 (2012.09.19)

よりよい方法を発見

2012.09.19 冒頭からすみません。もっと良い方法が見つかりましたので名前参照によらない関数呼び出しをご覧ください。

こんな事をする背景

プロジェクトに処理を追加していく際に当然ながら、メインの処理から呼び出すコーディングを入れますよね。 実は、単に1回呼べばいい訳じゃないので面白くありません。 呼び出しは3回必要です。なぜか?そりゃ私が決めたから。
  1. クロック設定の前
  2. タスクスケジュール開始前
  3. スケジュール開始直後
メイン処理から呼び出すコーディングをする人が、 機能処理のどの関数をどのタイミングで呼べば良いかなんて知る訳がないので、 呼び出しコーディングをしなくて良い方法を考えました。 当然の事ながら、機能処理をコーディングする人は、どのタイミングでどの関数を呼んでほしいか、 あるいは、どのタイミングでどんな処理をすると良いか把握しています。 なんでこんな事を考えたかというと、この先プログラム(機能)が大きくなっていった時に、機能単位のプロジェクトで動作させて上手く行ってから、 1個の大きなプロジェクトに追加していこうと考えています。 そのプロジェクト間の移動の際に、プロジェクトにソースを追加するだけで機能が追加されると 呼び出し処理の修正のミスや手間がなくて格好いいじゃないかと考えました。 ※LPCXpresso 1768(1769)はROMサイズが512KBもあるので、将来作成するプログラムを全部入れても空きができると思っています(笑)。 次の動画はデータを贅沢にもROMの512KBに入れた例です。普通は外部メディアに入れるそうですよ。

weakとalias

さて、普通に「将来呼ぶ予定の関数」をメイン処理で呼び出すコーディングをしてしまうと、リンク時に「そんな関数ないよ」と言われて失敗します。 形態はどうであれ、何かしらの呼び出すコーディングが必要です。 この一見矛盾する条件をクリアする良い方法を見つけました。 weakとaliasって属性をコーディングで使うとリンク時に面白いことが起きます。
  1. 本来のラベルが見つかると、そのラベルで解決して、aliasのラベルを破棄する。
  2. 本来のラベルがない場合、aliasで解決する。
スケルトンのプロジェクトを生成すると、ソースに割り込みテーブルと割り込み処理関数が入っていて、テーブルには所定のラベルを参照するようになっています。 ところが、そのラベルの関数はプロジェクトになくて、aliasで中身が無限ループの関数名(ラベル)を指定してあります。 割り込み処理関数を定義していないのでラベルがなく、リンカはalisaの無限ループの奴で解決しちゃいます。 将来、ユーザーが割り込み処理関数を定義するだけで、リンカが自動的にそのラベルで解決する仕組みです。 私もこの仕組みを利用しました。ただし、関数のラベルではなく配列(アドレス)のラベルとしてです。
#define INIT_WEAK(empty) extern __attribute__ ((weak, alias (#empty))) TAGITEM_TYPE

INIT_WEAK(init_empty) init_01[1];
INIT_WEAK(init_empty) init_02[1];
INIT_WEAK(init_empty) init_03[1];
INIT_WEAK(init_empty) init_04[1];
INIT_WEAK(init_empty) init_05[1];
INIT_WEAK(init_empty) init_06[1];
INIT_WEAK(init_empty) init_07[1];
INIT_WEAK(init_empty) init_08[1];
INIT_WEAK(init_empty) init_09[1];
INIT_WEAK(init_empty) init_10[1];

TAGITEM_TYPE init_empty[] =
{
    {TAG_END, TAG_NONE}
};
はい、これでinit_01~init_10の10個の配列は全部init_emptyで解決されますから、無事リンクできます。 そして、このプロジェクトにinit_03という配列を含むソースを追加してリンクすると、init_emptyの代わりに本物のinit_03で解決しますから、 追加したinit_03にメイン処理がアクセス出来る訳です。もちろん初めに説明した通り、メイン処理に変更は不要です。

関数の代わりに配列を定義しておくと

プロジェクトのスケルトンに習って関数を解決すると済む話をわざわざ配列にした事情があります。 冒頭で説明しましたように呼び出し機会は1個の機能に3回あります。 機能を10個(そんなに入れるのか!)用意すると呼び出し回数=3回/機能×10機能=30回となってラベルが30個必要なのと、 ループ処理で10機能分の呼び出し処理を済ませる都合があります。 もっと大きな理由が、呼び出し機会を追加した際に、例えば3回→4回に増えた時に30個→40個と増やさなくて済むようにしました。 古参の機能(?)は新設の呼び出し機会を知らなくてよく、新参の機能で必要であれば、新設の呼び出しで呼んでもらえばいい訳です。
//メインの初期処理 実際の呼び出しは離れています
ksrk_initcall(INIT_CLOCK);
ksrk_initcall(INIT_POWER);
ksrk_initcall(INIT_MAIN);

//10個まとめて呼ぶための配列
TAGITEM_TYPE *(InitCalls[]) =
{
    init_01,
    init_02,
    init_03,
    init_04,
    init_05,
    init_06,
    init_07,
    init_08,
    init_09,
    init_10,
    0
};

//10個まとめて呼ぶ(tag_getvalueは配列内のキーを検索して値を返す)
void ksrk_initcall(uint32_t ulTagKey)
{
    int i;
    for (i = 0; InitCalls[i]; i++)
    {
        void (*pFunc)(void) = (void (*)(void))tag_getvalue(InitCalls[i], ulTagKey, 0);
        if (pFunc)
        {
            (pFunc)();
        }
    }
}

//追加したソース
static void init_main(void)
{
}

//追加したソース内の配列 INIT_MAINのときに呼ばれる
TAGITEM_TYPE init_03[] =
{
    {INIT_MAIN, (uint32_t)init_main},
    {TAG_END, TAG_NONE}
};
これをコーディングしていて、なんだかC++のメンバ関数の動作を作ってるような気がしてきました。 結構似てるでしょ?
さて、今回の鉄ゲタコーナーは…

リンカが優秀で無縁のオブジェクトを破棄する

当初考えていた方法は、 「呼んでほしい関数のアドレスをマジックコード(珍しい数字)で挟んでおいて、メイン処理がメモリ内をスキャンして呼ぶ。」 と言うモノでした。 いざ実行してみるとちっとも見つけてくれない、デバッガでメモリの中身を人力で探してみるけど見つからない。 あれ、マジックコードどこ行った? リンカの実行結果のマップを見ると追加のオブジェクトをばっさり破棄してた。 確かに、ちょっとでも所要サイズを削りたいもんね。 メイン処理から見て関連のないオブジェクトを削除するので、今回の用途には使えませんでした。
まあ、どうせ話が通じないと思うので、あとはソース見てください(^^;
yrntrlmnmnt20120209.zip (248,155 バイトをVPSから 00:05 で)