오후 2:17 2005-08-04
DCOM 로컬서버하기 조경민 bro@shinbiro.com
=========================================================================

DCOM은?
----------------------------------------------------------
DCOM은 Distributed COM으로 다른 컴퓨터에 있는 COM 기능을 원격에서 호출할 수 있다.
DCOM을 구현하기 위해서 COM+ DLL을 작성하여 이를 MTS에 올려 미들웨어식으로
DCOM 패키지를 내보낼 수 있는데 이는 COM+과 MTS의 도움으로 DCOM 기능을 구현한 것인데
이는 "COM+ 컴포넌트 만들기" 문서를 참고한다.
http://neri.cafe24.com/menu/bbs/view.php?id=kb&page=1&sn1=&divpage=1&sn=off&ss=on&sc=on&keyword=MTS&select_arrange=headnum&desc=asc&no=176

만일 ATLCOM을 잘 모른다면 다음 문서를 참고한다.
ATL 조금 하기
http://neri.cafe24.com/menu/bbs/view.php?id=kb&page=1&sn1=&divpage=1&category=5&sn=off&ss=on&sc=on&keyword=COM&select_arrange=headnum&desc=asc&no=79

DCOM 로컬 서버
----------------------------------------------------------
지금 하려는 것은 로컬 서버 자신이 DCOM 서버가 되는 방법이다.
1. 먼저 VC를 실행 한 후 ATL COM 프로젝트로 실행하되(이름은 TestDCOM), Service를 선택한다.
2. ClassView에서 오른클릭 New ATL Object한 후 Simple Object을 만든다.
3. Project/Setting에서 Post-Build step에 다음을 추가한다.
   .\debug\TestDCOM.exe -regserver
   start nmake TestDCOMps.mk
   regsvr32 TestDCOMps.dll
   만일 로컬서버를 서비스로 동작시키려면 -regserver 대신 -service를 넣고 관리도구/서비스 에서
   해당 TestDCOM 서비스를 동작 시키면된다. TestDCOMpsmk는 자동생성된 TestDCOM의 Proxy com dll
   프로젝트다. proxy com dll이 없어도 어느정도 동작은되나 배열인자 전달이 제대로 안되는 경우가
   있으므로 proxy dll을 제공하는것이 좋다.
4. TestDCOM.cpp에 있는 소스를 수정한다.

void CServiceModule::Run()
{
    _Module.dwThreadID = GetCurrentThreadId();

//    HRESULT hr = CoInitialize(NULL);
//  If you are running on NT 4.0 or higher you can use the following call
//  instead to make the EXE free threaded.
//  This means that calls come in on a random RPC thread
        HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    _ASSERTE(SUCCEEDED(hr));


    // This provides a NULL DACL which will allow access to everyone.
//    CSecurityDescriptor sd;
//    sd.InitializeFromThreadToken();
//    hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

        // This provides a NULL DACL which will allow access to everyone.
    hr = CoInitializeSecurity(0, -1, 0, 0, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS, 0, 0, 0 );
    _ASSERTE(SUCCEEDED(hr));

    hr = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, REGCLS_MULTIPLEUSE);
    _ASSERTE(SUCCEEDED(hr));

    LogEvent(_T("Service started"));
    if (m_bService)
        SetServiceStatus(SERVICE_RUNNING);

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))
        DispatchMessage(&msg);

    _Module.RevokeClassObjects();

    CoUninitialize();
}

5. rgs파일을 수정한다.
   DCOM 서버에 접속하기 위해서는 Activation Security에 어긋나선 안되는데 이를 위해 LaunchPermission
   이 알맞아야 한다. CoCreateInstanceEx 시 접속할 컴퓨터의 로그인 계정(아이디암호)를 넣는 등 여러가지
   방법이 있으나 간편하게 Interactive user로 접속하는 MSDN 샘플 'DCOMTEST.exe' 에서 제시된 레지스트리
   파일을 이용하여 수정하였다. (codeproject의 anonymous DCOM에서 소개된 것으로는 안되었다.)
   TestDCOM.rgs를 수정한다.

HKCR
{
        NoRemove AppID
        {
                {4C4C2EB8-D5A6-49AD-9233-7B3E28E2B126} = s 'TSSCORE'
                {
                        val LaunchPermission = b '01000480300000004c000000000000001400000002001c00010000000000140001000000010100000000000100000000010500000000000515000000a05f841f5e2e6b49ce120303f4010000010500000000000515000000a05f841f5e2e6b49ce120303f4010000'
                        val RunAs = s 'Interactive User'
                }
                'TSSCORE.EXE'
                {
                        val AppID = s {4C4C2EB8-D5A6-49AD-9233-7B3E28E2B126}
                }
        }
}


