コンパイラーの関数へのポインターのサポートが壊れている場合、どのようにディスパッチャーを作成しますか?
-
08-07-2019 - |
質問
デバイスがコマンドインターフェイスを介して制御される組み込みアプリケーションで作業しています。 VCでコマンドディスパッチャーをモックし、満足のいく動作をさせました。しかし、その後、コードを組み込み環境に移動したときに、コンパイラのfuncへのポインターの実装が壊れていることがわかりました。
私が最初にコードを実装した方法は次のとおりです(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
}
この状況に対処するためのより良い方法を誰かが見ていますか?悲しいことに、「別のコンパイラを入手する」は実行可能なオプションではありません。 :(
---追加して編集: 明確にするために、コンパイラが" function-pointer table"を作成する想定という点で、特定の組み込み環境が壊れています。次に、コンパイラーがポインターを介して関数の呼び出しを解決するために使用します。残念ながら、コンパイラは壊れており、正しい関数テーブルを生成しません。
つまり、funcアドレスを抽出して呼び出す簡単な方法はありません。
---編集#2: ああ、はい、void *(set | get)_dispatcherの使用は、funcポインターの型定義に問題があるかどうかを確認するための私の試みでした。もともと、私は持っていた
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;
解決
structコマンド
を変更して、関数ポインタが実際の型になるようにしてください:
typedef struct command {
const char *code;
set_fn_t set_dispatcher;
get_fn_t get_dispatcher;
const char *_description;
} command_t;
残念ながら、関数ポインタはvoidポインタへの/からの変換を保証されていません(オブジェクトへのポインタにのみ適用されます)。
埋め込み環境とは
質問の更新に投稿された情報を考えると、それは本当にバグのあるコンパイラであることがわかります。
提案された解決策はかなり合理的だと思います-それはおそらく私が思いついたことに似ているでしょう。
他のヒント
実際には、関数ポインタはvoid *に収まる必要はありません。呼び出している値が実際に関数のアドレスであることを確認することができます。そうでない場合は、構造体で関数ポインター型を使用します。get_fn_tまたはIIRC void(*)(void)は、すべての関数ポインター型との互換性が保証されています。
編集:値による呼び出しを機能させることができないと仮定すると、OK、私はあなたが必要なことを行うより良い方法を考えることはできませんswitchステートメントを自動生成します。 Cプリプロセッサの前に、ruby / python / perl / php / whateverに市販の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;
}
マクロ/インクルードトリックよりも少し読みやすいかもしれませんが、新しいツールを導入してmakefileをセットアップすることは、おそらくこのような少量のコードには価値がありません。また、デバッグ情報の行番号は、プリプロセッサで追加の作業を行って指定しない限り、ソースファイルと思われるファイルに関連しません。
ベンダーにコンパイラを修正してもらえますか?
関数へのポインターはどの程度壊れていますか?
コンパイラが関数のアドレスの取得を許可している場合(私はC ++から来ましたが、&amp; getenv
は私が言っていることです)、呼び出し規約をアセンブラにラップできます。
前述のように、私はC ++ ssieですが、何か邪魔をしています
; function call
push [arg1]
push [arg2]
call [command+8] ; at the 4th location, the setter is stored
ret
それが壊れていても、アセンブリで定義する extern void *
ポインターの配列を定義できます。
この構文を試してください:
return(*((get_fn_t)commands [i] .get_dispatcher))(pc);
C&amp;を実行してからしばらく経ちました関数ポインターですが、元のC構文では関数ポインターを逆参照するときに*が必要でしたが、ほとんどのコンパイラーはそれなしで抜け出すことができます。
リンクマップにアクセスできますか? もしそうなら、多分あなたは不安定な関数ポインタテーブルの周りにあなたの方法をハックすることができます:
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つの格納場所に折りたたむのを防ぐことができます。アーキテクチャによっては、長い時間が必要になる場合があります)
この概念が機能する場合、スクリプトを実行するリンク後ステップを追加して、自動的に置換を行うことができます。もちろん、これは単なる理論であり、惨めに失敗するかもしれません。
たぶん、もう一度構造を調べる必要があります:
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;
}
どこか間違っている場合は修正してください...;)