Automation (자동화)

C++ 2010. 11. 2. 13:32


Automation (자동화)

script(JScript, VBScript, ..)언어에서도 사용할수 있는 component.
IDispatch 를 구현하여 사용. 약간의 성능 저하.
MFC 에선 CCmdTarget(서버측), COleDispatchDriver(클라이언트측) 클래스를 상속하여 구현.
서버 종류는 In-process 서버(DLL 타입)와 Out-of-process 서버(EXE 타입) 존재.
(사용자 인터페이스를 갖어야 하는 경우 EXE 타입 사용해야 함.)



차  례

필요한 개념
IDispatch
VARIANT 구조체
BSTR 데이터 타입
SAFEARRAY 데이터 타입
타입 라이브러리(.tlb 파일)

In-process 서버 (DLL 타입)
automation 서버 작성
automation 클라이언트 작성
**. VBScript 에서 호출
automation 서버 샘플
automation 클라이언트 샘플
Out-of-process 서버 (EXE 타입)
automation 서버 작성
automation 클라이언트 작성
**. VBScript 에서 호출
automation 서버 샘플
automation 클라이언트 샘플

서버를 하나만 띄우는 방법





필요한 개념

IDispatch
IUnknown을 상속한 인터페이스.
IUnknown의 메소드 AddRef(), Release(), QueryInterface() 에 
추가로 Invoke(), GetIDsOfNames() 를 구현해야 함.
Invoke() : automation 메소드/프로퍼티가 갖는 DISPID를 통해 접근하기 위한 메소드
GetIDsOfNames() : automation 메소드/프로퍼티가 갖고 있는 DISPID 조회용 메소드
VARIANT 구조체
automation 메소드에서 사용되는 매개변수로 모든 타입을 union으로 보유함.
ex)
VARIANT val;
val.vt = VT_I4; //type 지정
val.lval = 7; //값   지정

MFC 에선 COleVariant 클래스로 핸들링 가능.
ex)
long lval = 7;
COleVariant var(lval);
VARIANT *pVar = (LPVARIANT)var; 

BSTR 데이터 타입
"문자 개수(32bit) + NULL 종료 unicode 문자열"의 구조를 갖음.
ex) 
CString -> BSTR 변환
CString str = _T("hi");
BSTR bstr = str.AllocSysString();
BSTR -> CString 변환
CString str = (LPCWSTR)bstr; 

SAFEARRAY 데이터 타입
VARIANT 구조체에 배열 저장을 위한 타입.
MFC 에선 COleSafeArray 클래스로 핸들링 가능.
ex)
long lval[] = {1,2,3};
COleSafeArray sa;
sa.CreateOneDim(VT_I4, lval);
VARIANT *pVariant = (LPVARIANT)sa; 

타입 라이브러리(.tlb 파일)
automation 서버의 정보 제공 방법.
IDL(Interface Definition Language)로 정보가 기술된 .idl 파일 생성후 
MIDL 컴파일러로 타입 라이브러리 생성.




In-process 서버 (DLL 타입)

automation 서버 작성
1. "MFC > MFC DLL" project 생성 및 "Applicaton Settings > Automation" 체크
레지스트리 등록/해제(InitInstance()/DllRegisterServer()) 및 
DllCanUnloadNow(), DllGetClassObject() 코드 자동 생성됨.
2. CCmdTarget 파생 클래스 생성
"Class View 에서 MFC > MFC class" 로 생성하며 
"Base class : CCmdTarget", "Automation : Creatable by type ID" 선택
"Type ID" 는 ProgID 라고 불리며 Client가 서버에 접속할 때 사용되는 ID. (ex. AutomationDllServer.Calculator)
(CLSIDFromProgID(), ProgIDFromCLSID()를 통해 ProgID <-> CLSID 가능.)
**. 자동 생성 코드 EnableAutomation() : CCmdTarget 클래스의 automation 지원 기능을 활성화 AfxOleLockApp() : reference count 증가 AfxOleUnlockApp() : reference count 감소 OnFinalRelease() : reference count가 0일때 호출되는 함수. (메모리 해제 등의 작업이 필요할때 이곳에 코드 작성) BEGIN_INTERFACE_MAP() : 자동화 메소드가 있는 클래스 지정 및 IDispatch 구현을 위한 IID 지정
3. 사용자 정의 함수 추가.
"Class View > 프로젝트명 > I+CCmdTarget 파생클래스 이름 > 오른쪽 마우스 메뉴 > Add > Add Method..."
ex) LONG Add(LONG a, LONG b) 추가
.idl와 .cpp(CCmdTarget 파생 클래스)에 해당 메소드 및 BEGIN_DISPATCH_MAP 추가됨.
**. Add Method 시에 에러가 발생한다면.. http://www.ikpil.com/1133 (에러 메세지는 다르게 나올 수 있다.)  
4. 사용자 정의 함수 구현
자동 생성된 메소드에 실제 코드를 구현한다.
5. 컴파일 및 regsvr32로 dll 등록 (혹은 Post-Build Event 활용)
c:\> regsvr32 XXXXX.dll

