/*
 * Unit test suite for cstubs
 *
 * Copyright 2006 Huw Davies
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>
#include <stdio.h>

#define COBJMACROS

#include <windef.h>
#include <winbase.h>
#include <winnt.h>
#include <winerror.h>

#include "initguid.h"
#include <ole2.h>
#include "rpc.h"
#include "rpcdce.h"
#include "rpcproxy.h"

#include "wine/heap.h"
#include "wine/test.h"

#include "cstub.h"

static CStdPSFactoryBuffer PSFactoryBuffer;

static ULONG WINAPI test_CStdStubBuffer_Release(IRpcStubBuffer *This)
{
    return NdrCStdStubBuffer_Release(This, (IPSFactoryBuffer *)&PSFactoryBuffer);
}

static ULONG WINAPI test_CStdStubBuffer2_Release(IRpcStubBuffer *This)
{
    return NdrCStdStubBuffer2_Release(This, (IPSFactoryBuffer *)&PSFactoryBuffer);
}

static GUID IID_if1 = {0x12345678, 1234, 5678, {12,34,56,78,90,0xab,0xcd,0xef}};
static GUID IID_if2 = {0x12345679, 1234, 5678, {12,34,56,78,90,0xab,0xcd,0xef}};
static GUID IID_if3 = {0x1234567a, 1234, 5678, {12,34,56,78,90,0xab,0xcd,0xef}};
static GUID IID_if4 = {0x1234567b, 1234, 5678, {12,34,56,78,90,0xab,0xcd,0xef}};
static CLSID CLSID_psfact = {0x1234567c, 1234, 5678, {12,34,56,78,90,0xab,0xcd,0xef}};

static int my_alloc_called;
static int my_free_called;

static void * CALLBACK my_alloc(SIZE_T size)
{
    my_alloc_called++;
    return NdrOleAllocate(size);
}

static void CALLBACK my_free(void *ptr)
{
    my_free_called++;
    NdrOleFree(ptr);
}

typedef struct _MIDL_PROC_FORMAT_STRING
{
    short          Pad;
    unsigned char  Format[ 2 ];
} MIDL_PROC_FORMAT_STRING;

typedef struct _MIDL_TYPE_FORMAT_STRING
{
    short          Pad;
    unsigned char  Format[ 2 ];
} MIDL_TYPE_FORMAT_STRING;


static const MIDL_PROC_FORMAT_STRING __MIDL_ProcFormatString =
{
    0,
    {
        0, 0
    }
};

static const MIDL_TYPE_FORMAT_STRING __MIDL_TypeFormatString =
{
    0,
    {
        0, 0
    }
};

static const MIDL_STUB_DESC Object_StubDesc =
    {
    NULL,
    my_alloc,
    my_free,
    { 0 },
    0,
    0,
    0,
    0,
    __MIDL_TypeFormatString.Format,
    1, /* -error bounds_check flag */
    0x20000, /* Ndr library version */
    0,
    0x50100a4, /* MIDL Version 5.1.164 */
    0,
    NULL,
    0,  /* notify & notify_flag routine table */
    1,  /* Flags */
    0,  /* Reserved3 */
    0,  /* Reserved4 */
    0   /* Reserved5 */
    };

static HRESULT WINAPI if1_fn1_Proxy(void *This)
{
    return S_OK;
}

static void __RPC_STUB if1_fn1_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase)
{
    trace("fn1 stub\n");
}

static HRESULT WINAPI if1_fn2_Proxy(void *This)
{
    return S_OK;
}

static void __RPC_STUB if1_fn2_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase)
{
    trace("fn2 stub\n");
}

static CINTERFACE_PROXY_VTABLE(5) if1_proxy_vtbl =
{
    { &IID_if1 },
    {   IUnknown_QueryInterface_Proxy,
        IUnknown_AddRef_Proxy,
        IUnknown_Release_Proxy ,
        if1_fn1_Proxy,
        if1_fn2_Proxy
    }
};


static const unsigned short if1_FormatStringOffsetTable[] =
    {
    0,
    0
    };

static const MIDL_SERVER_INFO if1_server_info =
    {
    &Object_StubDesc,
    0,
    __MIDL_ProcFormatString.Format,
    &if1_FormatStringOffsetTable[-3],
    0,
    0,
    0,
    0};


static const PRPC_STUB_FUNCTION if1_table[] =
{
    if1_fn1_Stub,
    if1_fn2_Stub
};

static CInterfaceStubVtbl if1_stub_vtbl =
{
    {
        &IID_if1,
        &if1_server_info,
        5,
        &if1_table[-3]
    },
    {
        CStdStubBuffer_QueryInterface,
        CStdStubBuffer_AddRef,
        test_CStdStubBuffer_Release,
        CStdStubBuffer_Connect,
        CStdStubBuffer_Disconnect,
        CStdStubBuffer_Invoke,
        CStdStubBuffer_IsIIDSupported,
        CStdStubBuffer_CountRefs,
        CStdStubBuffer_DebugServerQueryInterface,
        CStdStubBuffer_DebugServerRelease
    }
};

static CINTERFACE_PROXY_VTABLE(13) if2_proxy_vtbl =
{
    { &IID_if2 },
    {   IUnknown_QueryInterface_Proxy,
        IUnknown_AddRef_Proxy,
        IUnknown_Release_Proxy ,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    }
};

static const unsigned short if2_FormatStringOffsetTable[] =
    {
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    0
    };

static const MIDL_SERVER_INFO if2_server_info =
    {
    &Object_StubDesc,
    0,
    __MIDL_ProcFormatString.Format,
    &if2_FormatStringOffsetTable[-3],
    0,
    0,
    0,
    0};


static const PRPC_STUB_FUNCTION if2_table[] =
{
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION,
    STUB_FORWARDING_FUNCTION
};

static CInterfaceStubVtbl if2_stub_vtbl =
{
    {
        &IID_if2,
        &if2_server_info,
        13,
        &if2_table[-3]
    },
    { 0, 0, test_CStdStubBuffer2_Release, 0, 0, 0, 0, 0, 0, 0 }
};

static CINTERFACE_PROXY_VTABLE(5) if3_proxy_vtbl =
{
    { &IID_if3 },
    {   IUnknown_QueryInterface_Proxy,
        IUnknown_AddRef_Proxy,
        IUnknown_Release_Proxy ,
        if1_fn1_Proxy,
        0
    }
};


static const unsigned short if3_FormatStringOffsetTable[] =
    {
    0,
    0
    };

static const MIDL_SERVER_INFO if3_server_info =
    {
    &Object_StubDesc,
    0,
    __MIDL_ProcFormatString.Format,
    &if3_FormatStringOffsetTable[-3],
    0,
    0,
    0,
    0};

