-
자동화 컨트롤러(클라이언트)ActiveX/Automation 2008. 12. 17. 00:25* VBScript(CalcClient.vbs)
Set Calc = CreateObject("CalcServer.Calculator")
Sum = Calc.Add(5, 3)
MsgBox("5+3 = " + CStr(Sum))
Set Calc = Nothing* VBScript(CalcClient.vbs) - 다중개체
Set Clock = CreateObject("ClockServer.Application")
' 시침 속성 설정
Clock.Appearance.Tickness(0) = 5
Clock.Appearance.Length(0) = 35
Clock.Appearance.Color(0) = RGB(0, 0, 255)
...
Clock.ShowWindow()
Clock.UpdateWindow()* VB
컬렉션* C++
Private oCompany As Company
Private oEmployees As IEmployees
Private oEmployee As IEmployee
Private Sub Form_Load()
Set oCompany = New Company
Set oEmployees = oCompany.Employees
End Sub
Private Sub Form_Unload(Cancel As Integer)
Set oEmployee = Nothing
Set oEmployees = Nothing
Set oCompany = Nothing
End Sub
Private Sub cmdCount_Click()
For i = 1 To oEmployees.Count
Set oEmployee = oEmployees(i)
MsgBox "이름 : " & oEmployee.Name & vbCrLf & _
"주소 : " & oEmployee.Address & vbCrLf & _
"전화번호 : " & oEmployee.Telephone
Next i
End Sub
Private Sub cmdEnum_Click()
For Each oEmployee In oEmployees
MsgBox "이름 : " & oEmployee.Name & vbCrLf & _
"주소 : " & oEmployee.Address & vbCrLf & _
"전화번호 : " & oEmployee.Telephone
Next
End Sub
STDMETHODIMP CallMethod(LPDISPATCH pDisp, LPOLESTR pszName, VARIANT* pvResult, UINT cArgs, VARIANTARG* rgVarParams)
{
if (NULL == pDisp) return E_POINTER;
DISPID dwDispID;
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
dispparams.rgvarg = rgVarParams;
dispparams.cArgs = cArgs;
HRESULT hr = pDisp->GetIDsOfNames(IID_NULL, &pszName, 1, LOCALE_USER_DEFAULT, &dwDispID);
if(SUCCEEDED(hr)) {
hr = pDisp->Invoke(dwDispID, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &dispparams, pvResult, NULL, NULL);
if (FAILED(hr)) {
hr = pDisp->Invoke(dwDispID, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dispparams, pvResult, NULL, NULL);
}
}
return hr;
}
class CButtonHandler : public IDispatch
{
public:
CButtonHandler(): m_cRef(0), m_pConnection(NULL){}
~CButtonHandler(void){FreeEvents();}
// IUnknown Implementation
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IDispatch Implementation
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo);
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo);
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId);
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr);
void SinkEvents(LPDISPATCH pButton);
void FreeEvents();
private:
ULONG m_cRef; //Reference count
IConnectionPoint* m_pConnection;
DWORD m_dwCookie;
};
// Grab our button to sink events...
vtParam.vt = VT_BSTR; vtParam.bstrVal = ::SysAllocString(L"My Button");
hr = CallMethod(vtCtrls.pdispVal, L"Item", &vtButton, 1, &vtParam);
VariantClear(&vtParam);
// If we can't find it, make a new button...
if (FAILED(hr)) {
vtParam.vt = VT_I4; vtParam.lVal = 1;
hr = CallMethod(vtCtrls.pdispVal, L"Add", &vtButton, 1, &vtParam);
if (FAILED(hr)) goto cleanup;
vtParam.vt = VT_BSTR; vtParam.bstrVal = ::SysAllocString(L"My Button2");
hr = PutProperty(vtButton.pdispVal, L"Caption", &vtParam);
VariantClear(&vtParam);
if (FAILED(hr)) goto cleanup;
vtParam.vt = VT_I4; vtParam.lVal = 2;
hr = PutProperty(vtButton.pdispVal, L"Style", &vtParam);
if (FAILED(hr)) goto cleanup;
vtParam.vt = VT_I4; vtParam.lVal = 1;
hr = PutProperty(vtButton.pdispVal, L"Visible", &vtParam);
if (FAILED(hr)) goto cleanup;
}
// Now, set up an event sink for the Button...
m_pButtonHandler = new CButtonHandler();
if (NULL == m_pButtonHandler) goto cleanup;
m_pButtonHandler->AddRef();
m_pButtonHandler->SinkEvents(vtButton.pdispVal);// 스마트 포인터
BOOL CCalCntApp::InitInstance()
{
// {
AfxOleInit();
// }
AfxEnableControlContainer();
...
}
#include <comdef.h>
//#import "../CalSrv/CalSrv.tlb" no_namespace
class CCalCntDlg : public CDialog
{
...
// Generated message map functions
//{{AFX_MSG(CCalCntDlg)
...
afx_msg void OnSum();
...
afx_msg void OnDiv();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
void CreateServer();
DISPID m_dispidSum;
...
DISPID m_dispidDiv;
DISPID m_dispidVar;
IDispatchPtr m_pDisp;
};
CCalCntDlg::CCalCntDlg(CWnd* pParent /*=NULL*/)
: CDialog(CCalCntDlg::IDD, pParent)
{
...
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_dispidSum = 0;
...
m_dispidDiv = 0;
m_dispidVar = 0;
}
BEGIN_MESSAGE_MAP(CCalCntDlg, CDialog)
//{{AFX_MSG_MAP(CCalCntDlg)
...
ON_BN_CLICKED(IDC_SUM, OnSum)
...
ON_BN_CLICKED(IDC_DIV, OnDiv)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
...
void CCalCntDlg::OnInitDialog()
{
...
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// {
CreateServer();
// }
return TRUE; // return TRUE unless you set the focus to a control
}
...
void CCalCntDlg::CreateServer()
{
OLECHAR* szName;
HRESULT hr = m_pDisp.CreateInstance("CalSrv.SimpleCalc");
if(FAILED(hr))
AfxMessageBox(_T("서버 연결에 실패하였습니다."));
// m_pDisp = IDispatchPtr(__uuidof(SimpleCalc));
szName = L"xyVar";
m_pDisp->GetIDsOfNames(IID_NULL,&szName,1,LOCALE_SYSTEM_DEFAULT,&m_dispidVar);
szName = L"Sum";
m_pDisp->GetIDsOfNames(IID_NULL,&szName,1,LOCALE_SYSTEM_DEFAULT,&m_dispidSum);
...
szName = L"Div";
m_pDisp->GetIDsOfNames(IID_NULL,&szName,1,LOCALE_SYSTEM_DEFAULT,&m_dispidDiv);
}
void CCalCntDlg::OnSum()
{
// TODO: Add your control notification handler code here
DISPPARAMS dispparams;// = {NULL, NULL, 0, 0};
DISPID mydispid = DISPID_PROPERTYPUT;
memset(&dispparams, 0, sizeof(dispparams));
dispparams.cArgs = 2;
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
dispparams.rgvarg[0].vt = VT_I2;
dispparams.rgvarg[0].iVal = GetDlgItemInt(IDC_YTERM);
dispparams.rgvarg[1].vt = VT_I2;
dispparams.rgvarg[1].iVal = GetDlgItemInt(IDC_XTERM);
dispparams.rgdispidNamedArgs = &mydispid;
dispparams.cNamedArgs = 1;
HRESULT hr = m_pDisp->Invoke(
m_dispidVar,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
delete [] dispparams.rgvarg;
if (FAILED(hr))
{
MessageBox("값을 전달할 수 없습니다.");
}
memset(&dispparams, 0, sizeof(dispparams));
dispparams.rgvarg = NULL;
dispparams.cArgs = 0;
dispparams.rgdispidNamedArgs = NULL;
dispparams.cNamedArgs = 0;
VARIANT varRetVal;
VariantInit(&varRetVal);
m_pDisp->Invoke(m_dispidSum,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET,
&dispparams,
&varRetVal,
NULL,
NULL);
SetDlgItemInt(IDC_RESULT, varRetVal.iVal);
}
...
void CCalCntDlg::OnDiv()
{
// TODO: Add your control notification handler code here
DISPPARAMS dispparams;// = {NULL, NULL, 0, 0};
DISPID mydispid = DISPID_PROPERTYPUT;
memset(&dispparams, 0, sizeof(dispparams));
dispparams.cArgs = 2;
dispparams.rgvarg = new VARIANT[dispparams.cArgs];
dispparams.rgvarg[0].vt = VT_I2;
dispparams.rgvarg[0].iVal = GetDlgItemInt(IDC_YTERM);
dispparams.rgvarg[1].vt = VT_I2;
dispparams.rgvarg[1].iVal = GetDlgItemInt(IDC_XTERM);
dispparams.rgdispidNamedArgs = &mydispid;
dispparams.cNamedArgs = 1;
HRESULT hr = m_pDisp->Invoke(
m_dispidVar,
IID_NULL,
LOCALE_USER_DEFAULT,
DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
delete [] dispparams.rgvarg;
if (FAILED(hr))
{
MessageBox("값을 전달할 수 없습니다.");
}
memset(&dispparams, 0, sizeof(dispparams));
dispparams.rgvarg = NULL;
dispparams.cArgs = 0;
dispparams.rgdispidNamedArgs = NULL;
dispparams.cNamedArgs = 0;
VARIANT varRetVal;
VariantInit(&varRetVal);
hr = m_pDisp->Invoke(m_dispidDiv,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET,
&dispparams,
&varRetVal,
NULL,
NULL);
if (FAILED(hr))
{
CString szMsg, szTemp;
ISupportErrorInfo* pSupportErrInfo;
IErrorInfo* pErrInfo;
BSTR bstrSource;
BSTR bstrDescription;
_bstr_t tSource;
_bstr_t tDescription;
hr = m_pDisp->QueryInterface(IID_ISupportErrorInfo,
(LPVOID*)&pSupportErrInfo);
if(SUCCEEDED(hr))
{
hr = pSupportErrInfo->InterfaceSupportsErrorInfo(__uuidof(m_pDisp));
if(SUCCEEDED(hr))
{
hr = ::GetErrorInfo(0,&pErrInfo);
if(SUCCEEDED(hr))
{
pErrInfo->GetSource(&bstrSource);
pErrInfo->GetDescription(&bstrDescription);
tSource = (_bstr_t) bstrSource;
tDescription = (_bstr_t) bstrDescription;
szTemp.Format("에러 소스: %s\n", //bstrSource);
tSource.length() ?
(LPCTSTR)tSource : _T("없음"));
szMsg += szTemp;
szTemp.Format("에러 설명: %s\n",
tDescription.length() ?
(LPCTSTR) tDescription : _T("없음"));
szMsg += szTemp;
AfxMessageBox(szMsg);
SysFreeString(bstrSource);
SysFreeString(bstrDescription);
pErrInfo->Release();
}
}
pSupportErrInfo->Release();
}
}
else
{
CString str;
str.Format("%0.5f", varRetVal.fltVal);
SetDlgItemText(IDC_RESULT, str);
}
}
BOOL CSmartPtrClientApp::InitInstance()
{
AfxEnableControlContainer();
// {
AfxOleInit();
// }
...
}
//#include <afxdisp.h> // ??
#import ".../AtlCalc.tlb" no_namespace
class CSmartPtrClientDlg : public CDialog
{
...
// Generated message map functions
//{{AFX_MSG(CSmartPtrClientDlg)
...
afx_msg void OnAdd();
...
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
ICalculatorPtr m_CalculatorPtr;
};
BEGIN_MESSAGE_MAP(CSmartPtrClientDlg, CDialog)
//{{AFX_MSG_MAP(CSmartPtrClientDlg)
...
ON_BN_CLICKED(IDC_ADD, OnAdd)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
...
BOOL CSmartPtrClientDlg::OnInitDialog()
{
...
SetIcon(m_hIcon, FALSE); // Set small icon
// {
HRESULT hr = m_CalculatorPtr.CreateInstance("ATLCalc.Calculator");
if(FAILED(hr))
AfxMessageBox(_T("서버 연결에 실패하였습니다."));
// m_CalculatorPtr = ICalculatorPtr(__uuidof(Calculator)); // ??
// }
return TRUE; // return TRUE unless you set the focus to a control
}
void CSmartPtrClientDlg::OnAdd()
{
UpdateData();
m_lResult = m_CalculatorPtr->Add(m_lA, m_lB);
UpdateData(FALSE);
}
컬렉션
class CVcClientDlg : public CDialog
{
...
// Implementation
protected:
ICompanyPtr m_pCompany;
IEmployeesPtr m_pEmployees;
HICON m_hIcon;
// Generated message map functions
//{{AFX_MSG(CVcClientDlg)
...
afx_msg void OnEnum();
afx_msg void OnCount();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CVcClientDlg::~CVcClientDlg()
{
m_pCompany = 0;
m_pEmployees = 0;
}
BOOL CVcClientDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
// {
m_pCompany = new ICompanyPtr(__uuidof(Company));
m_pEmployees = m_pCompany->Employees;
// }
return TRUE; // return TRUE unless you set the focus to a control
}
void CVcClientDlg::OnCount()
{
// TODO: Add your control notification handler code here
// {
IEmployeePtr pEmployee;
_bstr_t name;
...
CString str;
for(int i = 0; i < m_pEmployees->Count; ++i) {
pEmployee = m_pEmployees->Item[i+1];
name = pEmployee->Name;
address = pEmployee->Address;
telephone = pEmployee->Telephone;
str.Format("이름 : %s\n주소 : %s\n전화번호 : %s",
(char*)name, (char*)address, (char*)telephone);
AfxMessageBox(str);
pEmployee = 0;
}
// }
}
void CVcClientDlg::OnEnum()
{
// TODO: Add your control notification handler code here
// {
IEmployeePtr pEmployee;
IEnumVARIANTPtr pEnum;
_bstr_t name;
...
VARIANT var[10];
ULONG count, i;
CString str;
pEnum = m_pEmployees->_NewEnum;
for(i = 0; i < 10; ++i)
VariantInit(&var[i]);
pEnum->Next(10, var, &count);
for(i = 0; i < count; ++i) {
pEmployee = var[i].punkVal;
name = pEmployee->Name;
...
str.Format("이름 : %s\n주소 : %s\n전화번호 : %s",
(char*)name, (char*)address, (char*)telephone);
AfxMessageBox(str);
pEmployee = 0;
}
// }
}
* MFC
Microsoft Foundation Classes (MFC) has wizard support to add sink interfaces for ActiveX controls. However, this support does not extend to other COM servers.
AfxConnectionAdvise()
AfxConnectionUnadvise()
1. 새로운 MFC 어플리케이션 프로젝트를 생성한다.
2. 타입 라이브러리로 부터 래퍼 클래스들을 생성한다.
ClassWizard의 [Add Class...] 버튼에서 [From a type library] 메뉴를 선택해서 COleDispatchDriver 파생 클래스를 생성(.tlb)
3. COleDispatchDriver 파생 클래스의 멤버를 추가하고 초기화
BOOL CCalcClientApp::InitInstance()
{
// {
if(!AfxOleInit())
{
AfxMessageBox("Could not initialize COM dll");
return FALSE;
}
// }
AfxEnableControlContainer();
...
}
// Event dispatch interface for CSourceDoc
[ uuid(5E01B260-5C15-11cf-A78C-00AA00A70FC2),
helpstring("Event interface for CSourceDoc") ]
dispinterface IEventNotifySink
{
properties:
// Event interface has no properties
methods:
[id(1)] void FireEventOne(BSTR strEventSoure);
[id(2)] void FireEventTwo(BSTR strEventSoure);
};
class CMySink : public CCmdTarget
{
...
DECLARE_MESSAGE_MAP()
// Generated OLE dispatch map functions
//{{AFX_DISPATCH(CMySink)
afx_msg void GotEventOne(LPCTSTR strEventSource);
afx_msg void GotEventTwo(LPCTSTR strEventSource);
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
};
CMySink::CMySink()
{
EnableAutomation();
}
...
BEGIN_DISPATCH_MAP(CMySink, CCmdTarget)
//{{AFX_DISPATCH_MAP(CMySink)
DISP_FUNCTION(CMySink, "GotEventOne", GotEventOne, VT_EMPTY, VTS_BSTR)
DISP_FUNCTION(CMySink, "GotEventTwo", GotEventTwo, VT_EMPTY, VTS_BSTR)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
...
#include <initguid.h>
// {5E01B260-5C15-11cf-A78C-00AA00A70FC2}
DEFINE_GUID(IID_IEventNotifySink,
0x5e01b260, 0x5c15, 0x11cf, 0xa7, 0x8c, 0x0, 0xaa, 0x0, 0xa7, 0xf, 0xc2);
/*
// {EE7466E3-61C9-413B-AB06-B304A3A20A48}
static const IID IID_IMySink =
{ 0xee7466e3, 0x61c9, 0x413b, { 0xab, 0x6, 0xb3, 0x4, 0xa3, 0xa2, 0xa, 0x48 } };
*/
BEGIN_INTERFACE_MAP(CMySink, CCmdTarget)
INTERFACE_PART(CMySink, IID_IEventNotifySink, Dispatch)
END_INTERFACE_MAP()
...
void CMySink::GotEventOne(LPCTSTR strEventSource)
{
// TODO: Add your dispatch handler code here
CString strEventMessage;
strEventMessage.Format("Event One fired from %s.", strEventSource);
}
void CMySink::GotEventTwo(LPCTSTR strEventSource)
{
// TODO: Add your dispatch handler code here
...
}
#include "CalcServer.h"
#include "MySink.h"
...
class CCalcClientDlg : public CDialog
{
...
// Generated message map functions
//{{AFX_MSG(CCalcClientDlg)
...
afx_msg void OnAdd();
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
DWORD m_dwCookie;
CMySink *m_pMySink;
ICalculator m_Calc;
};
...
BEGIN_MESSAGE_MAP(CCalcClientDlg, CDialog)
//{{AFX_MSG_MAP(CCalcClientDlg)
...
ON_BN_CLICKED(IDC_ADD, OnAdd)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
...
#include "Afxctl.h"
// {5E01B260-5C15-11cf-A78C-00AA00A70FC2}
DEFINE_GUID(IID_IEventNotifySink,
0x5e01b260, 0x5c15, 0x11cf, 0xa7, 0x8c, 0x0, 0xaa, 0x0, 0xa7, 0xf, 0xc2);
BOOL CCalcClientDlg::OnInitDialog()
{
...
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// {
if ( !m_Calc.CreateDispatch(_T("CalcServer.Calculator")) )
AfxMessageBox(_T("서버 접속에 실패하였습니다"));
m_pMySink = new CMySink();
LPUNKNOWN pUnkSink = m_pMySink->GetIDispatch(FALSE);
AfxConnectionAdvise(m_graphObject, IID_IEventNotifySink, pUnkSink, TRUE,
&m_dwCookie);
// }
return TRUE; // return TRUE unless you set the focus to a control
}
...
void CVCClientDlg::OnDestroy()
{
CDialog::OnDestroy();
// TODO: Add your message handler code here
LPUNKNOWN pUnkSink = m_pMySink->GetIDispatch(FALSE);
AfxConnectionUnadvise(m_graphObject, IID_IEventNotifySink, pUnkSink, TRUE,
m_dwCookie);
delete m_pMySink;
}
void CCalcClientDlg::OnAdd()
{
UpdateData(TRUE);
m_Result = m_Calc.Add(m_A, m_B);
UpdateData(FALSE);
}
다중 개체의 접근
...
#include "ClockServer.h"
...
class CClockClientDlg : public CDialog
{
...
// Generated message map functions
//{{AFX_MSG(CClockClientDlg)
...
afx_msg void OnApply();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// {
private:
IClockServer m_ClockServer;
ITimeZone m_TimeZone;
// }
};
BEGIN_MESSAGE_MAP(CClockClientDlg, CDialog)
//{{AFX_MSG_MAP(CClockClientDlg)
...
ON_BN_CLICKED(IDC_UPDATE, OnApply)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CClockClientDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
// {
if(!m_ClockServer.CreateDispatch(_T("ClockServer.Application")))
AfxMessageBox(_T("서버와 연결하지 못했습니다."));
m_TimeZone.AttachDispatch(m_ClockServer.GetTimeZone());
m_ClockServer.ShowWindow();
// }
return TRUE; // return TRUE unless you set the focus to a control
}
...
void CClockClientDlg::OnApply()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
m_TimeZone.SetBias(m_nBias);
m_TimeZone.SetCity(m_sCity);
m_ClockServer.UpdateWindow();
}하나의 서버에 연결
...
BOOL CClockClientDlg::OnInitDialog()
{
...
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
// {
BOOL bSuccess = FALSE;
CLSID clsid;
if( SUCCEEDED( CLSIDFromProgID(OLESTR("ClockServer.Application"), &clsid))) {
IUnknown *pUnknown;
if( SUCCEEDED(::GetActiveObject(clsid, NULL, &pUnknown))) {
IDispatch *pDispatch;
if( SUCCEEDED(pUnknown->QueryInterface(IID_IDispatch,
(void **)&pDispatch)))
{
pDispatch->Release();
m_ClockServer.AttachDispatch(pDispatch);
bSuccess = TRUE;
}
} else {
if(m_ClockServer.CreateDispatch(_T("ClockServer.Application")))
bSuccess = TRUE;
}
}
if(bSuccess) {
m_TimeZone.AttachDispatch(m_ClockServer.GetTimeZone());
m_ClockServer.ShowWindow();
} else {
AfxMessageBox("Fail to connect to server");
}
// }
return TRUE; // return TRUE unless you set the focus to a control
}
...
참조 사이트:
http://jjjryu.tistory.com/entry/ADO
http://support.microsoft.com/kb/153582/en-us/
http://support.microsoft.com/kb/257717/en-us/
http://blog.narahome.com/entry/%EC%9C%88%EB%8F%84-%ED%83%90%EC%83%89%EA%B8%B0%EB%A1%9C-%EB%B6%80%ED%84%B0-%EC%A0%95%EB%B3%B4-%EB%B9%BC%EC%98%A4%EA%B8%B0
http://support.microsoft.com/kb/318426/en-us/