2009-03-09 6 views
6

는 (C로 작성) 스크립트 언어에서의 Win32 API에 대한 액세스를 허용하기 위해, 나는 다음과 같은 함수를 작성하고 싶습니다 : 일반적으로, 호출Win32 함수를 호출하기위한 일반적인 C 함수를 작성하려면 어떻게해야합니까?

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

, 모든 Win32 API 함수를.

(LPSTR 매개 변수는 본질적으로 바이트 배열로 사용됩니다. 함수의 외부에 올바른 데이터 형식을 가져올 수 있도록 올바르게 크기가 조정되어 있다고 가정합니다. 포인터와 비표준을 구별하기 위해 약간의 추가 복잡성이 필요합니다. 포인터 인수하지만이 질문의 목적을 위해 그것을 무시하고있다).

문제는 Win32 API 함수에 인수를 전달하는 것입니다. 이것들은 stdcall이므로 varargs를 사용할 수 없으므로 'Call'의 구현은 미리 인자의 수를 알아야하므로 일반화 될 수 없습니다. 인수를 반복하면서 스택으로 푸시)하지만 순수 C에서는 가능합니까?

업데이트 : 나는 '허용 할 수 없음'답변을 현재 허용으로 표시했습니다. C 기반 솔루션이 밝혀지면 물론이 점을 바꿀 것입니다.

업데이트 :ruby/dl은 적절한 메커니즘을 사용하여 구현 될 수 있습니다. 이것에 대한 자세한 내용은 감사하겠습니다.

답변

5

아니요, 일부 어셈블리를 쓰지 않고도 가능하지 않다고 생각합니다. 그 이유는 타겟 함수를 호출하기 전에 스택에있는 것을 정확하게 제어 할 필요가 있기 때문이며 순수한 C에서는이를 수행 할 실제 방법이 없습니다. 물론 어셈블리에서는 그렇게하는 것이 간단합니다.

또한이 모든 인수에 대해 PCSTR을 사용하고 있으며 실제로는 const char *입니다. 그러나이 모든 args는 문자열이 아니므로 반환 값과 인수 []에 실제로 사용하려는 것은 void * 또는 LPVOID입니다. 이것은 인수의 실제 유형을 알지 못할 때 사용해야하는 유형으로,이 매개 변수를 char *으로 변환하지 않아야합니다.

+0

문자열 대신 바이트 배열로 char *를 사용하고 있습니다. 내가 필요한 '실제'형식의 크기 (즉, 바이트 수)를 설명하는 '호출'함수에서 메타 데이터를 사용할 수 있다고 가정합니다. 이러한 의미에서 유형은 * 알려져 있습니다. 이들은 알려진 바이트 수입니다. –

+0

일반적으로 전달 규칙은 다른 유형의 데이터 (특히 부동 소수점 인수)에 따라 다를 수 있으므로 호출하기 위해 바이트 수 이상이 필요합니다. 하지만 당신이 옳을지도 모르겠지만 win32 __stdcall은 스택에 모든 것을 넣을뿐입니다. – puetzk

8

첫 번째 사항 : C에서 매개 변수로 유형을 전달할 수 없습니다. 남은 유일한 옵션은 매크로입니다.

이 체계는 Win32 함수를 호출하기 위해 LoadLibrary/GetProcAddress을 수행하는 경우 약간의 수정 (인수에 대해 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; 
} 
+0

그래서 C에서이 작업을 수행 할 수 있습니다 * 내 예제에서 '호출'함수의 프로토 타입이 아니라 Win32 함수 (실제로 제어 할 수 없음)에 대한 실제 호출이라는 문제가 있습니까? –

+0

여기서 __VA_ARGS__를 사용하지 않으면 호출이 stdcall이 아닌 cdecl이라고 가정합니다 (따라서 인수가 잘못된 순서로 스택에 푸시됩니다). –

+0

__VA_ARGS__는 가변 매크로입니다. 이것은 전처리 단계 후에 존재하지 않습니다. variadic 함수의 va_args와 같지 않습니다. 또한 함수의 유형을 매크로 매개 변수로 전달 중입니다. – dirkgently

