6

スクリプト言語(Cで記述)からWin32 APIにアクセスできるようにするために、次のような関数を記述したいと思います。

void Call(LPCSTR DllName, LPCSTR FunctionName, 
  LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[])

これは、一般的に、任意のWin32API関数を呼び出します。

(LPSTRパラメーターは基本的にバイト配列として使用されています-関数の外部で正しいデータ型を取得するために正しいサイズになっていると仮定します。また、ポインター引数と非ポインター引数を区別するには、さらに複雑さが必要だと思いますが、この質問の目的のためにそれを無視しています)。

私が抱えている問題は、Win32API関数に引数を渡すことです。これらはstdcallであるため、varargsを使用できないため、「Call」の実装は引数の数を事前に知っている必要があり、したがってジェネリックにすることはできません...

アセンブリコードでこれを行うことができると思います(引数をループし、それぞれをスタックにプッシュすることによって)が、これは純粋なCで可能ですか?

更新:今のところ、「いいえ、できません」という回答にマークを付けました。もちろん、Cベースのソリューションが明らかになった場合は、これを変更します。

更新: ruby / dlは、適切なメカニズムを使用して実装されているようです。これに関する詳細をいただければ幸いです。

4

8 に答える 8

8

まず最初に: C では型をパラメーターとして渡すことはできません。残された唯一のオプションはマクロです。

LoadLibrary/GetProcAddressこのスキームは、 Win32 関数を呼び出すために を実行している場合、少し変更 (引数の void * の配列) で機能します。それ以外の場合、関数名の文字列を使用しても役に立ちません。C では、関数を呼び出す唯一の方法は、ほとんどの場合、関数へのポインターに減衰する名前 (識別子) を使用することです。また、戻り値のキャストにも注意する必要があります。

私の最善の策:

// define a function type to be passed on to the next macro
#define Declare(ret, cc, fn_t, ...) typedef ret (cc *fn_t)(__VA_ARGS__)

// for the time being doesn't work with UNICODE turned on
#define Call(dll, fn, fn_t, ...) do {\
    HMODULE lib = LoadLibraryA(dll); \
    if (lib) { \
        fn_t pfn = (fn_t)GetProcAddress(lib, fn); \
        if (pfn) { \
            (pfn)(__VA_ARGS__); \
        } \
        FreeLibrary(lib); \
    } \
    } while(0)

int main() {
    Declare(int, __stdcall, MessageBoxProc, HWND, LPCSTR, LPCSTR, UINT);

    Call("user32.dll", "MessageBoxA", MessageBoxProc, 
          NULL, ((LPCSTR)"?"), ((LPCSTR)"Details"), 
          (MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2));

    return 0;
}
于 2009-03-09T11:11:09.147 に答える
5

いいえ、アセンブリを書かないとできないと思います。その理由は、ターゲット関数を呼び出す前にスタック上にあるものを正確に制御する必要があり、純粋な C でそれを行う実際の方法がないためです。もちろん、アセンブリで行うのは簡単です。

また、これらすべての引数に PCSTR を使用していますが、これは実際にはconst char *. しかし、これらの引数はすべて文字列ではないため、戻り値と Arguments[] に実際に使用したいのはvoid *orLPVOIDです。これは、引数を にキャストするのではなく、引数の実際の型がわからない場合に使用する型ですchar *

于 2009-03-09T11:09:38.933 に答える
2

他の投稿は、実際の呼び出し規則のすべての詳細は言うまでもなく、実際に呼び出しを行うためのアセンブリまたはその他の非標準のトリックのほぼ確実な必要性について正しいです。

Windows DLL は、関数に対して少なくとも 2 つの異なる呼び出し規則を使用します:stdcallcdecl. 両方を処理する必要があり、どちらを使用するかを把握する必要がある場合もあります。

これに対処する 1 つの方法は、既存のライブラリを使用して多くの詳細をカプセル化することです。驚くべきことに、libffiがあります。スクリプト環境での使用例として、任意の DLL へのインターフェイスを Alien 自体とは別に純粋な Lua で作成できるようにする Lua モジュールであるLua Alienの実装があります。

于 2009-04-08T06:49:28.897 に答える
1

次のようなものを試すことができます-win32API関数でうまく機能します:

int CallFunction(int functionPtr, int* stack, int size)
{
    if(!stack && size > 0)
        return 0;
    for(int i = 0; i < size; i++) {
        int v = *stack;
        __asm {
            push v
        }
        stack++;
    }
    int r;
    FARPROC fp = (FARPROC) functionPtr;
    __asm {
        call fp
        mov dword ptr[r], eax
    }
    return r;
}

「stack」引数のパラメーターは、逆の順序である必要があります(これは、スタックにプッシュされる順序であるため)。

于 2009-11-01T09:36:31.473 に答える
1

多くの Win32 API は、特定のレイアウトを持つ構造体へのポインターを取ります。これらのうち、大きなサブセットは、呼び出される前に最初の DWORD を構造体のサイズに初期化する必要がある一般的なパターンに従います。場合によっては、構造体を書き込むメモリ ブロックを渡す必要があります。メモリ ブロックは、最初に NULL ポインターを使用して同じ API を呼び出し、戻り値を読み取って正しい値を検出することによって決定されるサイズでなければなりません。サイズ。一部の API は、構造体を割り当て、それへのポインターを返すため、2 回目の呼び出しでポインターの割り当てを解除する必要があります。

