0

I'm trying to use MsRdpClient to connect to rdp server, and receive some basic notifcations. Now, i have the same issue, that people were having for about 8 years i haven't found any answer, so i raise this topic again. What i have is code like this, pieced together from various examples on how to use rdp with winapi:

#include <Windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#include "resource.h"
#include <atlbase.h>
#include <exdisp.h>

#include "mstscax.tlh"
#include "mstscax.tli"
using namespace MSTSCLib;


class CTscEventSink : public IMsTscAxEvents
{
public:
    CTscEventSink()
    {
        _ulRefs = 1;
        m_dwEvtCookie = 0;
    }
    ~CTscEventSink()
    {
    }
    BOOL Attach(IMsTscAx* pTscAx)
    {
        CComPtr<IConnectionPointContainer> cpCPCont;
        HRESULT hr;
        hr = pTscAx->QueryInterface(IID_IConnectionPointContainer, (LPVOID *)&cpCPCont);
        if (FAILED(hr))
            return FALSE;
        hr = cpCPCont->FindConnectionPoint(__uuidof(IMsTscAxEvents), &m_cpConnect);
        if (FAILED(hr))
        {
            return FALSE;
        }
        hr = m_cpConnect->Advise(this, &m_dwEvtCookie);
        if (FAILED(hr))
        {
            m_dwEvtCookie = 0;
            return FALSE;
        }
        return TRUE;
    }
    void Detach()
    {
        if ((m_dwEvtCookie) && (m_cpConnect))
            m_cpConnect->Unadvise(m_dwEvtCookie);
        if (m_cpConnect)
            m_cpConnect = NULL;
    }
    //IUnknown Methods
    STDMETHOD(QueryInterface) (REFIID riid, LPVOID * ppv)
    {
        if (riid == __uuidof(IMsTscAxEvents))
        {
            *ppv = (IMsTscAxEvents *)this;
        }
        else
        {
            *ppv = NULL;
            return E_NOINTERFACE;
        }
        AddRef();
        return S_OK;
    }
    STDMETHOD_(ULONG, AddRef) (void)
    {
        InterlockedIncrement((LONG*)&_ulRefs);
        return _ulRefs;
    }
    STDMETHOD_(ULONG, Release) (void)
    {
        ULONG ulRefs = _ulRefs;
        if (InterlockedDecrement((LONG*)&_ulRefs) == 0)
        {
            delete this;
            return 0;
        }
        return _ulRefs;
    }
    HRESULT OnConnecting()
    {
        DebugBreak();
        ::MessageBoxA(NULL, "Connecting", "", 0);
    }
    HRESULT OnConnected()
    {
        DebugBreak();
        ::MessageBoxA(NULL, "Connected", "", 0);
    }
    HRESULT OnFatalError()
    {
        DebugBreak();
        ::MessageBoxA(NULL, "Fatal Error", "", 0);
    }
    HRESULT OnLoginComplete()
    {
        DebugBreak();
        ::MessageBoxA(NULL, "Login complete", "", 0);
    }
    HRESULT OnLogonError(
        long lError)
    {
        DebugBreak();
        ::MessageBoxA(NULL, "OnLogonError", "", 0);
    }
    HRESULT OnDisconnected(
        long discReason)
    {
        DebugBreak();
        ::MessageBoxA(NULL, "OnDisconnected", "", 0);
    }
    //IDispatch Methods
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo)
    {
        return E_NOTIMPL;
    }
    STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
    {
        return E_NOTIMPL;
    }
    STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames,
        LCID lcid, DISPID FAR* rgdispid)
    {
        return E_NOTIMPL;
    }
    STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
        DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,
        EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr)
    {

        return S_OK;
    }
protected:
    ULONG _ulRefs;
    DWORD m_dwEvtCookie;
    CComPtr<IConnectionPoint> m_cpConnect;
};


CTscEventSink * pCTscEventSink;
MSTSCLib::IMsRdpClient2* pInterface = NULL;


LRESULT CALLBACK WindowProc(HWND hWnd, UINT messg, WPARAM wParam, LPARAM lParam)
{
    switch (messg)
    {
    case WM_SIZE:

        break;
    case WM_CLOSE:

        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:

        PostQuitMessage(0);
        break;
    default:

        return(DefWindowProc(hWnd, messg, wParam, lParam));
    }
    return 0;
}

