1. 関数ポインタ
AddressOf は、VB 内の関数ポインタを取得します。この関数ポインタを、この関数をコールバックする必要がある API に渡すことができます。その機能は、外部プログラムが VB 内の関数を呼び出せるようにすることです。
ただし、VB での関数ポインタの適用は C ほど広範囲ではありません。VB のドキュメントでは、関数ポインタを API に渡してコールバックを実装する方法が紹介されているだけで、関数ポインタの多くの魔法の機能については指摘されていません。ポインターの使用は推奨しません。関数ポインターも例外ではありません。
まず、関数ポインタがどのように使用されるかを分類してみましょう。
1. コールバック。これは最も基本的で重要な機能です。たとえば、VB ドキュメントで紹介されているサブクラス派生テクノロジの中核は、SetWindowLong と CallWindowPROc という 2 つの API です。
SetWindowLong API を使用して、元のウィンドウ関数ポインターを独自の関数ポインターに置き換え、元のウィンドウ関数ポインターを保存できます。このようにして、ウィンドウ メッセージを独自の関数に送信でき、CallWindowProc を使用して、いつでも以前に保存したウィンドウ ポインターを呼び出して、元のウィンドウ関数を呼び出すことができます。このようにして、元のウィンドウの機能を破壊することなく、フックされたメッセージを処理できます。
具体的な処理についてはよく知っておく必要があり、VB のドキュメントでも非常にわかりやすく説明されています。ここで注意が必要なのは CallWindowProc API です。その素晴らしい使い方については後ほど説明します。
ここではコールバックを呼び出して、「内部関数ポインターへの外部呼び出し」を許可します。
2. プログラムの内部使用のため。たとえば、C では、関数ポインタを必要とする C 関数 (後述する C ライブラリ関数 qsort など) に C 関数ポインタをパラメータとして渡すことができます。その宣言は次のとおりです。
2 つの変数のサイズを比較するには COMPARE タイプの関数ポインターが必要です。そのため、ソート関数はこの関数ポインターを呼び出して異なるタイプの変数を比較できるため、qsort は異なるタイプの変数配列をソートできます。
このアプリケーションを「内部からの内部関数ポインターの呼び出し」と呼びましょう。
3. 外部関数の呼び出し
「API を使用するということは、外部関数を呼び出すだけではないのですか?」と疑問に思われるかもしれません。はい、ただし、外部関数のポインタを直接取得する必要がある場合もあります。たとえば、LoadLibrary を通じて DLL を動的にロードし、次に GetProcAddress を通じて必要な関数エントリ ポインタを取得し、この関数ポインタを通じて外部関数を呼び出すこの DLL を動的にロードするテクノロジにより、外部関数をより柔軟に呼び出すことができます。
この方法を「内部から外部関数ポインタを呼び出す」と呼びます。
4. もちろん、「外部からの外部関数ポインタの呼び出し」も制御できます。いいえ、たとえば、複数の DLL をロードし、ある DLL の関数ポインタを別の DLL の関数に渡すことができます。
上記の「内部」と「外部」の区分はすべて相対的な用語です (DLL は実際にはまだプロセス中です)。この分類は、今後の問題について議論する際に役立ちます。今後の記事でもこの分類を使用するので、覚えておいてください。問題を分析します。
関数ポインタの使い方は上記の 4 つの方法に他なりません。しかし、実際に使用すると、柔軟で変更可能です。たとえば、C の継承とポリモーフィズム、COM のインターフェイスはすべて、vTable と呼ばれる関数ポインター テーブルの賢い応用です。関数ポインタを使用すると、プログラム処理がより効率的かつ柔軟になります。
VB ドキュメントでは、最初の方法を除いて他の方法は紹介されておらず、「Basic to Basic」関数ポインタはサポートされていないことも明記されています (つまり、上記の 2 番目の方法です)。上記 4 つの方法はすべて実現できます。今日は 2 番目の方法を実装する方法を見てみましょう。実装は比較的簡単なので、簡単な方法から始めましょう。 VB で外部関数ポインタを呼び出す方法や、VB で vTable インターフェイスの関数ポインタ ジャンプ テーブルを処理して、さまざまな関数ポインタの賢い応用を実現する方法については、COM の内部原理に関わるため、別の記事で詳しく説明します。 。
実際、VB のドキュメントは間違っていません。VB は「Basic から Basic」への関数ポインタをサポートしていません。しかし、それを実現するには、まず「Basic から API」を実行してから、最初の方法を使用することができます。 「外部から内部関数ポインタを呼び出して」「API から BASIC へ」移動することで、「基本から基本へ」の 2 番目の方法の目的を達成できます。このテクノロジは、VB でのみ利用できる「強制コールバック」と呼ぶことができます。テクノロジー。
少し複雑ですが、ウィンドウ サブクラス派生テクノロジの CallWindowProc についてよく考えてください。CallWindowProc を使用して、外部オペレーティング システムに元の保存されたウィンドウ関数ポインターを強制的に呼び出すこともできます。同様に、これを使用して内部ウィンドウ関数ポインターを強制的に呼び出すこともできます。関数ポインタ。
あはは、原理についてはあまり話さず、動きについてもっと話すべきだと前に言いました。さあ、動きを学び始めましょう。
C と同じように複数キーワードの比較をサポートする qsort を VB で実装することを考えてみましょう。完全なソース コードは、この記事のサポート コードにあります。ここでは、関数ポインターの適用に関連するコードのみを示します。
最後の qsort ステートメントを最後に見てみましょう。
上記の ArrayPtr は並べ替える必要がある配列の最初の要素へのポインタ、nCount は配列内の要素の数、nElemSize は各要素のサイズ、pfnCompare は比較関数ポインタです。この宣言は、C ライブラリ関数の qsort に非常に似ています。
C と同様に、Basic の関数ポインタを Basic の qsort 関数に渡すことができます。
使用方法:
賢い友人の皆さん、ここでの謎はもう分かりましたか?テストとして、qsort で関数ポインターを使用する方法を教えていただけますか?たとえば、関数ポインタを呼び出して、配列の i 番目の要素と j 番目の要素のサイズを比較する必要があります。
はい、もちろん、強制コールバックを実行するには、前に宣言した Compare API (実際には CallWindowProc) を使用する必要があります。
具体的な実装は以下の通りです。
動きが紹介されていますが、理解できましたか?上記の Compare の意味を簡単に説明します。これは CallWindowProc API を非常に賢く利用しています。この API には 5 つのパラメータが必要です。最初のパラメータは通常の関数ポインタです。この API はすぐに関数ポインタをコールバックし、この API の最後の 4 つの Long 型パラメータを関数ポインタが指す関数に渡すことができます。 CallWindowProc API では、渡される関数ポインターが WndProc 関数のプロトタイプに準拠している必要があるため、比較関数には 4 つのパラメーターが必要です。WndProc のプロトタイプは次のとおりです。
上記の LRESULT、HWND、UINT、WPARAM、LPARAM はすべて VB の Long 型に対応します。Long 型をポインタとして使用できるので、これは非常に優れています。
ワークフローをもう一度見てみましょう。AddressOfCompareSalaryName を関数ポインター パラメーターとして使用して qsort を呼び出すと、qsort の仮パラメーター pfnCompare に実際のパラメーター CompareSalaryName の関数ポインターが割り当てられます。現時点では、Compare を呼び出して pfnCompare へのコールバックを強制することは、次の VB ステートメントを呼び出すことと同じです。
これによりパラメータの型の不一致エラーが発生しないでしょうか? CompareSalaryName の最初の 2 つのパラメータは TEmployee 型ではありませんか?実際、VB の型チェックではそのような呼び出しが許可されないため、そのような呼び出しは VB では不可能です。ただし、この呼び出しは実際には API によって行われるコールバックであり、API コールバック関数のパラメータの型が通常の Long 数値型であるか構造体ポインタであるかを VB が確認することは不可能であるため、 VB の関数パラメータの制御をバイパスするため、この Long パラメータを任意の型のポインタとして宣言できます。したがって、この手法は慎重に使用する必要があります。たとえば、上記の CompareSalaryName 関数に最終的に渡されるパラメーター「ArrayPtr (i-1)*nElemSize」は、常にこのアドレスをチェックしません。誤って「ArrayPtr」として使用された場合、アドレスは TEmployee 型のポインタとして扱われます。 i*nElemSize" の場合、i が最後の要素の場合はメモリ アクセス エラーが発生するため、C でポインタを扱う場合と同様に境界の問題に注意する必要があります。
関数ポインターの賢い応用方法はすでにここで見られますが、ここで紹介した方法にはまだ大きな制限があります。関数には 4 つのパラメーターが必要です。よりクリーンなアプローチは、VC または Delphi で DLL を作成し、関数を実装するためのより準拠した API を作成することです。 CallWindowProc に似ています。 CallWindowProc の内部実装をトレースしました。これは、アプリケーションでは冗長であるウィンドウ メッセージに関連する多くの作業を実行します。実際、強制コールバック API を実装するには、最後のいくつかのパラメーターをスタックにプッシュし、最初のパラメーターを呼び出すだけで済みます。これは、ほんのいくつかのアセンブリ命令にすぎません。
CallWindowProc の制限のため、CallWindowProc を使用して外部関数ポインターを呼び出し、上記の 3 番目の関数ポインター呼び出しメソッドを実装することはできません。 3 番目のメソッドを実装するには、Master MattCurland が悪夢のような HACK メソッドを提供しました。VB で何もないところから IUnknown インターフェイスを構築し、IUnknown インターフェイスの vTable の元の 3 つのエントリの後に新しいエントリを追加する必要があります。新しいエントリ マシン コードを挿入します。このマシン コードは、指定した関数ポインタを最終的に呼び出す前に、このポインタを処理する必要があります。この関数ポインタは、内部か外部かに関係なく問題ありません。 COM の内部について詳しく説明するときに、このメソッドに戻ります。
さらに、ソート アルゴリズムは意見の問題です。この記事では当初、最高のパフォーマンスを備えた最も汎用性の高いアルゴリズムを提供したいと考えていましたが、あらゆる状況に「最適な」アルゴリズムはありません。この記事で提供されているさまざまなポインター テクノロジを使用して実装されたクイック ソート方法は、オブジェクト テクノロジを使用して同じ機能を実装するよりもはるかに高速で、使用するメモリも大幅に少なくなります。しかし、私が最適化したこのクイック ソート アルゴリズムでさえ、ShellSort は実装が簡単であるため、依然として ShellSort ほど優れていません。アルゴリズムから理論的に言えば、qsort の平均パフォーマンスは ShellSort よりも優れているはずですが、VB では必ずしもそうとは限りません (この記事のサポート コードを参照してください。この記事では、VBPJ 列 ShellSort のサポート コードも提供しています。非常に優れており、この記事のアイデアは This ShellSort から引用されています)。
ただし、ここでの Quick Sort と ShellSort の実装にはデータをコピーするために CopyMemroy を広範囲に使用する必要があるため (これは VB でポインターを使用する欠点の 1 つです)、ここでの Quick Sort と ShellSort は両方とも大幅に改善できる可能性があることに注意してください。実際には、VB の配列構造 (COM オートメーションの SafeArray) をハックするという、より良い方法があります。CopyMemroy を使わずに、SafeArray の各配列要素のポインタを一度に長い配列に入れることができます。 SafeArray 配列要素のポインタをリアルタイムで交換するという目的を達成するために、Long 配列内の要素を交換すると、データは移動されず、ポインタのみが移動されます。これがどれほど高速であるかは想像できるでしょう。
->