1

コマンド インターフェイスを介してデバイスを制御する組み込みアプリケーションに取り組んでいます。私は VC でコマンド ディスパッチャーを嘲笑し、満足のいく動作をさせました。しかし、コードを組み込み環境に移したとき、コンパイラーの関数へのポインターの実装が壊れていることがわかりました。

これが私が最初にコードを実装した方法です(VCで):

/* Relevant parts of header file  */
typedef struct command {
  const char *code;
  void *set_dispatcher;
  void *get_dispatcher;
  const char *_description;
} command_t;

#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label, &set_##dispatcher, &get_##dispatcher, (const char*)description} 


/* Dispatcher data structure in the C file */
const command_t commands[] = {
  COMMAND_ENTRY("DH", Dhcp, "DHCP (0=off, 1=on)"),
  COMMAND_ENTRY("IP", Ip, "IP Address (192.168.1.205)"),
  COMMAND_ENTRY("SM", Subnet, "Subunet Mask (255.255.255.0)"),
  COMMAND_ENTRY("DR", DefaultRoute, "Default router (192.168.1.1)"),
  COMMAND_ENTRY("UN", Username, "Web username"),
  COMMAND_ENTRY("PW", Password, "Web password"),
  ...
}


/* After matching the received command string to the command "label", the command is dispatched */
if (pc->isGetter)
  return ((get_fn_t)(commands[i].get_dispatcher))(pc);
else
  return ((set_fn_t)(commands[i].set_dispatcher))(pc);
  }

関数ポインターを使用しない場合、私の唯一の望みは、switch()/case ステートメントを使用して関数を呼び出すことです。しかし、大きな switch() ステートメントを手動で維持する必要は避けたいと思います。

私が考えていたのは、すべての COMMAND_ENTRY 行を別のインクルード ファイルに移動することです。次に、そのインクルード ファイルをさまざまな #define および #undefines でラップします。何かのようなもの:

/* Create enum's labels */
#define COMMAND_ENTRY(label,dispatcher,description) SET_##dispatcher, GET_##dispatcher
typedef enum command_labels = {
#include "entries.cinc"
  DUMMY_ENUM_ENTRY} command_labels_t;
#undefine COMMAND_ENTRY


/* Create command mapping table */
#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label, SET_##dispatcher, GET_##dispatcher, (const char*)description} 
const command_t commands[] = {
#include "entries.cinc"
  NULL /* dummy */ };
#undefine COMMAND_ENTRY

/*...*/

int command_dispatcher(command_labels_t dispatcher_id) {
/* Create dispatcher switch statement */
#define COMMAND_ENTRY(label,dispatcher,description) case SET_##dispatcher: return set_##dispatcher(pc); case GET_##dispatcher: return get_##dispatcher(pc);
switch(dispatcher_id) {
#include "entries.cinc"
default:
  return NOT_FOUND;
}
#undefine COMMAND_ENTRY
}