int CALLBACK wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR lpCmdLine,
    int nCmdShow)
{

    WNDCLASS wndclass;
    wndclass.style = CS_VREDRAW | CS_HREDRAW;
    wndclass.lpfnWndProc = &WindowProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = NULL;
    wndclass.hCursor = NULL;
    wndclass.hbrBackground = reinterpret_cast <HBRUSH> (COLOR_BTNFACE + 1);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = L"RdpTest1";
    ::RegisterClass(&wndclass);

    HWND mainWindow = ::CreateWindow(
        L"RdpTest1",
        L"Rdp!",
        0,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        800,
        NULL,
        NULL,
        hInstance,
        0);
    ::ShowWindow(mainWindow, nCmdShow);
    ::UpdateWindow(mainWindow);

    //ocx

    typedef HRESULT(WINAPI *PFonc)(IUnknown*, HWND, IUnknown**);
    HINSTANCE hDLL2 = ::LoadLibrary(TEXT("atl.dll"));
    if (!hDLL2)
        return 1;

    PFonc AtlAxAttachControl = (PFonc) ::GetProcAddress(hDLL2, "AtlAxAttachControl");

    HRESULT hr = ::CoInitialize(0);

    HRESULT hrInit = CoInitialize(NULL);
    if (FAILED(hrInit)) return 0;
    CLSID clsid = __uuidof(MSTSCLib::MsRdpClient2);
    IID iid = __uuidof(MSTSCLib::IMsRdpClient2);
    HRESULT hrInterface = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, iid, (void**)&pInterface);
    if (SUCCEEDED(hrInterface))
    {

        pCTscEventSink = new CTscEventSink();

        BOOL b = pCTscEventSink->Attach(pInterface);
        pInterface->PutServer(L"non_existing_server.lol");
        pInterface->PutFullScreen(VARIANT_TRUE);
        pInterface->PutDesktopWidth(GetSystemMetrics(SM_CXSCREEN));
        pInterface->PutDesktopHeight(GetSystemMetrics(SM_CYSCREEN));
        pInterface->PutUserName(L"some_incorrect_user");
        pInterface->AdvancedSettings2->PutClearTextPassword(L"some_incorrect_pass");

        pInterface->AdvancedSettings2->PutRDPPort(3389);

        IMsRdpExtendedSettings *pExtendedSetting = NULL;
        IID interface_MsRdpClient8NotSafeForScripting = __uuidof(MSTSCLib::MsRdpClient2NotSafeForScripting);
        HRESULT hr = pInterface->QueryInterface(interface_MsRdpClient8NotSafeForScripting, (void**)&pExtendedSetting);
        if (hr == S_OK)
        {
            VARIANT index;
            VariantInit(&index);
            V_VT(&index) = VT_BOOL;
            index.boolVal = VARIANT_TRUE;
            pExtendedSetting->put_Property((BSTR)L"DisableCredentialsDelegation", &index);
        }
        hr = AtlAxAttachControl(pInterface, mainWindow, 0);
        if (FAILED(hr)) {
            MessageBox(0, L"FAILED(AtlAxAttachControl(pitd, container, NULL))", L"Error", MB_ICONERROR | MB_OK);
        }
        HRESULT hrConnect = pInterface->Connect();
        if (FAILED(hrConnect))
        {
            MessageBoxW(NULL, L"pInterface->Connect()", L"Error", 0);
        }
        else MessageBoxW(NULL, L"pInterface->Connect() Success!!!", L"Success", 0);
    }
    else
    {
        wchar_t buf[16] = { 0 };
        _ltow(hrInterface, buf, 16);
        MessageBoxW(NULL, buf, L"CoCreateInstance failed", 0);
    }

    ::MSG message;
    while (::GetMessageA(&message, 0, 0, 0)) {
        switch (message.message) {
        case WM_QUIT:
            break;
        default:
            ::TranslateMessage(&message);
            ::DispatchMessage(&message);
            break;
        }
    }
    CoUninitialize();
    FreeLibrary(hDLL2);
    return 0;
}

Ok, so what i do in this code. I create a window, create a MSTSCLib::IMsRdpClient2* pInterface object, attach event sink class to catch events, to this object and i pass invalid data to it. But, no events get's fired, OnFatalError,OnConnecting etc, never got called, and HRESULT hrConnect = pInterface->Connect(); always returns S_OK, no matter what is the server/user/password. From what i understood this behaviour has to do with Invoke implementation in CTscEventSink i need to do something inside here:

STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
    DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr)
{

    return S_OK;
}

Rather then just returning S_OK, i need to do something, to actually pass the connect command further. Can someone help, what do i need to do here?? Like what to call from Invoke?? This is a mistery and i do not know any examples and no msdn pages say something about that.

  • Yes, you are supposed to look at dispidMember and call your corresponding function. Like your OnLogonError() for dispid 0x16. The dispids are not documented well, that doesn't help. You can see them by running oleview.exe c:\windows\syswow64\msctax.dll from the Developer Command Prompt. [id] attribute on the IMsTscAxEvents functions. – Hans Passant Dec 05 '18 at 01:22
  • The final 4 class members of `CTscEventSink` constitute the [IDispatch](https://learn.microsoft.com/en-us/windows/desktop/api/oaidl/nn-oaidl-idispatch) interface. Its purpose is to allow clients to bind to interfaces dynamically. This is a requirement for scripting environments (like VBScript). It works by assigning function names to IDs (`DISPID`), that are discoverable at runtime by clients. The `Invoke` implementation is required to dispatch calls by `DISPID` to its implementation. – IInspectable Dec 05 '18 at 10:17

0 Answers0