0

는 그런 기능이 나쁜 생각처럼 들리 데,하지만 당신이 시도 할 수 있습니다 : 거의 모든 값에 맞게 32 비트 시스템에서

int Call(LPCSTR DllName, LPCSTR FunctionName, 
    USHORT ArgumentCount, int args[]) 
{ 
    void STDCALL (*foobar)()=lookupDLL(...); 

    switch(ArgumentCount) { 
     /* Note: If these give some compiler errors, you need to cast 
      each one to a func ptr type with suitable number of arguments. */ 
     case 0: return foobar(); 
     case 1: return foobar(args[0]); 
     ... 
    } 
} 

을 32 비트 단어로 변환하고 더 짧은 값은 함수 호출 인수에 대해 32 비트 단어로 스택에 푸시됩니다. 따라서 모든 Win32 API 함수를이 방식으로 거의 호출 할 수 있어야합니다. 인수를 int로, 값을 int로 변환하면됩니다. 적절한 유형으로

+0

일반적인 구현의 경우, 인수 수에 따라 switch (또는 if-else) 코드를 실제로 갖고 싶지는 않습니다. 그러나 필자가 필요로하는 인수의 수에 실용적인 제한이 있음에 동의합니다. –

+0

또한 cdecl 함수를 호출해야합니다 ... – RBerteig

0

나는 그것이 당신에게 흥미로울 지 모르지만, 옵션은 RunDll32.exe를 쉘 아웃하고 당신을 위해 함수 호출을 실행할 것입니다. RunDll32에는 몇 가지 제한이 있으며 반환 값에 액세스 할 수 있다고는 생각하지 않지만 명령 줄 인수를 제대로 구성하면 함수가 호출됩니다.

다음은 추가 매개 변수로 각 인수의 크기를 추가해야합니다, 먼저 link

+0

링크 주셔서 감사하지만 RunDll32가 Win32 API 함수 호출을 명시 적으로 허용하지 않는다고 제안합니다 ... –

0

입니다. 그렇지 않으면 스택에 푸시 (push)하기 위해 각 함수의 각 매개 변수의 크기를 결정해야합니다. WinXX 함수는 문서화 된 매개 변수와 호환되어야하므로 지루합니다.

둘째, varargs 함수를 제외하고 인수를 알지 못하면 함수를 호출하는 "순수한 C"방법이 없으며 .DLL의 함수에서 사용하는 호출 규칙에 대한 제약이 없습니다.

실제로 두 번째 부분이 첫 번째 부분보다 중요합니다.

이론 상으로는 전처리 매크로/# include 구조를 설정하여 최대 11 개의 매개 변수 유형의 모든 조합을 생성 할 수 있지만 어떤 유형이 사용자에게 전달되는지 미리 알 수 있음을 의미합니다 Call. 네가 나 한테 물어 보면 미친 짓이야.

정말 안전하지 않은 경우 C++ 변경된 이름을 전달하고 UnDecorateSymbolName을 사용하여 매개 변수 유형을 추출 할 수 있습니다. 그러나 C 링키지로 내 보낸 함수에는 작동하지 않습니다.

1

많은 Win32 API는 특정 레이아웃이있는 구조체에 대한 포인터를 사용합니다. 이 중 큰 부분 집합은 호출되기 전에 구조체의 크기를 갖도록 첫 번째 DWORD를 초기화해야하는 일반적인 패턴을 따릅니다. 때로는 구조체를 쓸 메모리 블록이 필요하며, 메모리 블록은 먼저 NULL 포인터로 동일한 API를 호출하고 올바른 값을 찾기 위해 반환 값을 읽음으로써 결정되는 크기 여야합니다 크기. 일부 API는 구조체를 할당하고 그 포인터를 두 번째 호출로 할당 해제해야하는 포인터를 반환합니다.