この状況を処理するためのより良い方法を見ている人はいますか? 残念ながら、「別のコンパイラを入手する」という選択肢はありません。:(

--- 編集して追加: 明確にするために、特定の組み込み環境は、コンパイラが「関数ポインタ テーブル」を作成することになっているという点で壊れています。これは、ポインタを介して関数への呼び出しを解決するためにコンパイラによって使用されます。残念ながら、コンパイラは壊れており、正しい関数テーブルを生成しません。

そのため、関数アドレスを抽出して呼び出す簡単な方法がありません。

--- 編集 #2: ああ、はい、void *(set|get)_dispatcher の使用は、func ポインターの typedefine に問題があるかどうかを確認するための私の試みでした。もともと、持っていた

typedef int (*set_fn_t)(cmdContext_t *pCmdCtx);
typedef int (*get_fn_t)(cmdContext_t *pCmdCtx);

typedef struct command {
  const char *code;
  set_fn_t set_dispatcher;
  get_fn_t get_dispatcher;
  const char *_description;
} command_t;
4

7 に答える 7

7

struct command関数ポインターが実際の型を持つように変更してみてください。

typedef struct command {
  const char *code;
  set_fn_t set_dispatcher;
  get_fn_t get_dispatcher;
  const char *_description;
} command_t;

残念ながら、関数ポインターが void ポインターとの間で変換できることは保証されていません (これはオブジェクトへのポインターにのみ適用されます)。

組込み環境は?


質問の更新に投稿された情報を考えると、それは本当にバグのあるコンパイラであることがわかります。

あなたが提案した解決策はかなり合理的だと思います - それはおそらく私が思いついたものと似ています.

于 2008-11-12T19:09:34.847 に答える
3

関数ポインターは、実際には void* に収まる必要はありません。呼び出している値が実際に関数のアドレスであることを確認できます。そうでない場合は、構造体で関数ポインター型を使用します。get_fn_t または IIRC void(*)(void) のいずれかが、任意の関数ポインター型と互換性があることが保証されています。

編集: OK、値による呼び出しを機能させることができないと仮定すると、switch ステートメントを自動生成するよりも、必要なことを行うためのより適切な方法は考えられません。C プリプロセッサの前に、ruby/python/perl/php などに市販の ASP スタイルのプリプロセッサ モードを使用できるかもしれません。このようなもの:

switch(dispatcher_id) {
<% for c in commands %>
    case SET_<% c.dispatcher %>: return set_<% c.dispatcher %>(pc); 
    case GET_<% c.dispatcher %>: return get_<% c.dispatcher %>(pc);
<% end %>
default:
    return NOT_FOUND;
}

マクロ/インクルードのトリックよりも少し読みやすいかもしれませんが、新しいツールを導入してメイクファイルをセットアップすることは、そのような少量のコードにはおそらく価値がありません. また、デバッグ情報の行番号は、プリプロセッサで追加の作業を行って指定しない限り、ソース ファイルと見なされるファイルとは関係ありません。

于 2008-11-12T19:10:19.570 に答える
2

ベンダーにコンパイラの修正を依頼できますか?

于 2008-11-12T19:12:23.903 に答える
1

関数へのポインタはどの程度壊れていますか?

コンパイラで関数のアドレスを取得できる場合 (私は C++ 出身ですが、これ&getenvが意味です)、呼び出し規約をアセンブラーにラップできます。

前述のとおり、私は C++ を使用していますが、

; function call
push [arg1]
push [arg2]
call [command+8] ; at the 4th location, the setter is stored
ret

それでも壊れている場合はextern void*、アセンブリで再度定義するポインターの配列を定義できます。

于 2008-11-12T19:07:38.453 に答える
0

リンクマップにアクセスできますか?もしそうなら、多分あなたは風変わりな関数ポインタテーブルの周りにあなたの方法をハックすることができます:

unsigned long addr_get_dhcp = 0x1111111;
unsigned long addr_set_dhcp = 0x2222222; //make these unique numbers.

/* Relevant parts of header file  */
typedef struct command {
  const char *code;
  unsigned long set_dispatcher;
  unsigned long get_dispatcher;
  const char *_description;
} command_t;

#define COMMAND_ENTRY(label,dispatcher,description) {(const char*)label, 
    addr_set_##dispatcher, addr_get_##dispatcher, (const char*)description} 

次に、コンパイルし、リンクマップから関連するアドレスを取得し、定数を置き換えて、再コンパイルします。何も動かないので、マップは同じままである必要があります。(元の定数を一意にすることで、コンパイラーが同一の値を1つの保管場所に折りたたむのを防ぐことができます。アーキテクチャーによっては、長い時間が必要になる場合があります)

コンセプトが機能する場合は、スクリプトを実行するリンク後のステップを追加して、自動的に置換を行うことができます。もちろん、これは単なる理論であり、惨めに失敗する可能性があります。

于 2008-11-12T23:14:18.363 に答える
0

おそらく、構造をもう一度調べる必要があります。

typedef struct command {
  const char *code;
  void *set_dispatcher; //IMO, it does not look like a function pointer...
  void *get_dispatcher; //more like a pointer to void
  const char *_description;
} command_t;

ディスパッチャーに次のような同様の関数定義があるとします。

//a function pointer type definition
typedef int (*genericDispatcher)(int data);

ディスパッチャーが以下のようなものであると仮定します。

int set_DhcpDispatcher(int data) { return data; }
int get_DhcpDispatcher(int data) { return 2*data; }

したがって、改訂された構造は次のようになります。

typedef struct command {
  const char *code;
  genericDispatcher set_dispatcher; 
  genericDispatcher get_dispatcher; 
  const char *_description;
} command_t;

マクロは次のようになります。

#define COMMAND_ENTRY(label,dispatcher,description) \
{   (const char*)label, \
    set_##dispatcher##Dispatcher, \
    get_##dispatcher##Dispatcher, \
    (const char*)description } 

次に、通常どおり配列を設定できます。

int main(int argc, char **argv)
{
    int value1 = 0, value2 = 0;

    const command_t commands[] = {
      COMMAND_ENTRY("DH", Dhcp, "DHCP (0=off, 1=on)")
    };

    value1 = commands[0].set_dispatcher(1);
    value2 = commands[0].get_dispatcher(2);

    printf("value1 = %d, value2 = %d", value1, value2);

    return 0;
}

どこか間違っていたら訂正してください... ;)

于 2008-12-03T17:15:36.733 に答える
0

この構文を試してください:

return (*((get_fn_t)commands[i].get_dispatcher))(pc);

C と関数ポインターを作成してからしばらく経ちましたが、元の C 構文では関数ポインターを逆参照するときに * が必要だったと思いますが、ほとんどのコンパイラでは * がなくても問題ありません。

于 2008-11-12T19:03:43.907 に答える