반응형

WH_MOUSE_LL 불편(?) 진실

 

Syntax

HHOOK WINAPI SetWindowsHookEx(

  __in  int idHook,

  __in  HOOKPROC lpfn,

  __in  HINSTANCE hMod,

  __in  DWORD dwThreadId

);

 

Mouse/Keyboard, Windows Message Hooking을 위한 기본 함수일 뿐 아니라,

Dll Injection용도로도 많이 쓰이는 SetWindowsHookEx 함수입니다.

아시는 바와 같이 두번째 인자인 idHook을 이용해 Hook Type을 설정하게 됩니다.

 

기계적으로 가져다 사용하던 터에 몇 가지 문제가 생겨 좀 자세히 훑어 보았습니다.

혹시 InstallHookProcedure가 어떤 Context에서 호출되는지 생각해 본적 있으신가요?

 

* 이번 포스트는 WH_MOUSE_LL에 대한 내용입니다.(WH_KEYBOARD_LL또한 유사할 것으로 짐작됩니다.)

 

MSDN을 보면 WH_MOUSE_LL 타입에 대해 다음과 같이 설명되어 있군요.

WH_MOUSE_LL

14

 

 

installs a hook procedure that monitors low-level mouse input events.

Low-Level 마우스 입력을 모니터링하는 프로시저를 설치한다.

 

For more information, see the LowLevelMouseProc hook procedure.

정보를 얻으려면 LowLevelMouseProc 보라는 군요.

한번 따라가 보겠습니다.

LowLevelMouseProc

제가 정리하고자 하는 내용은 바로 여기 Remarks 부분에 있군요.

Remarks

…<전략>

This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

…<중략>

the WH_MOUSE_LL hook is not injected into another process. Instead, the context switches back to the process that installed the hook and it is called in its original context. Then the context switches back to the application that generated the event.

The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key:

HKEY_CURRENT_USER\Control Panel\Desktop

The value is in milliseconds. If the hook procedure times out, the system passes the message to the next hook. However, on Windows 7 and later, the hook is silently removed without being called. There is no way for the application to know whether the hook is removed.

The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key 

  

* 제가 설명하려는 부분에 대해 몇 가지만 찍어 보겠습니다.

1.     WH_MOUSE_LL 타입의 훅은 훅을 설치한 Thread와 같은 Context에서 호출된다. 이 호출은 훅이 설치된 Thread Message를 전달해서 호출된다. 이런 이유로 이 타입의 훅을 설치하는 Thread는 반드시 Message-Loop를 가져야 한다.

2.     WH_MOUSE_LL 타입의 훅은 다른 프로세스에 injection되지 않으며(당근 이 타입의 훅은 Dll인젝션에 사용할 수 없겠군요.), 대신 훅을 설치한 ContextContex-Switch한 후, 해당 Context에서 HookProcedure를 호출한다.

3.     HookProcedure는 최대한 빨리 처리되어야 하며, 특정 registry에 있는 Timeout안에 리턴하지 않으면 (win7 Or later에서는)해당 Hook을 조용히 제거해 버린다.(Hook을 설치한 어플리케이션으로 제거 사실은 통지되지 않으므로, 해당 시점부터 HookProcedure가 호출되지 않겠군요.)

 

* 마지막으로 제가 직면했던 문제사항은 아래와 같더랍니다.

1.     테스트OS(Win7)

2.     프로그램 시작시 WH_MOUSE_LL타입의 Hook을 설치함.

3.     Hook이 정상적으로 설치되었고, 특정 동작을 할떄까지 HookProcedure가 잘 ~ 호출됨.(특정 동작을 한 후라는걸 찾는것도 고생을 좀 했습니다.)

4.     HookHandle도 그대로 이고 UnHook하지도 않았건만, 특정 시점부터 HookProcedure가 호출되지 않음.

5.     물론 훅프로시저 내에서 긴 시간을 지체하는 코드는 없음.

 

* What’s the problem???

1.     HookProcedure내에서는 긴시간을 소요하는 작업이 없었지만, 훅을 installThread(Main Thread)에서 1초 이상 시간이 소요되는 작업요소가 있었는데, 해당 작업을 하고 나면 문제가 발생했습니다.