이제 로컬서버 DCOM은 끝났다.
모두 빌드 후 TestDCOM.exe를 더블 클릭 또는 Debug 모드로 실행한다.


DCOM 클라이언트
--------------------------------------------------------------------
DCOM 클라이언트를 작성한다.
1. MFC 기반 다이얼로그나 Win32 프로젝트를 하나 생성한다. TestDCOMClient라고 지었다고하자.
2. DCOM 연결을 위해서 stdafx.h 같은 공통 헤더파일에 아래를 추가한다.
   경로는 상대경로 절대경로다되므로 알맞게 맞춘다. tlb를 복사해와도 된다.

#import "TestDCOM.tlb" no_namespace
#define _WIN32_DCOM                                // DCOM USE!
#include <objbase.h>
#include <comdef.h>

3. 다음 코드를 추가한다.

IUnknownPtr        CreateDCOMInstance(const CLSID& clsid, LPCTSTR pszIP)
{
        _bstr_t bsIP(pszIP);
        MULTI_QI mqi = { &IID_IUnknown, 0, 0 };

    COAUTHINFO cai = {  RPC_C_AUTHN_NONE,
                        RPC_C_AUTHZ_NONE,
                        0,
                        RPC_C_AUTHN_LEVEL_NONE,
                        RPC_C_IMP_LEVEL_IMPERSONATE,
                        0,
                        EOAC_NONE};

    COSERVERINFO csi = { 0, bsIP, &cai, 0 };

    HRESULT hr = CoCreateInstanceEx(clsid, NULL, CLSCTX_REMOTE_SERVER, &csi, 1, &mqi);

    if (FAILED(hr))
    {

        TCHAR szError[1024];
        FormatMessage(  FORMAT_MESSAGE_FROM_SYSTEM,
                        NULL,
                        hr,
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                        szError,
                        1024,
                        NULL);
        return FALSE;
    }

    return (IUnknownPtr)mqi.pItf;
}

다음은 쓰는 부분이다. 접속하려는 DCOM 서버의 ip나 www.yahoo.com같은 도메인이름,
컴퓨터 이름 등을 넣어주면 된다.

// 먼저 COM 라이브러리를 초기화하고, 인증없이 익명 연결한다.
CoInitialize(NULL);
CoInitializeSecurity(0, -1, 0, 0, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS, 0, 0, 0 );

ITestPtr spTest;
spTest = CreateDCOMInstance(__uuidof(TestDCOM), "localhost" );
spTest->RemoteCall();

// 다썼으면 프로그램 끝내기 전에 COM 라이브러리 해제
CoUninitialize();

실행하기
-----------------------------------------------------------------------------
DCOM 서버에서 proxy dll인 TestDCOMps.dll을 regsvr32로 등록한 후,
DCOM 로컬서버를 실행한다.
DCOM 클라이언트에서는 proxy dll을 복사해와서 regsvr32로 등록한 후,
클라이언트를 실행한다.

배열인자 사용
-----------------------------------------------------------------------------
DCOM 서버의 메소드 중에 아래와 같은 것이 있다고 한다면

[id(1), helpstring("method Test")] HRESULT Test( [out] int* pnLen, [out,size_is(,*pnLen)] unsigned char** ppData );

size_is는 이 메소드가 호출될 당시에 배열의 길이가 결정된다는 것으로 *pnLen으로 결정되는
것인데 DCOM 서버에 proxy dll이 등록되지 않으면 제대로 동작하지 않는다.
DCOM서버의 Test 구현부다.

STDMETHODIMP TestDCOM::Test(int *pnLen, unsigned char** ppData)
{
        // TODO: Add your implementation code here
        char buf[5] = "abcd";
        *pnLen = 5;
        *ppData = (unsigned char*)CoTaskMemAlloc( *pnLen );

        strcpy( (char*)*ppData, buf );

        return S_OK;
}

DCOM을 통해 오가는 메모리는 CoTaskMemAlloc/CoTaskMemfree를 사용하길 권한다.
DCOM서버에서 Alloc하고 이를 클라이언트로 리턴했다면 클라이언트는 Free를 해야할 책임이 있다.
아래는 클라이언트 호출부이다. 만일 proxy dll을 DCOM 서버에서 등록해 놓은 상태가 아니면
abcd가 전달안되고 a만 전달되게 된다.

int nLen;
unsigned char* pData;
spTest->Test( &nLen, &pData );
CoTaskMemFree(pData);


DCOM 안될때
--------------------------------------------------------------------------------