単純な文字列表現から変換可能な個々の引数を使用して、ワンショットで便利に呼び出すことができる API のセットが非常に小さい場合でも、それほど驚くことではありません。

この考えを一般的に適用できるようにするには、かなり極端に行かなければなりません。

typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[],
                             size_t maxReturnValueSize, wchar_t *returnValue);

DynamicFunction *GenerateDynamicFunction(const wchar_t *code);

コードの簡単なスニペットを GenerateDynamicFunction に渡すと、そのコードが標準的なボイラープレートでラップされ、C コンパイラ/リンカーが呼び出されて、関数を含む DLL が作成されます (利用可能な無料のオプションがかなりあります)。次に、LoadLibraryその DLL を使用GetProcAddressして関数を検索し、それを返します。これにはコストがかかりますが、一度実行すると、結果の DynamicFunctionPtr をキャッシュして繰り返し使用できます。これを動的に行うには、ポインターをハッシュテーブルに保持し、コード スニペット自体をキーにします。

定型文は次のようになります。

#include <windows.h> 
// and anything else that might be handy

void DynamicFunctionWrapper(size_t argumentCount, const wchar_t *arguments[],
                             size_t maxReturnValueSize, wchar_t *returnValue)
{
    // --- insert code snipped here
}

したがって、このシステムの使用例は次のようになります。

DynamicFunction *getUserName = GenerateDynamicFunction(
    "GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))");

wchar_t userName[100];
getUserName(0, NULL, sizeof(userName) / sizeof(wchar_t), userName);

引数の数を受け入れるようにすることでこれを強化できるためGenerateDynamicFunction、ラッパーの開始時に正しい数の引数が渡されたことを確認できます。そこにハッシュテーブルを配置して、遭遇したコードニペットごとに関数をキャッシュすると、元の例に近づくことができます。Call 関数は、API 名だけでなくコード スニペットを使用しますが、それ以外は同じです。ハッシュテーブルでコード スニペットを検索し、存在しない場合は GenerateDynamicFunction を呼び出し、次回のために結果をハッシュ テーブルに格納します。次に、関数の呼び出しを実行します。使用例:

wchar_t userName[100];

Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))",
     0, NULL, sizeof(userName) / sizeof(wchar_t), userName);

もちろん、ある種の一般的なセキュリティ ホールを開くことが目的でない限り、これを行ってもあまり意味がありません。たとえば、Web サービスとして公開Callします。元のアイデアにはセキュリティへの影響が存在しますが、提案した元のアプローチがそれほど効果的ではないという理由だけで、それほど明白ではありません。一般的に強力にすればするほど、セキュリティ上の問題が大きくなります。

コメントに基づく更新:

.NET フレームワークには p/invoke と呼ばれる機能があり、これはまさに問題を解決するために存在します。したがって、何かを学ぶためのプロジェクトとしてこれを行っている場合は、 p/invoke を見て、それがどれほど複雑かを知ることができます。スクリプトをリアルタイムで解釈したり、スクリプトを独自のバイトコードにコンパイルしたりする代わりに、スクリプト言語で .NET フレームワークをターゲットにすることができます。IL にコンパイルすることができます。または、.NET で既に利用可能な多くのスクリプト言語から、既存のスクリプト言語をホストすることもできます。

于 2009-03-12T10:29:16.977 に答える
0

まず、各引数のサイズを追加パラメーターとして追加する必要があります。それ以外の場合は、スタックにプッシュする各関数の各パラメーターのサイズを推測する必要があります。これは、ドキュメントに記載されているパラメーターと互換性がある必要があるため WinXX 関数で可能ですが、面倒です。

第 2 に、varargs 関数を除いて、引数を知らずに関数を呼び出す "純粋な C" の方法はありません。また、.DLL 内の関数で使用される呼び出し規則に制約はありません。

実際には、最初の部分よりも 2 番目の部分の方が重要です。

理論的には、プリプロセッサ マクロ/#include 構造を設定して、たとえば 11 個のパラメーターまでのパラメーター型のすべての組み合わせを生成できますが、それは、関数を介して渡される型を事前に知っていることを意味しますCall。あなたが私に尋ねると、これは一種のクレイジーです。

ただし、これを本当に安全でない方法で行いたい場合は、C++ マングル名を渡しUnDecorateSymbolName、パラメーターの型を抽出するために使用できます。ただし、C リンケージでエクスポートされた関数では機能しません。

于 2009-03-09T20:25:24.463 に答える
0

興味があるかどうかはわかりませんが、オプションとして、RunDll32.exe にシェル アウトし、関数呼び出しを実行させることができます。RunDll32 にはいくつかの制限があり、戻り値にまったくアクセスできないと思いますが、コマンド ライン引数を適切に作成すると、関数が呼び出されます。

ここにリンクがあります

于 2009-03-09T17:44:46.550 に答える