2.     OS는 Mouse이벤트 발생시 내 ApplicationMainThread Context-Switch한 후, HookProcedure를 호출하려 했으나 (Maint Thread에서 처리되는)시간이 소요되는 다른 작업으로 인해 Timeout안에 해당 함수를 호출하여 리턴받지 못함(HookProcedure가 호출되는 Context가 훅을 설치한 Context와 동일하므로, OS 입장에서는 HookProcedure내에서 소요되는 시간뿐아니라 해당 Context내에서 동작하는 다른 코드가 실행되는 시간에도 영향을 받게 된다는 결론이군요.)

3.     이로 인해 내가 설치한 Hook MSDN에 나온대로 조용히~ Remove된게 아닌가 싶습니다.

4.     메뉴를 하나 만들어서 Sleep(5000)하면, 해당 메뉴 실행 후 바로 훅이 풀려버리더군요.

5.     Hook을 설치한 Thread가 시간을 많이 잡아먹는 동작을 한다면, 별도의 훅 Install Thread를 생성하는 것도 고려해야 할 듯 합니다.(위에서 기술한것처럼 훅 설치 Thread에는 메시지 루프가 있어야 한다는 것도 잊지 마시기 바랍니다.)

 

  

*참고로 WH_MOUSE TypeHookProcedure는 마우스 메시지가 발생한 윈도우의 Thread Context에서 호출 됩니다.


반응형
반응형

Driver Basic

 

 

 

n  Kernel Stack Size는 최대 12KB

n  LookAsideList
동일 LookAsideList 2회이상 initialize하면 DeadLock 발생

 

n  SpinLock
동일 Thread에서 spinlock 2회 이상 획득하면 DeadLock발생
SpinLock획득시 IRQL DISPATCH Level로 상승됨

반응형
반응형

Detour Library

 

n  MSDN Reaserch를 통해 배포되는 ApiHooking Library

n  Download Site

ü  http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx

 



반응형
반응형

Dependency Walker(depends.exe)Windows모듈의 Dependency를 보여주는 유용한 툴이다.

VisualStudio 6.0에는 기본으로 제공되었으나, 그 이후버전(정확히 어느버전인지 모른다)부터는 포함되어 있지 않다.

 

하지만 아래의 사이트에서 다운받을 수 있으니, 안심(?)해도 좋다.(64bit 포함)

 

http://www.dependencywalker.com

 

Download Version 2.2.6000 for x86 [610k]
Download Version 2.2.6000 for x64 [468k]

반응형
반응형

Win32 API에서는 이러한 정보를 구하는 방법을 제공하고 있지 않습니다. Windows Native API를 사용해야
해당 정보를 구할 수 있습니다. (ntdll.dll::ZwQueryObject(...))

백문이 불여일견.
해당 정보를 리턴하는 다음 코드를 공유합니다.
(코드가 좀 길어 보여서... "view plain"으로 보시면 수월할 겁니다.)