automation 클라이언트 작성
1. 일반 MFC application project 생성

2. AfxOleInit(); 코드 추가

3. COleDispatchDriver 파생 클래스 생성
"Class View 에서 MFC > MFC Class From TypeLib" 로 생성하며
"Add class from: File" 선택후 automation 서버 프로그램에서 생성된 .tlb 파일 지정.
"Intefaces: " 목록에 나타난 인터페이스 선택하여 "Generated classes"로 옮긴 후 Finish.

4. 파생 클래스 사용
생성된 파생 클래스 헤더파일 include.
파생 클래스의 CreateDispatch(ProgID) 호출. (ProgID는 서버 작성 2단계에서 지정한 Type ID)
파생 클래스에서 사용자 정의 함수 호출.
ex) #include "CCalculator.h" .. AfxOleInit(); //한번만 실행되도록 생성자로 이동 필요. CCalculator cls; BOOL bRet = cls.CreateDispatch(_T("AutomationDllServer.Calculator")); //(ProgID는 서버 작성 2단계에서 지정한 Type ID) if (!bRet) { AfxMessageBox(_T("CreateDispatch error")); } LONG ret = cls.Add(1, 2); CString str; str.Format(_T("ret = %x"), ret); AfxMessageBox(str); 
**. VBScript 에서 호출
ex) AutomationDllClient.vbs 파일 Set calc = CreateObject("AutomationDllServer.Calculator") ret = calc.add(1, 2) MsgBox("ret = " + CStr(ret)) Set calc = Nothing 
automation 서버 샘플

automation 클라이언트 샘플



Out-of-process 서버 (EXE 타입)

automation 서버 작성
1. 일반 MFC application project 생성
"Advanced Features" 에서 Automation 체크
(선택사항 "Single document" 로 생성함)
ex) project명 : AutomationEXEServer AutomationEXEServer.h 에 COleTemplateServer m_server; 자동 생성. AutomationEXEServer.cpp 에 CLSID, AfxOleInit(), m_server.ConnectTemplate(clsid, pDocTemplate, TRUE); 자동 생성. AutomationEXEServerDoc.cpp 에 BEGIN_DISPATCH_MAP(), EnableAutomation(), AfxOleLockApp(), AfxOleUnlockApp() 자동 생성. CAutomationEXEServerDoc는 CDocument 를 상속 하고 CDocument는 CCmdTarget를 상속함. 