RPC Server is Unavailable에 관하여
----------------------------------
- RPC Server is Unavailable 방화벽으로 막힌 경우가 많다.
- netstat -noa 후 135 포트 (RPC)가 리슨되고 있는지 확인한다.
- MSDN에서 DCOMTEST.exe 을 찾아서 확인해 보라.
- MSDN에서 Windows Resource kit 인 rpcping.exe를 사용할 수 있다.
http://groups-beta.google.com/group/microsoft.public.win32.programmer.wmi/browse_thread/thread/56473e1d1801e010/1f747c12e595d612?lnk=st&q=re:+DCOMTEST+800706ba&rnum=2#1f747c12e595d612

=> 본인은 방화벽 문제였다.


Access Denied에 관해서
----------------------
위의 CoInitializeSecurity(0, -1, 0, 0, RPC_C_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_ANONYMOUS, 0, 0, 0 );
코드를 서버와 클라이언트에 모두 넣어주면 (SP2시 SP2 보안설정까지) 다른 도메인, 다른 유저라도 가능하다.
유저가 Administrator 권한이 아니고 User 권한이라도 동작이 된다.

그래도 안되면 아래의 상황을 보자.

BUG: "Access Denied" Error Message in Response to DCOM Activation for ATL-Built COM Servers
8자 이하의 실행 파일명이어야 한다.

http://www.codeguru.com/activex/COMSecurity1.html
http://www.codeguru.com/activex/COMSecurity2.html
DCOM의 보안은 두가지 주제로 볼 수 있다. Activation Security과 Access Security이다.


Activation Security

Activation Security은 일단 DCOM 서버를 활성화시킬 수 있는가에 대한 얘기이다.
활성화하기 위해서는 DCOM 서버를 실행해야하므로 Launch Permission이 있어야 한다.
이말은 bro로 로그인된 DCOM 클라이언트 호스트에서 다른 컴퓨터인 DCOM 서버에 접근하려면
그 다른 컴퓨터에도 bro 계정이 있어야 한다는 말인데,
이를 해결하기 위해서
- anonymous (unauthenticated) permission을 설정
- COSERVERINFO구초체안의 COAUTHINFO 구조체 안의 COAUTHIDENTITY에 DCOM서버컴퓨터에 로그인 가능한
사용자 아이디와 암호를 넣어야 한다. 그리고 CoCreateInstanceEx or CoGetClassObject 를 호출하게 된다.
이 때 COAUTHINFO 구조체의 dwImpersonationLevel는 RPC_C_IMP_LEVEL_IMPERSONATE과 같거나 높은 레벨이어야 한다.

=> DCOMTEST.exe에서 제공된 레지스트리 값으로 해결했다.


XP SP2에 관하여
http://service1.symantec.com/Support/faxprod.nsf/docid/2004071911425704
------------------------------------
아래는 DCOM 클라이언트/ 서버 모두 해주어야 하는 설정이다.
XP SP2는 자체 방화벽으로 RPC server is unavailable 에러를 일으킬 수 있다.
방화벽을 끄거나, 제어판/윈도우즈 방화벽의 탭중 예외에 RPC인 135 포트를 추가하거나 프로그램에서
자신의 Local DCOM Server EXE를 선택한다.

그러나 SP2에서는 Anonymous DCOM Access를 Access Denied하기 때문에 다음과 같은 절차로 허용하게 만든다.

커맨드창을 열고 다음 명령어를 입력한다.
netsh
firewall
set service REMOTEADMIN ENABLE SUBNET 또는
set service REMOTEADMIN ENABLE ALL
show service
exit

다음으로 제어판/관리도구로 간 후 구성 요소 서비스를 연 후
트리 노드 루트/컴퓨터/내컴퓨터에서 오른클릭 속성으로 간 후 COM 보안에서
각각 제한값 편집 버튼을 눌러 사용자 'ANOYMOUS LOGIN' 추가 후 ANOYMOUS LOGIN을 모두 허용하게 만든다.

아래는 마지막으로 DCOM 서버가 할 이다.
구성 요소 서비스의 트리  루트 노드/컴퓨터/내컴퓨터/DCOM 구성에서 자신의 DCOM 서버를 선택한 후
오른클릭 속성으로 등록정보를 연후 보안탭을 연후 시작 및 활성화 권한을 사용자 지정으로 한후 편집 버튼
을 누른 후 ANONYMOUS LOGON을 추가후 모두 허용으로 한다.

'KB > tutorial' 카테고리의 다른 글

WinDbg 사용법  (0) 2006.08.27
Implementation FAT File System  (0) 2004.11.04
How to i386 32bit OS Kernel Compile in VC6  (0) 2004.11.04
DDK in VC with SoftICE  (1) 2004.11.04
COM 형식 라이브러리 사용하기  (0) 2004.11.04

+ Recent posts