자료구조를 포함하는 헤더 파일입니다.
  1. #include <winternl.h>   
  2.   
  3. // ntdll::ZwQueryObject(...)의 prototype   
  4. typedef NTSTATUS (__stdcall *LPFN_ZwQueryObject)(IN HANDLE Handle, IN DWORD ObjectInformationClass, OUT PVOID ObjectInformation, IN ULONG ObjectInformationLength, OUT PULONG ReturnLength);   
  5.   
  6. // ntdll::ZwQueryObject(...)에서 쓰임   
  7. typedef struct tagPUBLIC_OBJECT_TYPE_INFORMATION   
  8. {   
  9.     UNICODE_STRING  TypeName;   
  10.     ULONG           Reserved[MAX_PATH];   
  11. } PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION;   
  12.   
  13. // ZwQueryObject를 통한 Kernel Object의 Type   
  14. typedef INT TYPE_KERNEL_OBJECT;   
  15. #define     TYPE_KERNEL_OBJECT_UNKNOWN                  0   
  16. #define     TYPE_KERNEL_OBJECT_TYPE                     1   
  17. #define     TYPE_KERNEL_OBJECT_DIRECTORY                2   
  18. #define     TYPE_KERNEL_OBJECT_SYMBOLICLINK             3   
  19. #define     TYPE_KERNEL_OBJECT_TOKEN                    4   
  20. #define     TYPE_KERNEL_OBJECT_PROCESS                  5   
  21. #define     TYPE_KERNEL_OBJECT_THREAD                   6   
  22. #define     TYPE_KERNEL_OBJECT_JOB                      7   
  23. #define     TYPE_KERNEL_OBJECT_DEBUGOBJECT              8   
  24. #define     TYPE_KERNEL_OBJECT_EVENT                    9   
  25. #define     TYPE_KERNEL_OBJECT_EVENTPAIR                10   
  26. #define     TYPE_KERNEL_OBJECT_MUTANT                   11   
  27. #define     TYPE_KERNEL_OBJECT_CALLBACK                 12   
  28. #define     TYPE_KERNEL_OBJECT_SEMAPHORE                13   
  29. #define     TYPE_KERNEL_OBJECT_TIMER                    14   
  30. #define     TYPE_KERNEL_OBJECT_PROFILE                  15   
  31. #define     TYPE_KERNEL_OBJECT_KEYEDEVENT               16   
  32. #define     TYPE_KERNEL_OBJECT_WINDOWSTATION            17   
  33. #define     TYPE_KERNEL_OBJECT_DESKTOP                  18   
  34. #define     TYPE_KERNEL_OBJECT_SECTION                  19   
  35. #define     TYPE_KERNEL_OBJECT_KEY                      20   
  36. #define     TYPE_KERNEL_OBJECT_PORT                     21   
  37. #define     TYPE_KERNEL_OBJECT_WAITABLEPORT             22   
  38. #define     TYPE_KERNEL_OBJECT_ADAPTER                  23   
  39. #define     TYPE_KERNEL_OBJECT_CONTROLLER               24   
  40. #define     TYPE_KERNEL_OBJECT_DEVICE                   25   
  41. #define     TYPE_KERNEL_OBJECT_DRIVER                   26   
  42. #define     TYPE_KERNEL_OBJECT_IOCOMPLETION             27   
  43. #define     TYPE_KERNEL_OBJECT_FILE                     28   
  44. #define     TYPE_KERNEL_OBJECT_WMIGUID                  29   
  45. #define     TYPE_KERNEL_OBJECT_FILTERCONNECTIONPORT     30   
  46. #define     TYPE_KERNEL_OBJECT_FILTERCOMMUNICATIONPORT  31   
  47. #define     TYPE_KERNEL_OBJECT_OTHER                    32  

