1. Active Accessibility?
이번 강좌는 아마도 델파이 사용자들에게는
좀 생소한 항목이 될것 같네요. 왜냐면 볼랜드에서 이 주제와 관련한
dll을 델파이에서 사용할 수 있도록 유닛화 해놓지 않았기 때문입니다.
이 Active Accessiblity는 OleAcc.DLL 이란 라이브러리에서 지원이 되며,
Win98 이상의 OS에서만 사용이 가능합니다. MSDN을 살펴보면 이 기능이
Win95 에서는 지원이 안되는 관계로 라이브러리내의 API를 이용할때는
동적 로딩을 사용하라고 충고하고 있습니다. 정적 로딩을 사용할 경우
Win95에서는 Active Accessiblity 관련 기능만 못쓰는게 아니라 프로그램이
아예 실행도 되지 못할테니까 말이죠. 이 유닛 파일은 VC의 헤더 화일과
델파이의 임포트 결과를 참고해서 제가 만들었는데, 이 강좌에서는 그냥
정적 로딩으로 진행합니다.
컴퓨터 상에 동작중인 다른 프로그램을
제어하는 일은 재미있지만 쉬운일이 아니죠. 대상 프로그램이 OLE를
지원해 준다면 (IE 또는 MediaPlayer 처럼) 그나마 수월하게 제어가
가능하겠지만, OLE를 지원하지는 않는 일반 어플리케이션일 경우는 어떻게들
할까요? 보통은 메세지를 이용하죠. 원하는 컨트롤에 메세지를 날리는
방식인데요, 이처럼 내 프로그램이 아닌 다른 영역에 접근 하는것을
Accessiblity 라고 합니다.
오해를 하실까봐 미리 말씀드리지만
이 기능을 이용한다 하더라도 외부의 컨트롤을 직접 제어하는 것은 불가능
합니다. 다만 해당 컨트롤의 핸들을 알아오거나 하는 것이 고작입니다.
2. 무엇을 할 수 있나?
윈도우 표준 컨트롤들은 대부분(모두가
아닌 대부분입니다.) IAccessible 이라는 인터페이스를 지원합니다.
정확히 말한다면 지원하는 게 아니라 저 인터페이스에 바인딩 되어 있다고
하는게 맞겠네요. 저 인터페이스는 자체에 내장된 인터페이스가 아니라
필요에따라 외부에서 생성됩니다. IAccessible을 통하여 어떤 창을 뒤져볼때
알아 낼수 있는 정보는 창위에 어떤 컨트롤들이 있는지 따위의 정보와
컨트롤 속의 자식 컨트롤 등을 뒤져 볼 수 있고, 핸들을 가진 컨트롤이라면
그 핸들을 알아 낼 수 있는 정도 입니다. 그외 컨트롤의 조작은 이를
통해 알아낸 핸들을 통해 메세지를 날리는 방법밖에 없습니다. IAccessible
인터페이스는 멤버함수들의 이름만으로도 기능을 짐작할 수 있을 정도로
쉬운 인터페이스입니다.
3. 예제
먼저 IAccessible에 좀 더 친숙해
지기 위해 간단한 예제를 보죠.
procedure TForm1.Button1Click(Sender: TObject);
var
H : HWND;
ACC : IAccessible;
ENM : IEnumVariant;
I,K : Integer;
Fetched : DWORD;
Child,Role,State : OleVariant;
Buf, RoleText, StateText: String;
StateInt : DWORD;
cBuf : Array[0..256] of Char;
begin
Memo1.Clear;
H := FindWindow(nil, '제목 없음 - 메모장');
if H=0 then Exit;
if FAILED(AccessibleObjectFromWindow(H, OBJID_WINDOW, IAccessible, ACC)) or
FAILED(ACC.QueryInterface(IEnumVariant, ENM))
then
Exit;
ENM.Reset;
if ACC.accChildCount>0 then for I:=0 to ACC.accChildCount-1 do begin
Child := NULL;
if Assigned(ENM) then ENM.Next(1, Child, Fetched);
if Child = NULL then begin
TVarData(Child).VType := varInteger;
TVarData(Child).VInteger := I;
end;
Role := ACC.accRole[Child];
GetRoleText(TVarData(Role).vInteger, cBuf, 256);
RoleText := cBuf;
State := ACC.accState[Child];
StateInt := TVarData(State).vInteger;
StateText := '';
K := 1;
while K<>0 do begin
if (K and StateInt)<>0 then begin
GetStateText(K, cBuf, 256);
StateText := StateText + cBuf + ',';
end;
K := K Shl 1;
end;
Buf := Format('%2d : %s'#13#10 +
' - Desc : %s'#13#10 +
' - Role : %s'#13#10 +
' - State : %s'#13#10,
[I+1, ACC.accName[Child],
ACC.accDescription[Child],
RoleText,
StateText
]);
Memo1.Lines.Add(Buf);
end;
end;
먼저 "메모장"을 실행시키고
버튼을 눌러보면 결과가 나오는데, 설명을 하자면 이렇습니다.
1. FindWindow로 메모장의 핸들을 얻습니다.
2. 얻어진 핸들로부터 IAccessible 인터페이스를 뽑아냅니다.
3. 얻어진 IAccessible을 뒤져봅니다.
이때 원래는 재귀호출을 사용해 자식의 자식까지 뒤져봐야 하겠지만 여기서는 간단히
바로 아래 자식만 찾아봅니다.(자식 자식 하니깐 꼭 욕하는것 같죠? ^^;)
결과부터 확인하면 이해가 더 쉬울것 같네요.
1 : 시스템메뉴
- Desc : 창을 조작하는 명령이 포함되어 있습니다.
- Role : 메뉴 모음
- State :
2 :
- Desc : 창 이름을 표시하고 조작할 수 있는 컨트롤을 포함합니다.
- Role : 제목 표시줄
- State : 포커스 가능,
3 : 응용 프로그램 메뉴
- Desc : 현재 보기나 문서를 조작하는 명령이 포함되어 있습니다.
- Role : 메뉴 모음
- State :
4 : 제목 없음 - 메모장
- Desc :
- Role : 클라이언트
- State :
5 : 세로 스크롤 막대
- Desc : 수직 보기 영역을 바꿉니다.
- Role : 스크롤 막대
- State : 보이지 않음,
6 : 가로 스크롤 막대
- Desc : 수평 보기 영역을 바꿉니다.
- Role : 스크롤 막대
- State : 보이지 않음,
7 : 크기 조절 상자
- Desc : 창 너비와 높이를 조정할 수 있습니다.
- Role : 크기 조절
- State : 보이지 않음,
여기에 사용된 관련 API를 잠깐 짚고
넘어가죠.
function WindowFromAccessibleObject(Acc : IAccessible; var H : HWND) : HRESULT; stdcall;
function AccessibleObjectFromWindow(H: HWND; dwID : DWORD; const riid : TIID;
out vObject) : HRESULT; stdcall;
AccessibleObjectFromWindow 함수는 핸들로부터 IAccessible 인터페이스로
뽑아내는 기능을 합니다. WindowFromAccessibleObject 함수는 그 반대 겠죠.
여기서 dwID에는 OBJID_WINDOW,OBJID_SYSMENU,OBJID_TITLEBAR,OBJID_CLIENT
등을 넣으면 되는데 델파이에서 ctrl+Click 해 보시면 더 나옵니다.
이름을 보고 판단해서 원하는걸 넣으면 됩니다.
인터페이스들 중에 IEnum... 으로 시작하는 IEnumUnknown = interface(IUnknown) Next 메소드는 다음 데이터를 가져오라는 여기에 사용된건 IEnumVariant 인터페이스 |
이번에는 메모장에 특정 문자열들을
집어 넣는 프로그램을 만들어 보도록 하겠습니다. 이건 WM_SETTEXT
메세지를 이용하면 간단한 문제죠. 하지만 이 메세지를 사용하기 위해서는
메시지를 받을 핸들을 먼저 알아야 합니다.이 핸들을 찾아내는데, IAccessible
이 사용됩니다.
여기서 Role 이라는게 있는데, 이 Role은 해당 컨트롤의
속성을 담고 있습니다. 롤플레잉게임 할때 그 "롤"입니다. 배역,
역할 등의 의미가 있는데(왠 영어공부 --;), ROLE_SYSTEM_TITLEBAR
이면 타이틀바, ROLE_SYSTEM_COMBOBOX 면 콤보박스를 가리키는 겁니다.
메모나 에디터 컨트롤은 ROLE_SYSTEM_TEXT라는 Role을 가지게 됩니다.
그러면 IAccessible과 그자식들을 뒤지면서 저 Role을 찾으면 되겠군요.
에디터 박스가 두개 이상일 경우 등의 복잡한 조건하에서는 몇번째 해당
Role 컨트롤인지를 찾아야 하겠죠. 여기서는 자식의 자식까지 뒤져야하니까
재귀호출이 사용됩니다.
function TForm1.FindChildAccessibleWithRole(ACC : IAccessible; Role : Integer) : IAccessible;
var
I : Integer;
V : Variant;
ChildACC : IAccessible;
ChildVar : OleVariant;
ENM : IEnumVariant;
Fetched : DWORD;
begin
Result := nil;
V := ACC.accRole[CHILDID_SELF];
if TVarData(V).VInteger = Role then begin
Result := ACC;
Exit;
end;
if FAILED(ACC.QueryInterface(IEnumVariant, ENM)) then Exit;
ENM.Reset;
if ACC.accChildCount>0 then for I:=0 to ACC.accChildCount - 1 do begin
if FAILED(ENM.Next(1, ChildVar, Fetched)) or
VarIsNULL(ChildVar) then Exit;
try
if TVarData(ChildVar).VType=varDispatch then begin
if FAILED(IDispatch(TVarData(ChildVar).VDispatch).QueryInterface(IAccessible, ChildACC)) then Continue;
end
else if FAILED(ACC.accChild[ChildVar].QueryInterface(IAccessible, ChildACC)) or
(ACC = ChildACC) then Continue;
Result := FindChildAccessibleWithRole(ChildACC, Role);
if Assigned(Result) then Exit;
except
Exit;
end;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
H, ChildHandle : HWND;
ChildACC, ACC : IAccessible;
cBuf : PChar;
begin
H := FindWindow(nil, '제목 없음 - 메모장');
if H=0 then Exit;
if FAILED(AccessibleObjectFromWindow(H, OBJID_WINDOW, IAccessible, ACC))
then Exit;
ChildACC := FindChildAccessibleWithRole(ACC, ROLE_SYSTEM_TEXT);
if not Assigned(ChildACC) then Exit;
if FAILED(WindowFromAccessibleObject(ChildACC, ChildHandle)) then Exit;
GetMem(cBuf, Length(Memo1.Lines.Text)+1);
StrPCopy(cBuf, Memo1.Lines.Text);
SendMessage(ChildHandle, WM_SETTEXT, 0, LParam(cBuf));
FreeMem(cBuf);
end;
소스만 던져두고 자세한 설명이 없는것 같아 좀 그렇지만, 사실 별로 설명할
것도 없습니다. 크게 어려운 것도 없죠.
MSDN에는 이런 예제도 포함되어 있더군요.
SetWindowEventHook으로 윈도우 이벤트를 감시하면서 원하는 이벤트가
잡히면 AccessibleObjectFromEvent
함수로 IAccessible 인터페이스를 얻어내서 할 일을 하는 예제인데요,
별로 쓸모는 없겠더라구요
'COM, ATL' 카테고리의 다른 글
COM, ATL에서의 reinterpret_cast 의 쓰임새 (0) | 2008.09.11 |
---|---|
모니카 레퍼런스 - MSDN 영문 (2) | 2008.09.10 |
모니커와 MSHTML을 이용한 HTML 파싱 (2) | 2008.09.10 |
ROT 와 모니커(Moniker) - 개념 및 델파이 코딩 (0) | 2008.09.10 |
엑셀 오토메이션 (1) | 2008.09.03 |
ATL_NO_VTABLE (2) | 2008.08.30 |
BSTR 자료형 고찰과 사용시 주의점 (2) | 2008.08.29 |