단순한 문자열 표현에서 변환 가능한 개별 인수를 사용하여 한 번에 유용하게 호출 할 수있는 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함으로써이 문제를 개선 할 수, 그것은을 생성 할 수 있도록 랩퍼 시작 부분에서 올 Y 른 수의 인수가 전달되었는지 점검하십시오. 그리고 거기에 해시 테이블을 넣어서 각각의 코드 네피 닛에 대한 함수를 캐시하면 원래 예제에 가까워 질 수 있습니다. Call 함수는 API 이름 대신 코드 스 니펫을 사용하지만 그렇지 않은 경우 코드 스 니펫을 사용합니다. 해시 테이블에서 코드 스 니펫을 검색합니다. 존재하지 않으면 GenerateDynamicFunction을 호출하고 다음 번에 해시 테이블에 결과를 저장합니다. 그런 다음 함수에 대한 호출을 수행합니다. 사용 예제는 : 아이디어는 일반적인 보안 구멍이 어떤 종류의를 열어이었다 않는 한 물론

wchar_t userName[100]; 

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

이 중 하나를 수행 많은 점을 없을 것입니다. 예 : 웹 서비스로 Call을 노출합니다. 원래의 아이디어에는 보안상의 의미가 있지만, 제안한 원래의 방식이 그다지 효과적이지 않으므로 간단하지 않습니다. 더 일반적으로 우리가 그것을 강력하게 만들수록 보안 문제가 더 심각해질 것입니다. 의견에 따라

업데이트 :

닷넷 프레임 워크는 문제를 해결하기 위해 정확하게 존재 호출/P라는 특징을 갖고있다. 따라서 이것을 물건에 대해 배우는 프로젝트로하고 있다면 p/invoke를보고 그것이 얼마나 복잡한지를 알 수 있습니다. 스크립트 언어로 .NET 프레임 워크를 타겟팅 할 수도 있습니다. 스크립트를 실시간으로 해석하거나 자신의 바이트 코드로 컴파일하는 대신 일리노이로 컴파일 할 수 있습니다. 또는 이미 .NET에서 사용할 수있는 많은 스크립팅 언어를 기존의 스크립팅 언어로 호스팅 할 수 있습니다.

+0

+1하지만 내 의도는 임의로 Win32 API를 호출하는 기능을 추가하는 것입니다. C로 구현 된 스크립팅 언어 (보안 구멍을 열지 말고!) 차라리 구현은 가능한 경우 C 컴파일러/링커에 의존하지 않았습니다. –

+0

업데이트 해 주셔서 감사합니다 (불행히도 다시는 당신을 업 그레 이드 할 수 없습니다!). 이상적으로 C 기반 솔루션을 찾고 있지만 .NET 옵션은 생각을위한 음식입니다. –

+0

기술에는 보안 허점이 아닌 사용 사례가 있습니다. 하나는 스크립트 작성자가 컴파일 할 필요없이 외부 코드 나 시스템 API를 스크립팅 언어에 맞게 수정하는 것입니다. – RBerteig

2

다른 게시물은 실제 호출 규칙에 대한 자세한 내용은 말할 것도없고 실제로 호출하기위한 어셈블리 또는 기타 비표준 트릭에 대한 거의 확실한 필요성에 대해 옳습니다.

Windows DLL은 함수에 대해 두 가지 고유 한 호출 규칙 (stdcallcdecl)을 사용합니다. 둘 다 처리해야하며 사용할 대상을 찾아야 할 수도 있습니다.

이 문제를 해결하는 방법 중 하나는 기존 라이브러리를 사용하여 많은 세부 정보를 캡슐화하는 것입니다. 놀랍게도, 하나가 있습니다 : libffi. 스크립팅 환경에서 사용하는 예제는 Lua Alien의 구현입니다.이 모듈은 Alien 자체를 제외하고 순수 루아에서 임의의 DLL에 대한 인터페이스를 만들 수 있도록 허용합니다.

1

이 같은 것을 시도해 볼 수도 있습니다 - 그것은는 Win32 API 함수를 위해 잘 작동합니다

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; 
} 

매개 변수를 "스택"인수이 그들이에 밀어 순서대로 (역순으로해야한다 스택).

관련 문제