DCOM: PingRemoteHost function

DCOM takes an unreasonably long amount of time to fail an activation request -- this is because it tries each available network protocol (TCP, UDP, IPX, NP, etc.) in turn, until they all fail.  Timeouts of 3 minutes are not uncommon.  The DCOM designers say they chose robustness over performance, but that is hogwash.  No user I know will wait 3 minutes for a LAN connection to respond -- 3 seconds is more like it!

To alleviate this problem, simply use this helper function (AtlPingRemoteHost) to "ping" a remote DCOM server, before calling CoCreateInstanceEx -- thus greatly mitigating the DCOM timeout problem (at least for the initial activation request).  The ping function simply sends a DCOM activation request (for a bogus CLSID) to the server, on a background thread, to avoid blocking the client thread for longer than the specified timeout.  AtlPingRemoteHost makes use of AtlCoCreateInstanceEx, a strongly-typed template function wrapper around CoCreateInstanceEx, which you may also find convenient to use.

Usage is fairly straightforward:

HRESULT ConnectToServer(
   const OLECHAR* szAddress, IFoo **ppFoo)
{
   HRESULT hr = 0; //ok
   *ppFoo = 0;

   hr = ATLX::AtlPingRemoteHost(szAddress, 3000);
   if (FAILED(hr)) return hr;

   hr = ATLX::AtlCoCreateInstanceEx(szAddress, __uuidof(Foo), ppFoo);
   if (FAILED(hr)) return hr;

   return 0; //ok
}

Update -- Windows Server 2003 and Windows XP SP2 don't allow unauthenticated DCOM activation requests, by default (if at all).  I'm removing support for bUnAuthenticated=true.

#pragma once

namespace ATLX {

template <typename Q>
HRESULT AtlCoCreateInstanceEx(const TCHAR* host, const CLSID& clsid, Q** ppq, bool bUnAuthenticated=false)
{
    (*ppq) = 0;

    if (host && host[0])
    {
#if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM
        COAUTHINFO cai = 
        { RPC_C_AUTHN_WINNT, 0, 0, 
        RPC_C_AUTHN_LEVEL_NONE, 
        RPC_C_IMP_LEVEL_IMPERSONATE, 0, 0 };

        CComBSTR sHost = host;

        COSERVERINFO csi = 
        { 0, sHost.m_str, 0, 0 };

        if (bUnAuthenticated)
            csi.pAuthInfo = &cai;

        MULTI_QI mqi = 
        { &(__uuidof(Q)), 0, 0 };

        HRESULT hr = ::CoCreateInstanceEx(clsid,0,CLSCTX_SERVER,&csi,1,&mqi);
        if (FAILED(hr)) return hr;

        (*ppq) = (Q*)(mqi.pItf);
        return 0; //ok
#else
        ATLASSERT(false && "DCOM not supported by current build settings");
        return E_UNEXPECTED;
#endif
    }

    return ::CoCreateInstance(clsid,0,CLSCTX_SERVER,__uuidof(Q),(void**)(ppq));
}

#if (_WIN32_WINNT >= 0x0400 ) || defined(_WIN32_DCOM) // DCOM
static DWORD __stdcall AtlPingRemoteHostProc(void* pv)
{
    TCHAR* host = reinterpret_cast<TCHAR*>(pv);

    HRESULT hr = 0;

    // Initialize our COM apartment
    hr = ::CoInitializeEx(0,COINIT_MULTITHREADED);
    if (FAILED(hr)) return hr;

    // Declare a phony CLSID (we could probably just get away w/ all zeroes, but...)
    CLSID clsidNoSuchThing = // f3839019-166b-4ec4-856c-cc68371ac7b2
    { 0xf3839019, 0x166b, 0x4ec4, {0x85, 0x6c, 0xcc, 0x68, 0x37, 0x1a, 0xc7, 0xb2} };

    // Ping the remote host
    IUnknown* pDummy = 0;
    hr = AtlCoCreateInstanceEx(host,clsidNoSuchThing,&pDummy,true);

    // Tear down our apartment, and terminate this bkgrnd thread
    ::CoUninitialize();
    return hr;
}

static HRESULT AtlPingRemoteHost(const TCHAR* host, DWORD nTimeout)
{
    HRESULT hr = 0;

    // Just a little sanity-check
    if (!host || !host[0]) return 0; //ok

    // Spawn a bkgrnd thread
    CAutoHandle hThread = ::CreateThread(0,0,AtlPingRemoteHostProc,(void*)host,0,0);

    // Wait a bit for it to complete, and interpret the results
    if (::WaitForSingleObject(hThread,nTimeout) == WAIT_TIMEOUT)
        hr = 0x800706ba; // rpc server unavailable
    else
        ::GetExitCodeThread(hThread,(DWORD*)&hr);

    // We HOPE we get "class not registered"
    if (hr == 0x80040154)
        return 0; //ok

    return hr;
}
#endif // DCOM


} // namespace