2. 사용자 정의 함수 추가.
"Class View > AutomationEXEServer > IAutomationEXEServer > 오른쪽 마우스 메뉴 > Add > Add Method..."
ex)
창을 보여주는 함수 추가
void CAutomationEXEServerDoc::ShowWindow(void)
{
AFX_MANAGE_STATE(AfxGetAppModuleState());
// TODO: Add your dispatch handler code here
AfxGetMainWnd()->ShowWindow(SW_SHOW);
 

.idl와 .cpp(CCmdTarget 파생 클래스)에 해당 메소드 및 BEGIN_DISPATCH_MAP 추가됨.
**. Add Method 시에 에러가 발생한다면.. http://www.ikpil.com/1133 (에러 메세지는 다르게 나올 수 있다.)

3. 프로퍼티 추가 - Member variable
"Class View > AutomationEXEServer > IAutomationEXEServer > 오른쪽 마우스 메뉴 > Add > Add Property..."
Implementation type 에서 Member variable 선택.
Property type : 생성할 변수 타입
Property name : 생성할 프로퍼티 이름
Variable name : 실제 변수명(Property name에 따라 자동 입력 됨)
Notification function : 생성하는 프로퍼티 값이 변경되는 경우 호출될 함수
ex) Property type : BSTR Property name : str Variable name : m_str Notification function : OnstrChanged
AutomationEXEServerDoc.cpp/.h/.idl 에 변수 및 Notification function 자동 생성됨.
OnStrChanged() 에서 값 변경시 메세지 박스 출력 코드 삽입
ex) void CAutomationEXEServerDoc::OnstrChanged(void) { AFX_MANAGE_STATE(AfxGetAppModuleState()); // TODO: Add your property handler code here AfxMessageBox(m_str); SetModifiedFlag(); } 
4. 프로퍼티 추가 - Get/Set methods
"Class View > AutomationEXEServer > IAutomationEXEServer > 오른쪽 마우스 메뉴 > Add > Add Property..."
Implementation type 에서 Get/Set methods 선택.
Property type : 생성할 변수 타입
Property name : 생성할 프로퍼티 이름
Get function  : Get 메소드 명
Set function  : Set 메소드 명
ex) Property type : BSTR Property name : str2 Get function : Getstr2 Set function : Setstr2 
AutomationEXEServerDoc.cpp/.h/.idl 에 Get/Set 메소드 자동 생성됨. (멤버 변수는 생성되지 않음)
OnStrChanged() 에서 값 변경시 메세지 박스 출력 코드 삽입
ex) BSTR CAutomationEXEServerDoc::Getstr2(void) { AFX_MANAGE_STATE(AfxGetAppModuleState()); // TODO: Add your dispatch handler code here return m_str.AllocSysString(); } void CAutomationEXEServerDoc::Setstr2(LPCTSTR newVal) { AFX_MANAGE_STATE(AfxGetAppModuleState()); // TODO: Add your property handler code here m_str = (LPCWSTR)newVal; SetModifiedFlag(); } 
5. 컴파일

automation 클라이언트 작성
1. 일반 MFC application project 생성
ex) 프로젝트 명 : AutomationEXEClient
2. AfxOleInit(); 코드 추가
3. COleDispatchDriver 파생 클래스 생성
"Class View 에서 MFC > MFC Class From TypeLib" 로 생성하며
"Add class from: File" 선택후 automation 서버 프로그램에서 생성된 .tlb 파일 지정.
"Intefaces: " 목록에 나타난 인터페이스 선택하여 "Generated classes"로 옮긴 후 Finish.
4. 파생 클래스 사용
생성된 파생 클래스 헤더파일 include.
파생 클래스의 CreateDispatch(ProgID) 호출. (ProgID는 서버 작성 2단계에서 지정한 Type ID)
파생 클래스에서 사용자 정의 함수 호출.
ex) #include "CAutomationEXEServer.h" .. AfxOleInit(); //한번만 실행되도록 생성자로 이동 필요. CAutomationEXEServer cls; BOOL bRet = cls.CreateDispatch(_T("AutomationEXEServer.Document")); //"프로젝트명.Document" 로 호출. AutomationEXEServer.reg 파일 참조. if (!bRet) { AfxMessageBox(_T("CreateDispatch error")); } CString getstr = cls.Getstr(); AfxMessageBox(getstr); cls.ShowWindow(); CString setstr = _T("hi man~~"); cls.Setstr(setstr); cls.ShowWindow(); 
**. VBScript 에서 호출
ex) AutomationEXEClient.vbs 파일 Set test = CreateObject("AutomationEXEServer.Document") test.str = "hi vbscript~" test.ShowWindow() MsgBox(test.str) Set test = Nothing 
automation 서버 샘플

automation 클라이언트 샘플



서버를 하나만 띄우는 방법
서버 수정
1. Doc 클래스에 DWORD m_dwID; 멤버 변수 추가 2. extern CLSID clsid; 추가 3. Doc 생성자에 등록기능 추가 CAutomationEXEServerDoc::CAutomationEXEServerDoc() { IUnknown *pUnknown; GetIDispatch(FALSE)->QueryInterface(IID_IUnknown, (void**)&pUnknown); GetIDispatch(FALSE)->Release(); ::RegisterActiveObject(pUnknown, clsid, ACTIVEOBJECT_WEAK, &m_dwID); } 4. Doc OnFinalRelease()에 등록 해제 기능 추가 void CAutomationEXEServerDoc::OnFinalrelease(void) { ::revokeActiveObject(m_dwID, NULL); CDocument::OnFinalRelease(); } 
클라이언트 서버 연결부분 수정
BOOL bSuccess = FALSE; CLSID clsid; if (SUCCESSED(CLSIDFromProgID(OLESTR("XXXXX.XXXXX"), &clsid))) { IUnknown *pUnknown; if (SUCCESSED(::GetActiveObject(clsid, NULL, &pUnknown))) //현재 실행중인 서버 확인 { IDispatch *pDispatch; if (SUCCESSED(pUnknown->QueryInterface(IID_IDispatch, (void**)&pDispatch))) { pDispatch->Release(); m_server.AttachDispatch(pDispatch); bSuccess = TRUE; } } else { if (m_server.CreateDispatch(_T("XXXXX.XXXXX"))) bSuccess = TRUE; } } if (!bSuccess) { AfxMessageBox("connect to server error"); } //서버 호출 작업~~