Kernel Object Handle의 Type을 구하는 함수입니다.
  1. NTSTATUS GetHandleType(IN HANDLE hHandle, OUT TYPE_KERNEL_OBJECT *pnObject)   
  2. {   
  3.     NTSTATUS                        nRtnValue   = 0;   
  4.     HMODULE                         hModule     = NULL;   
  5.     LPFN_ZwQueryObject              pfn         = NULL;   
  6.     ULONG                           nSize       = 0;   
  7.     LPBYTE                          pBuf        = NULL;   
  8.     PPUBLIC_OBJECT_TYPE_INFORMATION pstInfo     = NULL;   
  9.     PUBLIC_OBJECT_TYPE_INFORMATION  stInfo      = {0,};   
  10.   
  11.     if (NULL == pnObject)   
  12.     {   
  13.         nRtnValue = STATUS_INVALID_PARAMETER;   
  14.         goto FINAL;   
  15.     }   
  16.   
  17.     (*pnObject) = TYPE_KERNEL_OBJECT_UNKNOWN;   
  18.   
  19.     // 만들어 놓은 아래 함수가 없다면,   
  20.     // hModule = ::LoadLibraryEx(TEXT("ntdll.dll"), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);   
  21.     // 를 사용한다.   
  22.     hModule = LoadLibraryFromSystem(TEXT("ntdll.dll"));   
  23.     if (NULL == hModule)   
  24.     {   
  25.         nRtnValue = STATUS_OBJECT_PATH_NOT_FOUND;   
  26.         goto FINAL;   
  27.     }   
  28.   
  29.     pfn = (LPFN_ZwQueryObject)::GetProcAddress(hModule, "ZwQueryObject");   
  30.     if (NULL == pfn)   
  31.     {   
  32.         nRtnValue = STATUS_PROCEDURE_NOT_FOUND;   
  33.         goto FINAL;   
  34.     }   
  35.   
  36.     pstInfo = &stInfo;   
  37.     nRtnValue = (*pfn)(hHandle, 2, pstInfo, sizeof(stInfo), &nSize);    // 2 : ObjectTypeInformation   
  38.     if (STATUS_INFO_LENGTH_MISMATCH == nRtnValue)   
  39.     {   
  40.         pBuf = new BYTE[nSize];   
  41.         if (NULL == pBuf)   
  42.         {   
  43.             nRtnValue = STATUS_NO_MEMORY;   
  44.             goto FINAL;   
  45.         }   
  46.   
  47.         ZeroMemory(pBuf, sizeof(BYTE)*nSize);   
  48.         pstInfo = (PPUBLIC_OBJECT_TYPE_INFORMATION)pBuf;   
  49.   
  50.         nRtnValue = (*pfn)(hHandle, 2, pstInfo, nSize, &nSize);         // 2 : ObjectTypeInformation   
  51.     }   
  52.   
  53.     if (STATUS_SUCCESS != nRtnValue)   
  54.     {   
  55.         goto FINAL;   
  56.     }   
  57.   
  58.     if (NULL == pstInfo->TypeName.Buffer)   
  59.     {   
  60.         nRtnValue = STATUS_INVALID_PARAMETER;   
  61.         goto FINAL;   
  62.     }   
  63.   
  64.     // 빈도 많음   
  65.     if (0 == _tcsicmp(TEXT("Key"), pstInfo->TypeName.Buffer))   
  66.         (*pnObject) = TYPE_KERNEL_OBJECT_KEY;   
  67.     else if (0 == _tcsicmp(TEXT("File"), pstInfo->TypeName.Buffer))   
  68.         (*pnObject) = TYPE_KERNEL_OBJECT_FILE;   
  69.     else if (0 == _tcsicmp(TEXT("Thread"), pstInfo->TypeName.Buffer))   
  70.         (*pnObject) = TYPE_KERNEL_OBJECT_THREAD;   
  71.     else if (0 == _tcsicmp(TEXT("Directory"), pstInfo->TypeName.Buffer))   
  72.         (*pnObject) = TYPE_KERNEL_OBJECT_DIRECTORY;   
  73.     else if (0 == _tcsicmp(TEXT("Section"), pstInfo->TypeName.Buffer))   
  74.         (*pnObject) = TYPE_KERNEL_OBJECT_SECTION;   
  75.     else if (0 == _tcsicmp(TEXT("Event"), pstInfo->TypeName.Buffer))   
  76.         (*pnObject) = TYPE_KERNEL_OBJECT_EVENT;   
  77.     else if (0 == _tcsicmp(TEXT("Mutant"), pstInfo->TypeName.Buffer))   
  78.         (*pnObject) = TYPE_KERNEL_OBJECT_MUTANT;   
  79.     else if (0 == _tcsicmp(TEXT("Port"), pstInfo->TypeName.Buffer))   
  80.         (*pnObject) = TYPE_KERNEL_OBJECT_PORT;   
  81.     // 빈도 적음   
  82.     else if (0 == _tcsicmp(TEXT("KeyedEvent"), pstInfo->TypeName.Buffer))   
  83.         (*pnObject) = TYPE_KERNEL_OBJECT_KEYEDEVENT;   
  84.     else if (0 == _tcsicmp(TEXT("Token"), pstInfo->TypeName.Buffer))   
  85.         (*pnObject) = TYPE_KERNEL_OBJECT_TOKEN;   
  86.     else if (0 == _tcsicmp(TEXT("WindowStation"), pstInfo->TypeName.Buffer))   
  87.         (*pnObject) = TYPE_KERNEL_OBJECT_WINDOWSTATION;   
  88.     else if (0 == _tcsicmp(TEXT("Type"), pstInfo->TypeName.Buffer))   
  89.         (*pnObject) = TYPE_KERNEL_OBJECT_TYPE;   
  90.     else if (0 == _tcsicmp(TEXT("SymbolicLink"), pstInfo->TypeName.Buffer))   
  91.         (*pnObject) = TYPE_KERNEL_OBJECT_SYMBOLICLINK;   
  92.     else if (0 == _tcsicmp(TEXT("Process"), pstInfo->TypeName.Buffer))   
  93.         (*pnObject) = TYPE_KERNEL_OBJECT_PROCESS;   
  94.     else if (0 == _tcsicmp(TEXT("Job"), pstInfo->TypeName.Buffer))   
  95.         (*pnObject) = TYPE_KERNEL_OBJECT_JOB;   
  96.     else if (0 == _tcsicmp(TEXT("DebugObject"), pstInfo->TypeName.Buffer))   
  97.         (*pnObject) = TYPE_KERNEL_OBJECT_DEBUGOBJECT;   
  98.     else if (0 == _tcsicmp(TEXT("EventPair"), pstInfo->TypeName.Buffer))   
  99.         (*pnObject) = TYPE_KERNEL_OBJECT_EVENTPAIR;   
  100.     else if (0 == _tcsicmp(TEXT("Callback"), pstInfo->TypeName.Buffer))   
  101.         (*pnObject) = TYPE_KERNEL_OBJECT_CALLBACK;   
  102.     else if (0 == _tcsicmp(TEXT("Semaphore"), pstInfo->TypeName.Buffer))   
  103.         (*pnObject) = TYPE_KERNEL_OBJECT_SEMAPHORE;   
  104.     else if (0 == _tcsicmp(TEXT("Timer"), pstInfo->TypeName.Buffer))   
  105.         (*pnObject) = TYPE_KERNEL_OBJECT_TIMER;   
  106.     else if (0 == _tcsicmp(TEXT("Profile"), pstInfo->TypeName.Buffer))   
  107.         (*pnObject) = TYPE_KERNEL_OBJECT_PROFILE;   
  108.     else if (0 == _tcsicmp(TEXT("Desktop"), pstInfo->TypeName.Buffer))   
  109.         (*pnObject) = TYPE_KERNEL_OBJECT_DESKTOP;   
  110.     else if (0 == _tcsicmp(TEXT("WaitablePort"), pstInfo->TypeName.Buffer))   
  111.         (*pnObject) = TYPE_KERNEL_OBJECT_WAITABLEPORT;   
  112.     else if (0 == _tcsicmp(TEXT("Adapter"), pstInfo->TypeName.Buffer))   
  113.         (*pnObject) = TYPE_KERNEL_OBJECT_ADAPTER;   
  114.     else if (0 == _tcsicmp(TEXT("Controller"), pstInfo->TypeName.Buffer))   
  115.         (*pnObject) = TYPE_KERNEL_OBJECT_CONTROLLER;   
  116.     else if (0 == _tcsicmp(TEXT("Device"), pstInfo->TypeName.Buffer))   
  117.         (*pnObject) = TYPE_KERNEL_OBJECT_DEVICE;   
  118.     else if (0 == _tcsicmp(TEXT("Driver"), pstInfo->TypeName.Buffer))   
  119.         (*pnObject) = TYPE_KERNEL_OBJECT_DRIVER;   
  120.     else if (0 == _tcsicmp(TEXT("IoCompletion"), pstInfo->TypeName.Buffer))   
  121.         (*pnObject) = TYPE_KERNEL_OBJECT_IOCOMPLETION;   
  122.     else if (0 == _tcsicmp(TEXT("WmiGuid"), pstInfo->TypeName.Buffer))   
  123.         (*pnObject) = TYPE_KERNEL_OBJECT_WMIGUID;   
  124.     else if (0 == _tcsicmp(TEXT("FilterConnectionPort"), pstInfo->TypeName.Buffer))   
  125.         (*pnObject) = TYPE_KERNEL_OBJECT_FILTERCONNECTIONPORT;   
  126.     else if (0 == _tcsicmp(TEXT("FilterCommunicationPort"), pstInfo->TypeName.Buffer))   
  127.         (*pnObject) = TYPE_KERNEL_OBJECT_FILTERCOMMUNICATIONPORT;   
  128.     else  
  129.         (*pnObject) = TYPE_KERNEL_OBJECT_OTHER;   
  130.   
  131. FINAL:   
  132.   
  133.     if (NULL != pBuf)   
  134.     {   
  135.         delete [] pBuf;   
  136.         pBuf = NULL;   
  137.     }   
  138.   
  139.     if (NULL != hModule)   
  140.     {   
  141.         ::FreeLibrary(hModule);   
  142.         hModule = NULL;   
  143.     }   
  144.   
  145.     return nRtnValue;   
  146. }  


