[형식 라이브러리 추출]
원하는 인터페이스의 idl을 midl 컴파일러를 통해 형식 라이브러리(tlb)를 얻어내어 이를 #import 하여 쉽게 COM을 억세스 하도록 합니다.
이해를 돕기 위하여 Microsoft의 MSN Messenger COM Library인 Messenger 인터페이스를 가져다 쓰는 예를 들며 설명합니다.
먼저, 원하는 인터페이스의 idl을 얻어내야 합니다. idl 파일이 직접 있다면 이를 이용하고, 없다면 VC++의 툴인 OLE/COM Object Viewer를 연 후, 원하는 COM 오브젝을 찾아내야 합니다.
Type Libraries 노드 밑에 Messenger API Type Library를 선택한 후 오른클릭 하여 View를 선택합니다.
( MSN 5.0 이하에서는 All Object 노드에서 Messenger를 찾을 수 있습니다. )

이렇게 하면 Messenger API 형식 라이브러리의 idl 을 볼 수 있습니다. 이제 이 idl을 메뉴에서 File/ Save As로 msnmsgr.idl로 저장 한 후 ( 이름은 마음대로 정하셔도 됩니다 ) 우리가 이 인터페이스를 사용하는데 도움을 주는 형식 라이브러리 (.tlb)로 만들어 봅시다.
아래는 idl에서 tlb 형식라이브러리를 만드는 예입니다.
D:\>midl /mktyplib203 msnmsgr.idl
MSN 5.0 이하에서는 문제없이 컴파일이 되었는데 MSN 6.0에서는 아래와 같은 결과를 출력합니다.
D:\>midl /mktyplib203 msnmsgr.idl
Microsoft (R) MIDL Compiler Version 5.01.0164
Copyright (c) Microsoft Corp 1991-1997. All rights reserved.
Processing .\msnmsgr.idl
msnmsgr.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\objidl.idl
objidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\wtypes.idl
wtypes.idl
.\msnmsgr.idl(76) : error MIDL2025 : syntax error : expecting a type specificati
on near "MOPTIONPAGE"
.\msnmsgr.idl(76) : error MIDL2026 : cannot recover from earlier syntax errors;
aborting compilation
이 idl을 노트패드에서 열어보면 MOPTIONPAGE 의 정의가 아래 쪽에 있는 것을 확인 하 실 수 있습니다. 따라서 인자로 쓰이는 구조체의 정의 부를 해당 인터페이스 정의 부 위로 옮겨야 합니다. ( 예전 버전의 MSN 인터페이스는 그러지 않았는데 새로 인자 형식이 많이 생긴 듯합니다 )
그런 식으로 에러 나지 않게 정의부를 위로 옮기시면 아래와 같이 컴파일이 잘 됩니다. 그리고 그 결과로 msnmsgr.tlb 파일이 생기게 됩니다.
D:\bro\mine\gosunet\using_tlb>midl /mktyplib203 msnmsgr.idl
Microsoft (R) MIDL Compiler Version 5.01.0164
Copyright (c) Microsoft Corp 1991-1997. All rights reserved.
Processing .\msnmsgr.idl
msnmsgr.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\objidl.idl
objidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\wtypes.idl
wtypes.idl
이제 우리가 원하는 형식 라이브러리를 얻게 되었습니다. 이를 이용하여 COM 프로그래밍을 쉽게 할 수 있습니다.
간단하게 이 형식 라이브러리를 이용해서 MSN 자동 로그인을 하도록 하는 기능을 만들어 봅니다.
Visual C++을 실행 한 후 다이얼로그 기반의 프로젝트를 생성합니다.
그리고 방금 만든 msnmsgr.tlb를 해당 프로젝트 폴더로 복사합니다.
stdafx.h 파일에 다음을 추가합니다.
#import "msnmsgr.tlb" named_guids, no_namespace
버튼 클릭 시 아래와 같은 코드가 실행되게 합니다.
AfxOleInit(); // COM 초기화는 App::InitInstance() 에 두길 추천합니다.
IMessengerPtr msn;
msn.CreateInstance( __uuidof(Messenger), NULL, CLSCTX_LOCAL_SERVER );
msn->AutoSignin();
버튼 클릭으로 위의 코드가 실행되면 msn 메신저가 로그 오프일 때 자동 로그인이 실행되게 됩니다.
만일 형식라이브러리를 Import하여 스마트 포인터를 사용하지 않는다면
HRESULT get_MyContacts(
IDispatch ** ppMContacts
);
를 사용하려 한다면 raw한 COM 프로그래밍으로 하려면
IMessengerGroups* Groups;
IMessenger2* msg;
:
IDispatch* lpGroups;
msg->get_MyGroups( &lpGroups );
Groups = lpGroups;
lpGroups->Release();
이렇게 해야 합니다만, 형식라이브러리 Import 후 스마트 포인터를 사용하면 아래와 같이 간결하고 안전한 코드를 짤 수 있습니다.
IMessengerGroupsPtr Groups;
IMessenger2Ptr msg;
Groups = msg->MyGroups;
이는 형식라이브러리를 import 할때 인터페이스의 프로퍼티를 자동으로 추가해주어서 가능한것으로 VB에서 프로퍼티 접근 하듯이 무척 간단하고 보기 좋은 프로그래밍을 할 수 있게 됩니다.
참고로, 만일 형식라이브러리를 사용하지 않고 직접 스마트 포인터를 사용하게 된다면
#include "msgruaid.h"
#include "msgrua.h"
#pragma comment(lib,"msgrguid.lib")
_COM_SMARTPTR_TYPEDEF(IMessenger, __uuidof(IMessenger));
_COM_SMARTPTR_TYPEDEF(IMessenger2, __uuidof(IMessenger2)););
_COM_SMARTPTR_TYPEDEF(IMessenger3, __uuidof(IMessenger3)););
:
위처럼 사용하여 스마트 포인터를 쓸 수는 있지만 프로퍼티를 위에서 했듯이 불편한 방법으로 접근해야 할 것입니다.
[이벤트 받기]
이벤트를 받는 코드를 raw하게 짜려 한다면 무척 불편하기도 합니다. 따라서 저는 ATL의 이벤트 받는 방식으로 접근합니다. 장점은 무엇보다 이벤트 메소드를 정의해서 쓰므로 가독성도 좋고 코드도 보기 좋다는 점입니다.
만일 signin 말고 다른 이벤트를 받기 위해서 이벤트의 메소드 아이디를 알고 싶으시면 모든 인터페이스의 정의가 되어 있는 idl 파일에서 그 값을 보실 수 있습니다.
예를 들어 아래 0x400의 OnSign 이벤트는
BEGIN_SINK_MAP(CMessengerEvents)
SINK_ENTRY_EX(0, DIID_DMessengerEvents, 0x400, OnSigninx)
END_SINK_MAP()
idl에서 아래에서 찾아볼 수 있고 id는 0x400인것을 확인 하실 수 있습니다.
[
uuid(C9A6A6B6-9BC1-43A5-B06B-E58874EEBC96),
helpstring("Messenger Events"),
hidden
]
dispinterface DMessengerEvents {
properties:
methods:
[id(0x00000415), helpstring("A new group has been created.")]
void OnGroupAdded(
[in] long hr,
[in] IDispatch* pMGroup);
:
[id(0x00000400), helpstring("Signin attempt complete.")]
void OnSignin([in] long hr);
또한 재미있는 팁은 msn에서 파일 보내기 메소드를 실행시 파일을 바로 보내지 못하게 msn 4.x 이후에는 보안상 막아두었습니다. ( msn 바이러스가 퍼지지 못하도록 하기 위해서 였겠죠 ) 제 프로젝트 성격상 파일 보내기가 자동으로 되어야 하기 때문에 작성된 저의 트릭을 보실 수 있을 것입니다. 98/2000/xp에서 잘 수행되었습니다.
이벤트 받는 코드를 포함한 모든 저의 샘플 코드를 적어 놓도록 하겠습니다.
msnx.h
#if !defined(AFX_MSNX_H__F7BD2C8E_8C37_4B4F_B106_CAB2E772167E__INCLUDED_)
#define AFX_MSNX_H__F7BD2C8E_8C37_4B4F_B106_CAB2E772167E__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
class CExeModule : public CComModule
{
public:
CExeModule();
};
extern CExeModule _Module;
#include <atlcom.h>
#include <atlwin.h>
#import "msn6.tlb" named_guids, no_namespace
IMessengerPtr GetMessenger();
class CMessengerEvents :
public IDispEventImpl<0,CMessengerEvents,&DIID_DMessengerEvents,&LIBID_MessengerAPI,1,0>
{
public:
IUnknownPtr m_psinkObj;
bool Create( IUnknownPtr p )
{
m_psinkObj = p;
return ( DispEventAdvise(p,&DIID_DMessengerEvents) == S_OK);
}
void Close()
{
if( m_psinkObj )
{
DispEventUnadvise(m_psinkObj);
m_psinkObj = NULL;
}
}
virtual ~CMessengerEvents() { Close(); }
STDMETHOD (OnSigninx) ( long hr )
{
return S_OK;
}
BEGIN_SINK_MAP(CMessengerEvents)
SINK_ENTRY_EX(0, DIID_DMessengerEvents, 0x400, OnSigninx)
END_SINK_MAP()
};
class CMessengerAPI : public IMessengerPtr, public CMessengerEvents
{
public:
virtual ~CMessengerAPI()
{
Close();
}
void Close()
{
CMessengerEvents::Close();
}
bool Create()
{
IMessengerPtr msn = GetMessenger();
if( msn )
{
*(IMessengerPtr*)this = msn;
return CMessengerEvents::Create( msn );
}
return false;
}
CString GetMyID();
CString GetMyNick();
IMessengerWindowPtr SendFile( LPCTSTR pszSigninName, LPCTSTR pszPath );
IMessengerWindowPtr InstantMessage( LPCTSTR pszSigninName );
STDMETHOD (OnSigninx)( long hr )
{
// 로그인 될때 이벤트가 떨어집니다.
return S_OK;
}
};
#endif // !defined(AFX_MSNX_H__F7BD2C8E_8C37_4B4F_B106_CAB2E772167E__INCLUDED_)
msnx.cpp
#include "stdafx.h"
#include "resource.h"
#include "msnx.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CExeModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
CExeModule::CExeModule()
{
Init(ObjectMap, NULL, &LIBID_MessengerAPI);
}
IMessengerPtr GetMessenger()
{
IMessengerPtr msn = NULL;
try
{
msn.CreateInstance( __uuidof(Messenger), NULL, CLSCTX_LOCAL_SERVER );
}
//catch(_com_error &e)
catch(...)
{
//PrintComError(e);
return NULL;
}
return msn;
}
IMessengerWindowPtr CMessengerAPI::InstantMessage( LPCTSTR pszSigninName )
{
IMessengerWindowPtr window = NULL;
try
{
IMessengerPtr msn = *this;
IMessengerContactPtr contact = (IMessengerContactPtr)msn->GetContact( (_bstr_t)pszSigninName, (_bstr_t)msn->GetMyServiceId() );
if( contact )
window = msn->InstantMessage( _variant_t((IDispatch*)contact,true));
}
//catch(_com_error &e)
catch(...)
{
//PrintComError(e);
return NULL;
}
return window;
}
CString CMessengerAPI::GetMyID()
{
CString id;
try
{
IMessengerPtr msn = *this;
id = (LPCTSTR)msn->MySigninName;
}
//catch(_com_error &e)
catch(...)
{
//PrintComError(e);
return _T("");
}
return id;
}
CString CMessengerAPI::GetMyNick()
{
CString nick;
try
{
IMessengerPtr msn = *this;
nick = (LPCTSTR)msn->MyFriendlyName;
}
//catch(_com_error &e)
catch(...)
{
//PrintComError(e);
return _T("");
}
return nick;
}
typedef struct _tagHint
{
CString text;
HWND hFound;
}HINT,*LPHINT;
BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam )
{
LPHINT hint = (LPHINT)lParam;
TCHAR szText[1000]={0,};
GetWindowText( hwnd, szText, sizeof(szText) );
CString sFileText;
sFileText.Format(_T("%s 님에게 파일 보내기"), hint->text );
if( sFileText == szText )
//if( _tcsncmp( hint->text, szText, hint->text.GetLength() ) == 0)
{
//if( !_tcschr( szText+hint->text.GetLength(), '-' ) )
{
hint->hFound = hwnd;
return FALSE;
}
}
return TRUE;
}
IMessengerWindowPtr CMessengerAPI::SendFile( LPCTSTR pszSigninName, LPCTSTR pszPath )
{
IMessengerWindowPtr window = NULL;
try
{
IMessengerPtr msn = *this;
IMessengerContactPtr contact = (IMessengerContactPtr)msn->GetContact( (_bstr_t)pszSigninName, (_bstr_t)msn->GetMyServiceId() );
if( contact )
{
window = msn->SendFile( _variant_t((IDispatch*)contact,true), pszPath);
//Sleep(100);
if( window )
{
//// 열기 버튼을 클릭한다.
//PostMessage( (HWND)window->HWND, WM_COMMAND, 1 , 0 );
HINT hint;
hint.text = (LPCTSTR)contact->FriendlyName;
hint.hFound = NULL;
// 찾을때까지 게속 창을 팢는다.
while( !hint.hFound )
{
// 피일 보내기 창을 찾는다.
EnumWindows( EnumWindowsProc, (LPARAM)&hint );
if( hint.hFound )
{
// 먼저 포커스를 주고..
::AttachThreadInput( ::GetCurrentThreadId(), ::GetWindowThreadProcessId(hint.hFound, NULL) , TRUE);
SetFocus( hint.hFound );
::AttachThreadInput( ::GetCurrentThreadId(), ::GetWindowThreadProcessId(hint.hFound, NULL) , FALSE);
// 열기 버튼을 클릭한다.
PostMessage( hint.hFound, WM_COMMAND, 1 , 0 );
break;
}
Sleep(100);
}
}
}
}
//catch(_com_error &e)
catch(...)
{
//PrintComError(e);
return NULL;
}
return window;
}
정리
형식라이브러리를 Import 하여 스마트 포인터를 사용하여 COM 프로그래밍을 하면 무척 쉽고 편하게 프로그래밍을 하실 수 있습니다. 본 문서에서는 형식라이브러리를 일반적으로 Import하여 쓰는 것외에 자신이 원하는 어떠한 인터페이스도 idl을 추출하여 형식라이브러리로 만들어 사용 할 수 있다는데 중점을 두어 설명을 하였습니다.
- Unicode/MBCS(DBCS)/ANSI/TCHAR (0)2007/03/05
- [ATL] 새로운 인터페이스 추가하기 (0)2007/03/05
- COM 형식 라이브러리 사용하기 (0)2007/01/05
- GetProcAddress 쉽게 사용하기 (0)2006/12/27
- OLE를 이용한 Drag and Drop (0)2006/11/29

수안이의 컴퓨터 연구실



Leave your greetings.