static CInterfaceStubVtbl if3_stub_vtbl =
{
    {
        &IID_if3,
        &if3_server_info,
        5,
        &if1_table[-3]
    },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

static CINTERFACE_PROXY_VTABLE(7) if4_proxy_vtbl =
{
    { &IID_if4 },
    {   IUnknown_QueryInterface_Proxy,
        IUnknown_AddRef_Proxy,
        IUnknown_Release_Proxy ,
        0,
        0,
        0,
        0
    }
};

static const unsigned short if4_FormatStringOffsetTable[] =
    {
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    (unsigned short) -1,
    0
    };

static const MIDL_SERVER_INFO if4_server_info =
    {
    &Object_StubDesc,
    0,
    __MIDL_ProcFormatString.Format,
    &if4_FormatStringOffsetTable[-3],
    0,
    0,
    0,
    0};

static CInterfaceStubVtbl if4_stub_vtbl =
{
    {
        &IID_if4,
        &if4_server_info,
        7,
        &if2_table[-3]
    },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

static const CInterfaceProxyVtbl *cstub_ProxyVtblList[] =
{
    (const CInterfaceProxyVtbl *) &if1_proxy_vtbl,
    (const CInterfaceProxyVtbl *) &if2_proxy_vtbl,
    (const CInterfaceProxyVtbl *) &if3_proxy_vtbl,
    (const CInterfaceProxyVtbl *) &if4_proxy_vtbl,
    NULL
};

static const CInterfaceStubVtbl *cstub_StubVtblList[] =
{
    &if1_stub_vtbl,
    &if2_stub_vtbl,
    &if3_stub_vtbl,
    &if4_stub_vtbl,
    NULL
};

static PCInterfaceName const if_name_list[] =
{
    "if1",
    "if2",
    "if3",
    "if4",
    NULL
};

static const IID *base_iid_list[] =
{
    NULL,
    &IID_ITypeLib,
    NULL,
    &IID_IDispatch,
    NULL
};

#define cstub_CHECK_IID(n)     IID_GENERIC_CHECK_IID( cstub, pIID, n)

static int __stdcall iid_lookup( const IID * pIID, int * pIndex )
{
    IID_BS_LOOKUP_SETUP

    IID_BS_LOOKUP_INITIAL_TEST( cstub, 4, 4 )
    IID_BS_LOOKUP_NEXT_TEST( cstub, 2 )
    IID_BS_LOOKUP_NEXT_TEST( cstub, 1 )
    IID_BS_LOOKUP_RETURN_RESULT( cstub, 4, *pIndex )

}


static BOOL check_address(void *actual, void *expected)
{
    static void *ole32_start = NULL;
    static void *ole32_end = NULL;
    static void *combase_start = NULL;
    static void *combase_end = NULL;

    if (actual == expected)
        return TRUE;

    /* On Win7, actual can be located inside ole32.dll */
    if (ole32_start == NULL || ole32_end == NULL)
    {
        PIMAGE_NT_HEADERS nt_headers;
        ole32_start = (void *) GetModuleHandleA("ole32.dll");
        if (ole32_start == NULL)
            return FALSE;
        nt_headers = (PIMAGE_NT_HEADERS)((char *) ole32_start + ((PIMAGE_DOS_HEADER) ole32_start)->e_lfanew);
        ole32_end = (void *)((char *) ole32_start + nt_headers->OptionalHeader.SizeOfImage);
    }

    if (ole32_start <= actual && actual < ole32_end)
        return TRUE;

    /* On Win8, actual can be located inside combase.dll */
    if (combase_start == NULL || combase_end == NULL)
    {
        PIMAGE_NT_HEADERS nt_headers;
        combase_start = (void *) GetModuleHandleA("combase.dll");
        if (combase_start == NULL)
            return FALSE;
        nt_headers = (PIMAGE_NT_HEADERS)((char *) combase_start + ((PIMAGE_DOS_HEADER) combase_start)->e_lfanew);
        combase_end = (void *)((char *) combase_start + nt_headers->OptionalHeader.SizeOfImage);
    }

    return (combase_start <= actual && actual < combase_end);
}

static const ExtendedProxyFileInfo my_proxy_file_info =
{
    (const PCInterfaceProxyVtblList *) &cstub_ProxyVtblList,
    (const PCInterfaceStubVtblList *) &cstub_StubVtblList,
    (const PCInterfaceName *) &if_name_list,
    (const IID **) &base_iid_list,
    &iid_lookup,
    4,
    1,
    NULL,
    0,
    0,
    0
};

static const ProxyFileInfo *proxy_file_list[] = {
    &my_proxy_file_info,
    NULL
};


static IPSFactoryBuffer *test_NdrDllGetClassObject(void)
{
    HMODULE rpcrt4 = GetModuleHandleA("rpcrt4.dll");
    IPSFactoryBuffer *ppsf = NULL;
    const PCInterfaceProxyVtblList* proxy_vtbl;
    const PCInterfaceStubVtblList* stub_vtbl;
    const CLSID CLSID_Unknown = {0x45678, 0x1234, 0x6666, {0xff, 0x67, 0x45, 0x98, 0x76, 0x12, 0x34, 0x56}};
    static const GUID * const interfaces[] = { &IID_if1, &IID_if2, &IID_if3, &IID_if4 };
    UINT i;
    HRESULT r;
    HMODULE hmod = GetModuleHandleA("rpcrt4.dll");
    void *CStd_QueryInterface = GetProcAddress(hmod, "CStdStubBuffer_QueryInterface");
    void *CStd_AddRef = GetProcAddress(hmod, "CStdStubBuffer_AddRef");
    void *CStd_Release = GetProcAddress(hmod, "NdrCStdStubBuffer_Release");
    void *CStd_Connect = GetProcAddress(hmod, "CStdStubBuffer_Connect");
    void *CStd_Disconnect = GetProcAddress(hmod, "CStdStubBuffer_Disconnect");
    void *CStd_Invoke = GetProcAddress(hmod, "CStdStubBuffer_Invoke");
    void *CStd_IsIIDSupported = GetProcAddress(hmod, "CStdStubBuffer_IsIIDSupported");
    void *CStd_CountRefs = GetProcAddress(hmod, "CStdStubBuffer_CountRefs");
    void *CStd_DebugServerQueryInterface = GetProcAddress(hmod, "CStdStubBuffer_DebugServerQueryInterface");
    void *CStd_DebugServerRelease = GetProcAddress(hmod, "CStdStubBuffer_DebugServerRelease");

    r = NdrDllGetClassObject(&CLSID_Unknown, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             &CLSID_psfact, &PSFactoryBuffer);
    ok(r == CLASS_E_CLASSNOTAVAILABLE, "NdrDllGetClassObject with unknown clsid should have returned CLASS_E_CLASSNOTAVAILABLE instead of 0x%lx\n", r);
    ok(ppsf == NULL, "NdrDllGetClassObject should have set ppsf to NULL on failure\n");

    r = NdrDllGetClassObject(&CLSID_psfact, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             &CLSID_psfact, &PSFactoryBuffer);

    ok(r == S_OK, "ret %08lx\n", r);
    ok(ppsf != NULL, "ppsf == NULL\n");

    proxy_vtbl = PSFactoryBuffer.pProxyFileList[0]->pProxyVtblList;
    stub_vtbl = PSFactoryBuffer.pProxyFileList[0]->pStubVtblList;
    ok(PSFactoryBuffer.pProxyFileList == proxy_file_list, "pfl not the same\n");
    ok(proxy_vtbl == (PCInterfaceProxyVtblList *) &cstub_ProxyVtblList, "proxy vtbllist not the same\n");
    ok(stub_vtbl == (PCInterfaceStubVtblList *) &cstub_StubVtblList, "stub vtbllist not the same\n");

    /* if1 is non-delegating, if2 is delegating, if3 is non-delegating
       but I've zero'ed the vtbl entries, similarly if4 is delegating
       with zero'ed vtbl entries */

#define VTBL_TEST_NOT_CHANGE_TO(name, i)                                  \
    ok(stub_vtbl[i]->Vtbl.name != CStd_##name, #name "vtbl %d updated %p %p\n", \
       i, stub_vtbl[i]->Vtbl.name, CStd_##name )
#define VTBL_TEST_CHANGE_TO(name, i)                                  \
    ok(check_address(stub_vtbl[i]->Vtbl.name, CStd_##name), #name "vtbl %d not updated %p %p\n", \
       i, stub_vtbl[i]->Vtbl.name, CStd_##name )
#define VTBL_TEST_ZERO(name, i)                                  \
    ok(stub_vtbl[i]->Vtbl.name == NULL, #name "vtbl %d not null %p\n", \
       i, stub_vtbl[i]->Vtbl.name )

    VTBL_TEST_NOT_CHANGE_TO(QueryInterface, 0);
    VTBL_TEST_NOT_CHANGE_TO(AddRef, 0);
    VTBL_TEST_NOT_CHANGE_TO(Release, 0);
    VTBL_TEST_NOT_CHANGE_TO(Connect, 0);
    VTBL_TEST_NOT_CHANGE_TO(Disconnect, 0);
    VTBL_TEST_NOT_CHANGE_TO(Invoke, 0);
    VTBL_TEST_NOT_CHANGE_TO(IsIIDSupported, 0);
    VTBL_TEST_NOT_CHANGE_TO(CountRefs, 0);
    VTBL_TEST_NOT_CHANGE_TO(DebugServerQueryInterface, 0);
    VTBL_TEST_NOT_CHANGE_TO(DebugServerRelease, 0);

    VTBL_TEST_CHANGE_TO(QueryInterface, 1);
    VTBL_TEST_CHANGE_TO(AddRef, 1);
    VTBL_TEST_NOT_CHANGE_TO(Release, 1);
    VTBL_TEST_NOT_CHANGE_TO(Connect, 1);
    VTBL_TEST_NOT_CHANGE_TO(Disconnect, 1);
    VTBL_TEST_CHANGE_TO(Invoke, 1);
    VTBL_TEST_CHANGE_TO(IsIIDSupported, 1);
    VTBL_TEST_NOT_CHANGE_TO(CountRefs, 1);
    VTBL_TEST_CHANGE_TO(DebugServerQueryInterface, 1);
    VTBL_TEST_CHANGE_TO(DebugServerRelease, 1);

    VTBL_TEST_CHANGE_TO(QueryInterface, 2);
    VTBL_TEST_CHANGE_TO(AddRef, 2);
    VTBL_TEST_ZERO(Release, 2);
    VTBL_TEST_CHANGE_TO(Connect, 2);
    VTBL_TEST_CHANGE_TO(Disconnect, 2);
    VTBL_TEST_CHANGE_TO(Invoke, 2);
    VTBL_TEST_CHANGE_TO(IsIIDSupported, 2);
    VTBL_TEST_CHANGE_TO(CountRefs, 2);
    VTBL_TEST_CHANGE_TO(DebugServerQueryInterface, 2);
    VTBL_TEST_CHANGE_TO(DebugServerRelease, 2);

    VTBL_TEST_CHANGE_TO(QueryInterface, 3);
    VTBL_TEST_CHANGE_TO(AddRef, 3);
    VTBL_TEST_ZERO(Release, 3);
    VTBL_TEST_NOT_CHANGE_TO(Connect, 3);
    VTBL_TEST_NOT_CHANGE_TO(Disconnect, 3);
    VTBL_TEST_CHANGE_TO(Invoke, 3);
    VTBL_TEST_CHANGE_TO(IsIIDSupported, 3);
    VTBL_TEST_NOT_CHANGE_TO(CountRefs, 3);
    VTBL_TEST_CHANGE_TO(DebugServerQueryInterface, 3);
    VTBL_TEST_CHANGE_TO(DebugServerRelease, 3);

#define VTBL_PROXY_TEST(i,num,ptr) \
    ok( check_address(proxy_vtbl[i]->Vtbl[num], (ptr)), "wrong proxy %u func %u %p/%p\n", \
        (i), (num), proxy_vtbl[i]->Vtbl[num], (ptr) )
#define VTBL_PROXY_TEST_NOT_ZERO(i,num) \
    ok( proxy_vtbl[i]->Vtbl[num] != NULL, "wrong proxy %u func %u is NULL\n", (i), (num))

    VTBL_PROXY_TEST(0, 0, IUnknown_QueryInterface_Proxy);
    VTBL_PROXY_TEST(0, 1, IUnknown_AddRef_Proxy);
    VTBL_PROXY_TEST(0, 2, IUnknown_Release_Proxy);
    VTBL_PROXY_TEST(0, 3, if1_fn1_Proxy);
    VTBL_PROXY_TEST(0, 4, if1_fn2_Proxy);

    VTBL_PROXY_TEST(1, 0, GetProcAddress(rpcrt4,"IUnknown_QueryInterface_Proxy"));
    VTBL_PROXY_TEST(1, 1, GetProcAddress(rpcrt4,"IUnknown_AddRef_Proxy"));
    VTBL_PROXY_TEST(1, 2, GetProcAddress(rpcrt4,"IUnknown_Release_Proxy"));
    VTBL_PROXY_TEST_NOT_ZERO(1, 3);
    VTBL_PROXY_TEST_NOT_ZERO(1, 4);
    VTBL_PROXY_TEST_NOT_ZERO(1, 5);
    VTBL_PROXY_TEST_NOT_ZERO(1, 6);
    VTBL_PROXY_TEST_NOT_ZERO(1, 7);
    VTBL_PROXY_TEST_NOT_ZERO(1, 8);
    VTBL_PROXY_TEST_NOT_ZERO(1, 9);
    VTBL_PROXY_TEST_NOT_ZERO(1, 10);
    VTBL_PROXY_TEST_NOT_ZERO(1, 11);
    VTBL_PROXY_TEST_NOT_ZERO(1, 12);

    VTBL_PROXY_TEST(2, 0, IUnknown_QueryInterface_Proxy);
    VTBL_PROXY_TEST(2, 1, IUnknown_AddRef_Proxy);
    VTBL_PROXY_TEST(2, 2, IUnknown_Release_Proxy);
    VTBL_PROXY_TEST(2, 3, if1_fn1_Proxy);
    todo_wine VTBL_PROXY_TEST_NOT_ZERO(2, 4);

    VTBL_PROXY_TEST(3, 0, GetProcAddress(rpcrt4,"IUnknown_QueryInterface_Proxy"));
    VTBL_PROXY_TEST(3, 1, GetProcAddress(rpcrt4,"IUnknown_AddRef_Proxy"));
    VTBL_PROXY_TEST(3, 2, GetProcAddress(rpcrt4,"IUnknown_Release_Proxy"));
    VTBL_PROXY_TEST_NOT_ZERO(3, 3);
    VTBL_PROXY_TEST_NOT_ZERO(3, 4);
    VTBL_PROXY_TEST_NOT_ZERO(3, 5);
    VTBL_PROXY_TEST_NOT_ZERO(3, 6);

#undef VTBL_TEST_NOT_CHANGE_TO
#undef VTBL_TEST_CHANGE_TO
#undef VTBL_TEST_ZERO
#undef VTBL_PROXY_TEST
#undef VTBL_PROXY_TEST_NOT_ZERO

    for (i = 0; i < ARRAY_SIZE(interfaces); i++)
        ok( proxy_vtbl[i]->header.piid == interfaces[i],
            "wrong proxy %u iid %p/%p\n", i, proxy_vtbl[i]->header.piid, interfaces[i] );

    ok(PSFactoryBuffer.RefCount == 1, "ref count %ld\n", PSFactoryBuffer.RefCount);
    IPSFactoryBuffer_Release(ppsf);

    /* One can also search by IID */
    r = NdrDllGetClassObject(&IID_if3, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             &CLSID_psfact, &PSFactoryBuffer);
    ok(r == S_OK, "ret %08lx\n", r);
    ok(ppsf != NULL, "ppsf == NULL\n");
    IPSFactoryBuffer_Release(ppsf);

    r = NdrDllGetClassObject(&IID_if3, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             NULL, &PSFactoryBuffer);
    ok(r == S_OK, "ret %08lx\n", r);
    ok(ppsf != NULL, "ppsf == NULL\n");
    IPSFactoryBuffer_Release(ppsf);

    /* but only if the PS factory implements it */
    r = NdrDllGetClassObject(&IID_IDispatch, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             &CLSID_psfact, &PSFactoryBuffer);
    ok(r == CLASS_E_CLASSNOTAVAILABLE, "ret %08lx\n", r);

    /* Create it again to return */
    r = NdrDllGetClassObject(&CLSID_psfact, &IID_IPSFactoryBuffer, (void**)&ppsf, proxy_file_list,
                             &CLSID_psfact, &PSFactoryBuffer);
    ok(r == S_OK, "ret %08lx\n", r);
    ok(ppsf != NULL, "ppsf == NULL\n");

    /* Because this PS factory is not loaded as a dll in the normal way, Windows 8 / 10
       get confused and will crash when one of the proxies for the delegated ifaces is created.
       Registering the ifaces fixes this (in fact calling CoRegisterPSClsid() with any IID / CLSID is enough). */

    r = CoRegisterPSClsid(&IID_if1, &CLSID_psfact);
    ok(r == S_OK, "ret %08lx\n", r);
    r = CoRegisterPSClsid(&IID_if2, &CLSID_psfact);
    ok(r == S_OK, "ret %08lx\n", r);
    r = CoRegisterPSClsid(&IID_if3, &CLSID_psfact);
    ok(r == S_OK, "ret %08lx\n", r);
    r = CoRegisterPSClsid(&IID_if4, &CLSID_psfact);
    ok(r == S_OK, "ret %08lx\n", r);

    return ppsf;
}

static int base_buffer_invoke_called;
static HRESULT WINAPI base_buffer_Invoke(IRpcStubBuffer *This, RPCOLEMESSAGE *msg, IRpcChannelBuffer *channel)
{
    base_buffer_invoke_called++;
    ok(msg == (RPCOLEMESSAGE*)0xcafebabe, "msg ptr changed\n");
    ok(channel == (IRpcChannelBuffer*)0xdeadbeef, "channel ptr changed\n");
    return S_OK; /* returning any failure here results in an exception */
}

static IRpcStubBufferVtbl base_buffer_vtbl = {
    (void*)0xcafebab0,
    (void*)0xcafebab1,
    (void*)0xcafebab2,
    (void*)0xcafebab3,
    (void*)0xcafebab4,
    base_buffer_Invoke,
    (void*)0xcafebab6,
    (void*)0xcafebab7,
    (void*)0xcafebab8,
    (void*)0xcafebab9
};

static void test_NdrStubForwardingFunction(void)
{
    void *This[5];
    void *real_this;
    IRpcChannelBuffer *channel = (IRpcChannelBuffer*)0xdeadbeef;
    RPC_MESSAGE *msg = (RPC_MESSAGE*)0xcafebabe;
    DWORD *phase = (DWORD*)0x12345678;
    IRpcStubBufferVtbl *base_buffer_vtbl_ptr = &base_buffer_vtbl;
    IRpcStubBuffer *base_stub_buffer = (IRpcStubBuffer*)&base_buffer_vtbl_ptr;

    memset(This, 0xcc, sizeof(This));
    This[0] = base_stub_buffer;
    real_this = &This[1];

    NdrStubForwardingFunction( real_this, channel, msg, phase );
    ok(base_buffer_invoke_called == 1, "base_buffer_invoke called %d times\n", base_buffer_invoke_called);

}

static IRpcStubBuffer *create_stub(IPSFactoryBuffer *ppsf, REFIID iid, IUnknown *obj, HRESULT expected_result)
{
    IRpcStubBuffer *pstub = NULL;
    HRESULT r;

    r = IPSFactoryBuffer_CreateStub(ppsf, iid, obj, &pstub);
    ok(r == expected_result, "CreateStub returned %08lx expected %08lx\n", r, expected_result);
    return pstub;
}

static HRESULT WINAPI create_stub_test_QI(IUnknown *This, REFIID iid, void **ppv)
{
    ok(IsEqualIID(iid, &IID_if1), "incorrect iid\n");
    *ppv = (void*)0xdeadbeef;
    return S_OK;
}

static IUnknownVtbl create_stub_test_vtbl =
{
    create_stub_test_QI,
    NULL,
    NULL
};

static HRESULT WINAPI create_stub_test_fail_QI(IUnknown *This, REFIID iid, void **ppv)
{
    ok(IsEqualIID(iid, &IID_if1), "incorrect iid\n");
    *ppv = NULL;
    return E_NOINTERFACE;
}

static IUnknownVtbl create_stub_test_fail_vtbl =
{
    create_stub_test_fail_QI,
    NULL,
    NULL
};

struct dummy_unknown
{
    IUnknown IUnknown_iface;
    LONG ref;
};

static inline struct dummy_unknown *impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct dummy_unknown, IUnknown_iface);
}

static HRESULT WINAPI dummy_QueryInterface(IUnknown *This, REFIID iid, void **ppv)
{
    *ppv = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI dummy_AddRef(LPUNKNOWN iface)
{
    struct dummy_unknown *this = impl_from_IUnknown(iface);
    return InterlockedIncrement( &this->ref );
}

static ULONG WINAPI dummy_Release(LPUNKNOWN iface)
{
    struct dummy_unknown *this = impl_from_IUnknown(iface);
    return InterlockedDecrement( &this->ref );
}

static IUnknownVtbl dummy_unknown_vtbl =
{
    dummy_QueryInterface,
    dummy_AddRef,
    dummy_Release
};
static struct dummy_unknown dummy_unknown = { { &dummy_unknown_vtbl }, 0 };

static void create_proxy_test( IPSFactoryBuffer *ppsf, REFIID iid, const void *expected_vtbl )
{
    IRpcProxyBuffer *proxy = NULL;
    IUnknown *iface = NULL;
    HRESULT r;
    ULONG count;

    r = IPSFactoryBuffer_CreateProxy(ppsf, NULL, iid, &proxy, (void **)&iface);
    ok( r == S_OK, "IPSFactoryBuffer_CreateProxy failed %lx\n", r );
    ok( *(void **)iface == expected_vtbl, "wrong iface pointer %p/%p\n", *(void **)iface, expected_vtbl );
    count = IUnknown_Release( iface );
    ok( count == 1, "wrong refcount %lu\n", count );
    count = IRpcProxyBuffer_Release( proxy );
    ok( count == 0, "wrong refcount %lu\n", count );

    dummy_unknown.ref = 4;
    r = IPSFactoryBuffer_CreateProxy(ppsf, &dummy_unknown.IUnknown_iface, iid, &proxy,
            (void **)&iface);
    ok( r == S_OK, "IPSFactoryBuffer_CreateProxy failed %lx\n", r );
    ok( dummy_unknown.ref == 5, "wrong refcount %lu\n", dummy_unknown.ref );
    ok( *(void **)iface == expected_vtbl, "wrong iface pointer %p/%p\n", *(void **)iface, expected_vtbl );
    count = IUnknown_Release( iface );
    ok( count == 4, "wrong refcount %lu\n", count );
    ok( dummy_unknown.ref == 4, "wrong refcount %lu\n", dummy_unknown.ref );
    count = IRpcProxyBuffer_Release( proxy );
    ok( count == 0, "wrong refcount %lu\n", count );
    ok( dummy_unknown.ref == 4, "wrong refcount %lu\n", dummy_unknown.ref );
}

static void test_CreateProxy( IPSFactoryBuffer *ppsf )
{
    create_proxy_test( ppsf, &IID_if1, if1_proxy_vtbl.Vtbl );
    create_proxy_test( ppsf, &IID_if2, if2_proxy_vtbl.Vtbl );
    create_proxy_test( ppsf, &IID_if3, if3_proxy_vtbl.Vtbl );
    create_proxy_test( ppsf, &IID_if4, if4_proxy_vtbl.Vtbl );
}

static void test_CreateStub(IPSFactoryBuffer *ppsf)
{
    IUnknownVtbl *vtbl = &create_stub_test_vtbl;
    IUnknown *obj = (IUnknown*)&vtbl;
    IRpcStubBuffer *pstub = create_stub(ppsf, &IID_if1, obj, S_OK);
    CStdStubBuffer *cstd_stub = (CStdStubBuffer*)pstub;
    const CInterfaceStubHeader *header = &CONTAINING_RECORD(cstd_stub->lpVtbl, const CInterfaceStubVtbl, Vtbl)->header;

    ok(IsEqualIID(header->piid, &IID_if1), "header iid differs\n");
    ok(cstd_stub->RefCount == 1, "ref count %ld\n", cstd_stub->RefCount);
    /* 0xdeadbeef returned from create_stub_test_QI */
    ok(cstd_stub->pvServerObject == (void*)0xdeadbeef, "pvServerObject %p\n", cstd_stub->pvServerObject);
    ok(cstd_stub->pPSFactory != NULL, "pPSFactory was NULL\n");
    cstd_stub->pvServerObject = NULL;
    IRpcStubBuffer_Release(pstub);

    vtbl = &create_stub_test_fail_vtbl;
    pstub = create_stub(ppsf, &IID_if1, obj, E_NOINTERFACE);
    ok(pstub == S_OK, "create_stub failed: %lu\n", GetLastError());

}

static HRESULT WINAPI connect_test_orig_QI(IUnknown *This, REFIID iid, void **ppv)
{
    ok(IsEqualIID(iid, &IID_if1) ||
       IsEqualIID(iid, &IID_if2), "incorrect iid\n");
    *ppv = (void*)This;
    return S_OK;
}

static int connect_test_orig_release_called;
static ULONG WINAPI connect_test_orig_release(IUnknown *This)
{
    connect_test_orig_release_called++;
    return 0;
}

static IUnknownVtbl connect_test_orig_vtbl =
{
    connect_test_orig_QI,
    NULL,
    connect_test_orig_release
};

static HRESULT WINAPI connect_test_new_QI(IUnknown *This, REFIID iid, void **ppv)
{
    ok(IsEqualIID(iid, &IID_if1) ||
       IsEqualIID(iid, &IID_if2), "incorrect iid\n");
    *ppv = (void*)0xcafebabe;
    return S_OK;
}

static IUnknownVtbl connect_test_new_vtbl =
{
    connect_test_new_QI,
    NULL,
    NULL
};

static HRESULT WINAPI connect_test_new_fail_QI(IUnknown *This, REFIID iid, void **ppv)
{
    ok(IsEqualIID(iid, &IID_if1), "incorrect iid\n");
    *ppv = (void*)0xdeadbeef;
    return E_NOINTERFACE;
}

static IUnknownVtbl connect_test_new_fail_vtbl =
{
    connect_test_new_fail_QI,
    NULL,
    NULL
};

static int connect_test_base_Connect_called;
static HRESULT WINAPI connect_test_base_Connect(IRpcStubBuffer *pstub, IUnknown *obj)
{
    connect_test_base_Connect_called++;
    ok(*(void**)obj == (void*)0xbeefcafe, "unexpected obj %p\n", obj);
    return S_OK;
}

static IRpcStubBufferVtbl connect_test_base_stub_buffer_vtbl =
{
    (void*)0xcafebab0,
    (void*)0xcafebab1,
    (void*)0xcafebab2,
    connect_test_base_Connect,
    (void*)0xcafebab4,
    (void*)0xcafebab5,
    (void*)0xcafebab6,
    (void*)0xcafebab7,
    (void*)0xcafebab8,
    (void*)0xcafebab9
};

static void test_Connect(IPSFactoryBuffer *ppsf)
{
    IUnknownVtbl *orig_vtbl = &connect_test_orig_vtbl;
    IUnknownVtbl *new_vtbl = &connect_test_new_vtbl;
    IUnknownVtbl *new_fail_vtbl = &connect_test_new_fail_vtbl;
    IUnknown *obj = (IUnknown*)&orig_vtbl;
    IRpcStubBuffer *pstub = create_stub(ppsf, &IID_if1, obj, S_OK); 
    CStdStubBuffer *cstd_stub = (CStdStubBuffer*)pstub;
    IRpcStubBufferVtbl *base_stub_buf_vtbl = &connect_test_base_stub_buffer_vtbl;
    HRESULT r;

    obj = (IUnknown*)&new_vtbl;
    r = IRpcStubBuffer_Connect(pstub, obj);
    ok(r == S_OK, "r %08lx\n", r);
    ok(connect_test_orig_release_called == 1, "release called %d\n", connect_test_orig_release_called);
    ok(cstd_stub->pvServerObject == (void*)0xcafebabe, "pvServerObject %p\n", cstd_stub->pvServerObject);

    cstd_stub->pvServerObject = (IUnknown*)&orig_vtbl;
    obj = (IUnknown*)&new_fail_vtbl;
    r = IRpcStubBuffer_Connect(pstub, obj);
    ok(r == E_NOINTERFACE, "r %08lx\n", r);
    ok(cstd_stub->pvServerObject == (void*)0xdeadbeef, "pvServerObject %p\n", cstd_stub->pvServerObject);
    ok(connect_test_orig_release_called == 2, "release called %d\n", connect_test_orig_release_called);    

    /* Now use a delegated stub.

       We know from the NdrStubForwardFunction test that
       (void**)pstub-1 is the base interface stub buffer.  This shows
       that (void**)pstub-2 contains the address of a vtable that gets
       passed to the base interface's Connect method.  Note that
       (void**)pstub-2 itself gets passed to Connect and not
       *((void**)pstub-2), so it should contain the vtable ptr and not
       an interface ptr. */

    obj = (IUnknown*)&orig_vtbl;
    pstub = create_stub(ppsf, &IID_if2, obj, S_OK);
    *((void**)pstub-1) = &base_stub_buf_vtbl;
    *((void**)pstub-2) = (void*)0xbeefcafe;

    obj = (IUnknown*)&new_vtbl;
    r = IRpcStubBuffer_Connect(pstub, obj);
    ok(r == S_OK, "r %08lx\n", r);
    ok(connect_test_base_Connect_called == 1, "connect_test_bsae_Connect called %d times\n",
       connect_test_base_Connect_called);
    ok(connect_test_orig_release_called == 3, "release called %d\n", connect_test_orig_release_called);
    cstd_stub = (CStdStubBuffer*)pstub;
    ok(cstd_stub->pvServerObject == (void*)0xcafebabe, "pvServerObject %p\n", cstd_stub->pvServerObject);
}

static void test_Disconnect(IPSFactoryBuffer *ppsf)
{
    IUnknownVtbl *orig_vtbl = &connect_test_orig_vtbl;
    IUnknown *obj = (IUnknown*)&orig_vtbl;
    IRpcStubBuffer *pstub = create_stub(ppsf, &IID_if1, obj, S_OK);
    CStdStubBuffer *cstd_stub = (CStdStubBuffer*)pstub;

    connect_test_orig_release_called = 0;
    IRpcStubBuffer_Disconnect(pstub);
    ok(connect_test_orig_release_called == 1, "release called %d\n", connect_test_orig_release_called);
    ok(cstd_stub->pvServerObject == NULL, "pvServerObject %p\n", cstd_stub->pvServerObject);
    IRpcStubBuffer_Release(pstub);
}


static int release_test_psfacbuf_release_called;
static ULONG WINAPI release_test_pretend_psfacbuf_release(IUnknown *pUnk)
{
    release_test_psfacbuf_release_called++;
    return 1;
}

static IUnknownVtbl release_test_pretend_psfacbuf_vtbl =
{
    NULL,
    NULL,
    release_test_pretend_psfacbuf_release
};

static void test_Release(IPSFactoryBuffer *ppsf)
{
    LONG facbuf_refs;
    IUnknownVtbl *orig_vtbl = &connect_test_orig_vtbl;
    IUnknown *obj = (IUnknown*)&orig_vtbl;
    IUnknownVtbl *pretend_psfacbuf_vtbl = &release_test_pretend_psfacbuf_vtbl;
    IUnknown *pretend_psfacbuf = (IUnknown *)&pretend_psfacbuf_vtbl;
    IRpcStubBuffer *pstub = create_stub(ppsf, &IID_if1, obj, S_OK);
    CStdStubBuffer *cstd_stub = (CStdStubBuffer*)pstub;

    facbuf_refs = PSFactoryBuffer.RefCount;

    /* This shows that NdrCStdStubBuffer_Release doesn't call Disconnect */
    ok(cstd_stub->RefCount == 1, "ref count %ld\n", cstd_stub->RefCount);
    connect_test_orig_release_called = 0;
    IRpcStubBuffer_Release(pstub);
todo_wine {
    ok(connect_test_orig_release_called == 0, "release called %d\n", connect_test_orig_release_called);
}
    ok(PSFactoryBuffer.RefCount == facbuf_refs - 1, "factory buffer refs %ld orig %ld\n", PSFactoryBuffer.RefCount, facbuf_refs);

    /* This shows that NdrCStdStubBuffer_Release calls Release on its 2nd arg, rather than on This->pPSFactory
       (which are usually the same and indeed it's odd that _Release requires this 2nd arg). */
    pstub = create_stub(ppsf, &IID_if1, obj, S_OK);
    ok(PSFactoryBuffer.RefCount == facbuf_refs, "factory buffer refs %ld orig %ld\n", PSFactoryBuffer.RefCount, facbuf_refs);
    NdrCStdStubBuffer_Release(pstub, (IPSFactoryBuffer*)pretend_psfacbuf);
    ok(release_test_psfacbuf_release_called == 1, "pretend_psfacbuf_release called %d\n", release_test_psfacbuf_release_called);
    ok(PSFactoryBuffer.RefCount == facbuf_refs, "factory buffer refs %ld orig %ld\n", PSFactoryBuffer.RefCount, facbuf_refs);
}

static HRESULT WINAPI delegating_invoke_test_QI(ITypeLib *pUnk, REFIID iid, void** ppv)
{

    *ppv = pUnk;
    return S_OK;
}

static ULONG WINAPI delegating_invoke_test_addref(ITypeLib *pUnk)
{
    return 1;
}

static ULONG WINAPI delegating_invoke_test_release(ITypeLib *pUnk)
{
    return 1;
}

static UINT WINAPI delegating_invoke_test_get_type_info_count(ITypeLib *pUnk)
{
    return 0xabcdef;
}

static ITypeLibVtbl delegating_invoke_test_obj_vtbl =
{
    delegating_invoke_test_QI,
    delegating_invoke_test_addref,
    delegating_invoke_test_release,
    delegating_invoke_test_get_type_info_count,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};

static HRESULT WINAPI delegating_invoke_chan_query_interface(IRpcChannelBuffer *pchan,
                                                             REFIID iid,
                                                             void **ppv)
{
    ok(0, "call to QueryInterface not expected\n");
    return E_NOINTERFACE;
}

static ULONG WINAPI delegating_invoke_chan_add_ref(IRpcChannelBuffer *pchan)
{
    return 2;
}

static ULONG WINAPI delegating_invoke_chan_release(IRpcChannelBuffer *pchan)
{
    return 1;
}

static HRESULT WINAPI delegating_invoke_chan_get_buffer(IRpcChannelBuffer *pchan,
                                                        RPCOLEMESSAGE *msg,
                                                        REFIID iid)
{
    msg->Buffer = HeapAlloc(GetProcessHeap(), 0, msg->cbBuffer);
    return S_OK;
}

static HRESULT WINAPI delegating_invoke_chan_send_receive(IRpcChannelBuffer *pchan,
                                                          RPCOLEMESSAGE *pMessage,
                                                          ULONG *pStatus)
{
    ok(0, "call to SendReceive not expected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI delegating_invoke_chan_free_buffer(IRpcChannelBuffer *pchan,
                                                         RPCOLEMESSAGE *pMessage)
{
    ok(0, "call to FreeBuffer not expected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI delegating_invoke_chan_get_dest_ctx(IRpcChannelBuffer *pchan,
                                                          DWORD *pdwDestContext,
                                                          void **ppvDestContext)
{
    *pdwDestContext = MSHCTX_LOCAL;
    *ppvDestContext = NULL;
    return S_OK;
}

static HRESULT WINAPI delegating_invoke_chan_is_connected(IRpcChannelBuffer *pchan)
{
    ok(0, "call to IsConnected not expected\n");
    return E_NOTIMPL;
}

static IRpcChannelBufferVtbl delegating_invoke_test_rpc_chan_vtbl =
{
    delegating_invoke_chan_query_interface,
    delegating_invoke_chan_add_ref,
    delegating_invoke_chan_release,
    delegating_invoke_chan_get_buffer,
    delegating_invoke_chan_send_receive,
    delegating_invoke_chan_free_buffer,
    delegating_invoke_chan_get_dest_ctx,
    delegating_invoke_chan_is_connected
};

static void test_delegating_Invoke(IPSFactoryBuffer *ppsf)
{
    ITypeLibVtbl *obj_vtbl = &delegating_invoke_test_obj_vtbl;
    IUnknown *obj = (IUnknown*)&obj_vtbl;
    IRpcStubBuffer *pstub = create_stub(ppsf, &IID_if2, obj, S_OK);
    IRpcChannelBufferVtbl *pchan_vtbl = &delegating_invoke_test_rpc_chan_vtbl;
    IRpcChannelBuffer *pchan = (IRpcChannelBuffer *)&pchan_vtbl;
    HRESULT r = E_FAIL;
    RPCOLEMESSAGE msg;

    memset(&msg, 0, sizeof(msg));
    msg.dataRepresentation = NDR_LOCAL_DATA_REPRESENTATION;
    msg.iMethod = 3;
    r = IRpcStubBuffer_Invoke(pstub, &msg, pchan);
    ok(r == S_OK, "ret %08lx\n", r);
    if(r == S_OK)
    {
        ok(*(DWORD*)msg.Buffer == 0xabcdef, "buf[0] %08lx\n", *(DWORD*)msg.Buffer);
        ok(*((DWORD*)msg.Buffer + 1) == S_OK, "buf[1] %08lx\n", *((DWORD*)msg.Buffer + 1));
    }
    /* free the buffer allocated by delegating_invoke_chan_get_buffer */
    HeapFree(GetProcessHeap(), 0, msg.Buffer);
    IRpcStubBuffer_Release(pstub);
}
static const CInterfaceProxyVtbl *cstub_ProxyVtblList2[] =
{
    NULL
};

static const CInterfaceStubVtbl *cstub_StubVtblList2[] =
{
    NULL
};

static PCInterfaceName const if_name_list2[] =
{
    NULL
};

static const IID *base_iid_list2[] =
{
    NULL,
};

static const ExtendedProxyFileInfo my_proxy_file_info2 =
{
    (const PCInterfaceProxyVtblList *) &cstub_ProxyVtblList2,
    (const PCInterfaceStubVtblList *) &cstub_StubVtblList2,
    (const PCInterfaceName *) &if_name_list2,
    (const IID **) &base_iid_list2,
    &iid_lookup,
    0,
    1,
    NULL,
    0,
    0,
    0
};

static const ProxyFileInfo *proxy_file_list2[] = {
    &my_proxy_file_info2,
    NULL
};

static void test_NdrDllRegisterProxy( void )
{
    HRESULT res;
    const ExtendedProxyFileInfo *pf;
    HMODULE hmod = GetModuleHandleA(NULL);


    res = NdrDllRegisterProxy(NULL, NULL, NULL);
    ok(res == E_HANDLE, "Incorrect return code %lx\n",res);
    pf = NULL;
    res = NdrDllRegisterProxy(hmod, &pf, NULL);
    ok(res == E_NOINTERFACE, "Incorrect return code %lx\n",res);
    res = NdrDllRegisterProxy(hmod, proxy_file_list2, NULL);
    ok(res == E_NOINTERFACE, "Incorrect return code %lx\n",res);
    /* This fails on Vista and Windows 7 due to permissions */
    res = NdrDllRegisterProxy(hmod, proxy_file_list, NULL);
    ok(res == S_OK || res == E_ACCESSDENIED, "NdrDllRegisterProxy failed %lx\n",res);
    if (res == S_OK)
    {
        res = NdrDllUnregisterProxy(hmod,proxy_file_list, NULL);
        ok(res == S_OK, "NdrDllUnregisterProxy failed %lx\n",res);
    }
}

static HANDLE create_process(const char *arg)
{
    PROCESS_INFORMATION pi;
    STARTUPINFOA si = {0};
    char cmdline[200];
    char **argv;
    BOOL ret;

    si.cb = sizeof(si);
    winetest_get_mainargs(&argv);
    sprintf(cmdline, "\"%s\" %s %s", argv[0], argv[1], arg);
    ret = CreateProcessA(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    ok(ret, "CreateProcess failed: %lu\n", GetLastError());
    CloseHandle(pi.hThread);
    return pi.hProcess;
}

DEFINE_GUID(CLSID_test1,0xdeadf00d,0x0001,0x44c7,0x85,0x0f,0x2a,0x0f,0x46,0x5c,0x0c,0x6c);

static HRESULT WINAPI test1_QueryInterface(ITest1 *iface, REFIID iid, void **out)
{
    if (winetest_debug > 1) trace("%s\n", wine_dbgstr_guid(iid));
    if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_ITest1))
    {
        *out = iface;
        return S_OK;
    }
    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI test1_AddRef(ITest1 *iface)
{
    return 2;
}

static ULONG WINAPI test1_Release(ITest1 *iface)
{
    return 1;
}

static HRESULT WINAPI test1_GetClassID(ITest1 *iface, CLSID *clsid)
{
    *clsid = CLSID_test1;
    return S_OK;
}

static int WINAPI test1_square(ITest1 *iface, int x)
{
    return x * x;
}

static const ITest1Vtbl test1_vtbl =
{
    test1_QueryInterface,
    test1_AddRef,
    test1_Release,
    test1_GetClassID,
    test1_square,
};

static HRESULT WINAPI test_cf_QueryInterface(IClassFactory *iface, REFIID iid, void **out)
{
    if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IClassFactory))
    {
        *out = iface;
        return S_OK;
    }
    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI test_cf_AddRef(IClassFactory *iface)
{
    return 2;
}

static ULONG WINAPI test_cf_Release(IClassFactory *iface)
{
    return 1;
}

static HRESULT WINAPI test_cf_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID iid, void **out)
{
    ITest1 *obj = heap_alloc(sizeof(*obj));

    obj->lpVtbl = &test1_vtbl;

    return ITest1_QueryInterface(obj, iid, out);
}

static HRESULT WINAPI test_cf_LockServer(IClassFactory *iface, BOOL lock)
{
    return S_OK;
}

static const IClassFactoryVtbl test_cf_vtbl =
{
    test_cf_QueryInterface,
    test_cf_AddRef,
    test_cf_Release,
    test_cf_CreateInstance,
    test_cf_LockServer,
};

static IClassFactory test_cf = { &test_cf_vtbl };

extern CStdPSFactoryBuffer gPFactory;
extern const ProxyFileInfo * aProxyFileList;

static void local_server_proc(void)
{
    DWORD obj_cookie, ps_cookie, index;
    HANDLE stop_event, ready_event;
    IPSFactoryBuffer *ps;
    HRESULT hr;

    stop_event = OpenEventA(EVENT_ALL_ACCESS, FALSE, "wine_cstub_test_server_stop");
    ready_event = OpenEventA(EVENT_ALL_ACCESS, FALSE, "wine_cstub_test_server_ready");

    CoInitialize(NULL);

    hr = CoRegisterClassObject(&CLSID_test1, (IUnknown *)&test_cf,
        CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &obj_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = NdrDllGetClassObject(&CLSID_test_ps, &IID_IPSFactoryBuffer, (void **)&ps,
        &aProxyFileList, &CLSID_test_ps, &gPFactory);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoRegisterClassObject(&CLSID_test_ps, (IUnknown *)ps,
        CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ps_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoRegisterPSClsid(&IID_ITest1, &CLSID_test_ps);
    ok(hr == S_OK, "got %#lx\n", hr);

    SetEvent(ready_event);

    hr = CoWaitForMultipleHandles(0, 1000, 1, &stop_event, &index);
    ok(hr == S_OK, "got %#lx\n", hr);
    ok(!index, "got %lu\n", index);

    hr = CoRevokeClassObject(ps_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoRevokeClassObject(obj_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);

    CoUninitialize();
    ExitProcess(0);
}

static void test_delegated_methods(void)
{
    HANDLE process, stop_event, ready_event;
    IPSFactoryBuffer *ps;
    ITest1 *test_obj;
    DWORD ps_cookie;
    CLSID clsid;
    HRESULT hr;
    int ret;

    stop_event = CreateEventA(NULL, TRUE, FALSE, "wine_cstub_test_server_stop");
    ready_event = CreateEventA(NULL, TRUE, FALSE, "wine_cstub_test_server_ready");

    process = create_process("server");
    ok(!WaitForSingleObject(ready_event, 5000), "wait failed\n");

    hr = NdrDllGetClassObject(&CLSID_test_ps, &IID_IPSFactoryBuffer, (void **)&ps,
        &aProxyFileList, &CLSID_test_ps, &gPFactory);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoRegisterClassObject(&CLSID_test_ps, (IUnknown *)ps,
        CLSCTX_INPROC_SERVER, REGCLS_MULTIPLEUSE, &ps_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoRegisterPSClsid(&IID_ITest1, &CLSID_test_ps);
    ok(hr == S_OK, "got %#lx\n", hr);

    hr = CoCreateInstance(&CLSID_test1, NULL, CLSCTX_LOCAL_SERVER, &IID_ITest1, (void **)&test_obj);
    ok(hr == S_OK, "got %#lx\n", hr);

    ret = ITest1_square(test_obj, 3);
    ok(ret == 9, "got %d\n", ret);

    hr = ITest1_GetClassID(test_obj, &clsid);
    ok(hr == S_OK, "got %#lx\n", hr);
    ok(IsEqualGUID(&clsid, &CLSID_test1), "got %s\n", wine_dbgstr_guid(&clsid));

    ITest1_Release(test_obj);

    SetEvent(stop_event);
    ok(!WaitForSingleObject(process, 1000), "wait failed\n");

    hr = CoRevokeClassObject(ps_cookie);
    ok(hr == S_OK, "got %#lx\n", hr);
}

typedef struct tagChannelBufferRefCount
{
    IRpcChannelBuffer IRpcChannelBuffer_iface;
    LONG                                RefCount;
} CChannelBufferRefCount;

static CChannelBufferRefCount* impl_from_IRpcChannelBuffer(IRpcChannelBuffer* iface)
{
    return CONTAINING_RECORD(iface, CChannelBufferRefCount, IRpcChannelBuffer_iface);
}

static HRESULT WINAPI test_chanbuf_refcount_chan_query_interface(IRpcChannelBuffer* pchan,
    REFIID iid,
    void** ppv)
{
    if (IsEqualGUID(&IID_IRpcChannelBuffer, iid))
    {
        *ppv = pchan;
        IRpcChannelBuffer_AddRef(pchan);
        return S_OK;
    }
    return E_NOINTERFACE;
}

static ULONG WINAPI test_chanbuf_refcount_chan_add_ref(IRpcChannelBuffer* pchan)
{
    CChannelBufferRefCount* This = impl_from_IRpcChannelBuffer(pchan);
    return InterlockedIncrement(&This->RefCount);
}

static ULONG WINAPI test_chanbuf_refcount_chan_release(IRpcChannelBuffer* pchan)
{
    CChannelBufferRefCount* This = impl_from_IRpcChannelBuffer(pchan);
    return InterlockedDecrement(&This->RefCount);
}

static HRESULT WINAPI test_chanbuf_refcount_chan_get_buffer(IRpcChannelBuffer* pchan,
    RPCOLEMESSAGE* msg,
    REFIID iid)
{
    ok(0, "call to GetBuffer not expected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI test_chanbuf_refcount_chan_send_receive(IRpcChannelBuffer* pchan,
    RPCOLEMESSAGE* pMessage,
    ULONG* pStatus)
{
    ok(0, "call to SendReceive not expected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI test_chanbuf_refcount_chan_free_buffer(IRpcChannelBuffer* pchan,
    RPCOLEMESSAGE* pMessage)
{
    ok(0, "call to FreeBuffer not expected\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI test_chanbuf_refcount_chan_get_dest_ctx(IRpcChannelBuffer* pchan,
    DWORD* pdwDestContext,
    void** ppvDestContext)
{
    *pdwDestContext = MSHCTX_LOCAL;
    *ppvDestContext = NULL;
    return S_OK;
}

static HRESULT WINAPI test_chanbuf_refcount_chan_is_connected(IRpcChannelBuffer* pchan)
{
    ok(0, "call to IsConnected not expected\n");
    return E_NOTIMPL;
}

static IRpcChannelBufferVtbl test_chanbuf_refcount_test_rpc_chan_vtbl =
{
    test_chanbuf_refcount_chan_query_interface,
    test_chanbuf_refcount_chan_add_ref,
    test_chanbuf_refcount_chan_release,
    test_chanbuf_refcount_chan_get_buffer,
    test_chanbuf_refcount_chan_send_receive,
    test_chanbuf_refcount_chan_free_buffer,
    test_chanbuf_refcount_chan_get_dest_ctx,
    test_chanbuf_refcount_chan_is_connected
};

static void test_ChannelBufferRefCount(IPSFactoryBuffer *ppsf)
{
    IRpcProxyBuffer* proxy_buffer = NULL;
    IUnknown* proxy_if1 = NULL;
    CChannelBufferRefCount test_chanbuf = {{&test_chanbuf_refcount_test_rpc_chan_vtbl}, 1};

    RPC_MESSAGE rpcMessage = {0};
    MIDL_STUB_MESSAGE stubMessage = {0};
    MIDL_STUB_DESC stubDesc = {0};
    ULONG refs;

    HRESULT hr = IPSFactoryBuffer_CreateProxy(ppsf, NULL, &IID_if1, &proxy_buffer, (void**)&proxy_if1);
    ok(hr == S_OK, "got %#lx\n", hr);

    ok(test_chanbuf.RefCount == 1, "got %ld\n", test_chanbuf.RefCount);
    hr = IRpcProxyBuffer_Connect(proxy_buffer, &test_chanbuf.IRpcChannelBuffer_iface);
    ok(hr == S_OK, "got %#lx\n", hr);
    /* proxy_buffer should have acquired its own refcount on test_chanbuf */
    ok(test_chanbuf.RefCount == 2, "got %ld\n", test_chanbuf.RefCount);

    /* which therefore survives releasing the initial one */
    refs = IRpcChannelBuffer_Release(&test_chanbuf.IRpcChannelBuffer_iface);
    ok(refs == 1, "got %ld\n", refs);

    NdrProxyInitialize(proxy_if1, &rpcMessage, &stubMessage, &stubDesc, 0);
    /* stubMessage should add its own refcount on test_chanbuf */
    ok(test_chanbuf.RefCount == 2, "got %ld\n", test_chanbuf.RefCount);
    ok(stubMessage.pRpcChannelBuffer != NULL, "NULL pRocChannelBuffer\n");

    /* stubMessage doesn't add its own refcounts on proxy_if1 or proxy_buffer,
     * so it's possible these are freed out from under it.
     * E.g. an event sink might unadvise upon receiving the event it was waiting for;
     * this unadvise could be reentrant to Invoke because SendReceive pumps STA messages.
     * The source would then erase that connection point entry and Release the proxy. */
    IRpcProxyBuffer_Disconnect(proxy_buffer);
    ok(test_chanbuf.RefCount == 1, "got %ld\n", test_chanbuf.RefCount);
    IRpcProxyBuffer_Release(proxy_buffer);
    refs = IUnknown_Release(proxy_if1);
    ok(refs == 0, "got %ld\n", refs);
    ok(test_chanbuf.RefCount == 1, "got %ld\n", test_chanbuf.RefCount);

    /* NdrProxyFreeBuffer must not dereference the now-freed proxy_if1,
     * yet should still free the remaining reference on test_chanbuf */
    NdrProxyFreeBuffer(proxy_if1, &stubMessage);
    ok(test_chanbuf.RefCount == 0, "got %ld\n", test_chanbuf.RefCount);
    ok(!stubMessage.pRpcChannelBuffer, "dangling pRpcChannelBuffer = %p\n", stubMessage.pRpcChannelBuffer);
}

START_TEST( cstub )
{
    IPSFactoryBuffer *ppsf;
    int argc;
    char **argv;

    argc = winetest_get_mainargs( &argv );
    if (argc > 2 && !strcmp(argv[2], "server"))
    {
        local_server_proc();
        return;
    }

    OleInitialize(NULL);

    ppsf = test_NdrDllGetClassObject();
    test_NdrStubForwardingFunction();
    test_CreateProxy(ppsf);
    test_CreateStub(ppsf);
    test_Connect(ppsf);
    test_Disconnect(ppsf);
    test_Release(ppsf);
    test_delegating_Invoke(ppsf);
    test_NdrDllRegisterProxy();
    test_delegated_methods();
    test_ChannelBufferRefCount(ppsf);

    OleUninitialize();
}
