取引がありました-FTDIの USBモジュールを操作するためのインターフェイスを作成しました。 DLLインターフェースの接続にかなり手を加える必要がありました。 Microsoft Visual Studio 2008の自動リンクの可能性に苛立ち( UPD :その後このトピックを見つけました)、私はそれを手動で行うことにしました。 その過程で、数十個の機能を手動で接続することに非常にうんざりしていました。 次に、Google、C ++、およびテンプレートを使用しました。 また、C ++スタイルのDLLの接続で問題が発生しなかった場合、FT_Openオブジェクトが「エラー= FT_Open(Num、&_Handler)」のスタイルで接続された関数への便利な呼び出しがすぐに失敗しました。 結果(ここでのそのような関数の場合)はカットされています。 要するに-私は関数ポインターをラップしました。
問題の声明
すぐに予約する-私はWindows XP Prof、Visual Studioで働いています。 これは、関数のアドレスを取得するために重要です。 ただし、関数ポインターを使用する場合、これは重要ではありません。
トピックに含まれていない人のために、WinAPIを使用してFTD2XX.dllからFT_Open関数を見つけるシーケンスを次に示します。
#include "FTD2XX.h" // FTDI typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *); // " FT_OPEN" // ... HMODULE hMod = LoadLibrary ("FTD2XX.dll"); // - . . pFT_Open pOpen = GetProcAddress (hMod, "FT_Open"); // - . . // ... FT_STATUS st = pOpen (0, &hDev); // // ... FreeLibrary (hMod); //
関数が1つである場合は問題ではありませんが、このライブラリでは51個の関数をカウントしました。 そして、それぞれについて次のことを行う必要があります。
typedef FT_STATUS (*pFT_Open) (int, FT_HANDLE *); // " " pFT_Open pOpen; // " " pFT_Open pOpen = GetProcAddress (hMod, "FT_Open"); // "FT_Open"
特に厄介なのは、大量のtypedefを生成する必要があることです。 はい、私は知っています、あなたはtypedefなしで書くことができますが、それは嫌に見えます!
したがって、私は何らかの形で私の人生を簡素化したいです:
Funct2<FT_STATUS, int, FT_HANDLE *> Open; // " 2 " Open = GetProcAddress (hMod, "FT_Open"); // "FT_Open" // ... FT_STATUS st = Open (0, &hDev); //
解決策
実験と脳の沸騰の間に、私はそのようなクラステンプレートを得ました:
template <typename Ret, typename Arg1, typename Arg2, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue> class Funct2 { public: typedef Ret (*tfPtr) (Arg1, Arg2); tfPtr fPtr; public: Funct2 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct2 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1, Arg2 A2) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1, A2); } }; // class Funct2
ここではすべてが初歩的だと思いますが、まだ初心者のために説明します。
Funct2テンプレートが作成され、関数によって返されるRetタイプが最初のパラメーターとして設定されます。 次の2つのパラメーター-Arg1およびArg2-は、関数の引数のタイプを指定します。 エラー処理を普遍化するために、例外タイプとその値Valueが設定されます(デフォルトのパラメーターは#define FunctPtrExceptionTypeと#define FunctPtrExceptionDefValueに設定されます)。
クラステンプレートの本体では、tfPtr型「2つのパラメーターを持つ関数へのポインター」とポインターfPtr自体が設定されます。
デフォルトコンストラクターは、nullポインターまたは特定のアドレス(指定されている場合)を設定します。 また、アドレスは、オーバーロード演算子=(void * Ptr)を使用して設定できます。 なぜvoid *-GetProcAddress()が正確にvoid *を返すため。 署名演算子=(tfPtr Ptr)でオーバーロードする必要はありません-コンパイラはそれが何であるかをすでに理解しています。
最後に、オーバーロード演算子()を使用して、クラスをファンクターとして使用し、クラスユーザーのために使用します。一般的には単純な関数呼び出しです。
便利ですか? とても! 参照:
結果
#include < Windows.h > // GetProcAdress #include < stdio.h > // printf // ** // ** // ** #define FunctPtrExceptionType int // #define FunctPtrExceptionDefValue 0 // // ** // ** // ** template < typename Ret = void, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct0 { public: typedef Ret (*tfPtr) (void); tfPtr fPtr; public: Funct0 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct0 &operator= (tfPtr Ptr) { fPtr = Ptr; return this; } Ret operator () (void) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (); } }; // ** // ** 1 // ** template < typename Ret, typename Arg1, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct1 { public: typedef Ret (*tfPtr) (Arg1); tfPtr fPtr; public: Funct1 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct1 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1); } }; // ** // ** 2 // ** template < typename Ret, typename Arg1, typename Arg2, typename Except = FunctPtrExceptionType, Except Value = FunctPtrExceptionDefValue > class Funct2 { public: typedef Ret (*tfPtr) (Arg1, Arg2); tfPtr fPtr; public: Funct2 (tfPtr Ptr = 0): fPtr (Ptr) {} Funct2 &operator= (void *Ptr) { fPtr = reinterpret_cast<tfPtr> (Ptr); return *this; } Ret operator () (Arg1 A1, Arg2 A2) throw (Except) { if (!fPtr) throw Except (Value); return fPtr (A1, A2); } }; // ** // ** // ** int add (const int A, const int *B) { int C; C = A + *B; printf (" int add (const int %d, const int %d) = %d\n", A, *B, C); return C; } void prn (void) { printf (" void prn (void)\n"); } // ** // ** // ** void main (void) { int i, i2; double d; long l; Funct0<> prner (prn); Funct1< double, long *, int, 2 > longer; Funct2< int, const int, const int * > adder; adder = add; longer = GetProcAddress (0, "Longer"); try { prner (); i2 = 6; i = adder (5, &i2); d = longer (&l); } catch (int val) { switch (val) { case 2: printf (" *** !\n"); break; default: printf (" *** !\n"); break; } }
まとめ
リリースモードの逆アセンブラは、このような関数を呼び出すときのオーバーヘッドが0番目の値、したがって別の呼び出しをチェックしていることを示しました。 現代のPCではこれは問題ではないと思います。
完璧を期すために、例外のトピックを何らかの方法で洗練することができます-テキスト文字列、独自のカスタムエラークラスなどを渡すのは良いことです。しかし、
もちろん、3x、4xなどの引数に対してFunctのさまざまなバリアントをリベットする必要があります。 それらを生成するマクロの種類を考え出すといいでしょう...
さて、さらに理解しやすいことですが、これらすべてを別の.Hファイルに入れる必要があります。
誰かが時間を節約してくれることを願っています。 建設的なコメントに感謝します!
PS操作中に、通話契約などの不快なことが明らかになりました。 Funct0Stdcall、Funct0Cdeclを行う必要があるようです。 Funct1Stdcall、Funct1Cdecl ...