Kernel Object Handle의 Name을 구하는 함수입니다.
  1. NTSTATUS GetHandleObjectName(IN HANDLE hHandle, OUT LPTSTR lpszName, IN DWORD dwCchName)   
  2. {   
  3.     NTSTATUS                        nRtnValue   = 0;   
  4.     HMODULE                         hModule     = NULL;   
  5.     LPFN_ZwQueryObject              pfn         = NULL;   
  6.     ULONG                           nSize       = 0;   
  7.     LPBYTE                          pBuf        = NULL;   
  8.     PPUBLIC_OBJECT_TYPE_INFORMATION pstInfo     = NULL;   
  9.     PUBLIC_OBJECT_TYPE_INFORMATION  stInfo      = {0,};   
  10.   
  11.     if (NULL == lpszName)   
  12.     {   
  13.         nRtnValue = STATUS_INVALID_PARAMETER;   
  14.         goto FINAL;   
  15.     }   
  16.   
  17.     // 만들어 놓은 아래 함수가 없다면,   
  18.     // hModule = ::LoadLibraryEx(TEXT("ntdll.dll"), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);   
  19.     // 를 사용한다.   
  20.     hModule = LoadLibraryFromSystem(TEXT("ntdll.dll"));   
  21.     if (NULL == hModule)   
  22.     {   
  23.         nRtnValue = STATUS_OBJECT_PATH_NOT_FOUND;   
  24.         goto FINAL;   
  25.     }   
  26.   
  27.     pfn = (LPFN_ZwQueryObject)::GetProcAddress(hModule, "ZwQueryObject");   
  28.     if (NULL == pfn)   
  29.     {   
  30.         nRtnValue = STATUS_PROCEDURE_NOT_FOUND;   
  31.         goto FINAL;   
  32.     }   
  33.   
  34.     pstInfo = &stInfo;   
  35.     nRtnValue = (*pfn)(hHandle, 1, pstInfo, sizeof(stInfo), &nSize);    // 1 : ObjectNameInformation   
  36.     if (STATUS_INFO_LENGTH_MISMATCH == nRtnValue)   
  37.     {   
  38.         pBuf = new BYTE[nSize];   
  39.         if (NULL == pBuf)   
  40.         {   
  41.             nRtnValue = STATUS_NO_MEMORY;   
  42.             goto FINAL;   
  43.         }   
  44.   
  45.         ZeroMemory(pBuf, sizeof(BYTE)*nSize);   
  46.         pstInfo = (PPUBLIC_OBJECT_TYPE_INFORMATION)pBuf;   
  47.   
  48.         nRtnValue = (*pfn)(hHandle, 1, pstInfo, nSize, &nSize);         // 1 : ObjectNameInformation   
  49.     }   
  50.   
  51.     if (STATUS_SUCCESS == nRtnValue)   
  52.     {   
  53.         StringCchCopy(lpszName, dwCchName, pstInfo->TypeName.Buffer);   
  54.     }   
  55.   
  56. FINAL:   
  57.   
  58.     if (NULL != pBuf)   
  59.     {   
  60.         delete [] pBuf;   
  61.         pBuf = NULL;   
  62.     }   
  63.   
  64.     if (NULL != hModule)   
  65.     {   
  66.         ::FreeLibrary(hModule);   
  67.         hModule = NULL;   
  68.     }   
  69.   
  70.     return nRtnValue;   
  71. }  


반응형
반응형

COM 응용 기술

n  Automation

ü  IDispatch 인터페이스를 통해 COM을 제어 

n  OLE Document

ü  IOleContainer, IOleObject 등의 인터페이스를 데이터를 공유하는 방법

n  OLE Clipboard

ü  IDataObject 인터페이스와 Clipboard를 통해 데이터를 주고 받는 방법 

n  Drag & Drop

ü  IDropTarget, IDropSource 등의 인터페이스를 이용하여, 데이터를 삽입하는 방법
놓고 전송하는 기술.
 

n  ActiveX Control

ü  일확장자가 OCX이며 OLE Control이라고도 부름.
일반적인 컨트롤처럼 사용될 수도 있으며, 웹페이지에도 삽입가능함.

 

 

반응형

+ Recent posts