[OLE #78] Run RPCs on a new thread client side

Mike Hearn mh at codeweavers.com
Thu Mar 3 09:39:52 CST 2005


We should use thread pooling in future, but let's get it working first
and worry about speed later. Thanks to Rob for the tests.

Almost there ....

Rob Shearman <rob at codeweavers.com>
Mike Hearn <mh at codeweavers.com>
- Add re-entrancy tests to the test suite
- Run RPCs on a new thread client side so we can pump the message loop.
-------------- next part --------------
--- dlls/ole32/tests/marshal.c  (revision 144)
+++ dlls/ole32/tests/marshal.c  (revision 145)
@@ -1203,6 +1203,97 @@ static void test_stubbuffer(REFIID riid)
     ok(refs == 0, "Ref-count leak of %ld on IRpcProxyBuffer\n", refs);
 }
 
+static HWND hwnd_app;
+
+static HRESULT WINAPI TestRE_IClassFactory_CreateInstance(
+    LPCLASSFACTORY iface,
+    LPUNKNOWN pUnkOuter,
+    REFIID riid,
+    LPVOID *ppvObj)
+{
+    DWORD res;
+    BOOL ret = SendMessageTimeout(hwnd_app, WM_NULL, 0, 0, SMTO_BLOCK, 5000, &res);
+    ok(ret, "Timed out sending a message to originating window during RPC call\n");
+    return S_FALSE;
+}
+
+static IClassFactoryVtbl TestREClassFactory_Vtbl =
+{
+    Test_IClassFactory_QueryInterface,
+    Test_IClassFactory_AddRef,
+    Test_IClassFactory_Release,
+    TestRE_IClassFactory_CreateInstance,
+    Test_IClassFactory_LockServer
+};
+
+IClassFactory TestRE_ClassFactory = { &TestREClassFactory_Vtbl };
+
+static LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+    switch (msg)
+    {
+    case WM_USER:
+    {
+        HRESULT hr;
+        IStream *pStream = NULL;
+        IClassFactory *proxy = NULL;
+        IUnknown *object;
+        DWORD tid;
+        HANDLE thread;
+
+        cLocks = 0;
+
+        hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream);
+        ok_ole_success(hr, CreateStreamOnHGlobal);
+        tid = start_host_object(pStream, &IID_IClassFactory, (IUnknown*)&TestRE_ClassFactory, MSHLFLAGS_NORMAL, &thread);
+
+        ok_more_than_one_lock();
+
+        IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL);
+        hr = CoUnmarshalInterface(pStream, &IID_IClassFactory, (void **)&proxy);
+        ok_ole_success(hr, CoReleaseMarshalData);
+        IStream_Release(pStream);
+
+        ok_more_than_one_lock();
+
+        hr = IClassFactory_CreateInstance(proxy, NULL, &IID_IUnknown, (void **)&object);
+
+        IClassFactory_Release(proxy);
+
+        ok_no_locks();
+
+        end_host_object(tid, thread);
+
+        PostMessage(hwnd, WM_QUIT, 0, 0);
+
+        return 0;
+    }
+    default:
+        return DefWindowProc(hwnd, msg, wparam, lparam);
+    }
+}
+
+static void test_message_reentrancy()
+{
+    WNDCLASS wndclass;
+    MSG msg;
+
+    memset(&wndclass, 0, sizeof(wndclass));
+    wndclass.lpfnWndProc = window_proc;
+    wndclass.lpszClassName = "WineCOMTest";
+    RegisterClass(&wndclass);
+
+    hwnd_app = CreateWindow("WineCOMTest", NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, 0);
+    ok(hwnd_app != NULL, "Window creation failed\n");
+
+    PostMessage(hwnd_app, WM_USER, 0, 0);
+
+    while (GetMessage(&msg, NULL, 0, 0))
+    {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+}
 
 /* doesn't pass with Win9x COM DLLs (even though Essential COM says it should) */
 #if 0
@@ -1222,6 +1313,97 @@ static void UnlockModuleOOP()
         SetEvent(heventShutdown);
 }
 
+static HWND hwnd_app;
+
+static HRESULT WINAPI TestRE_IClassFactory_CreateInstance(
+    LPCLASSFACTORY iface,
+    LPUNKNOWN pUnkOuter,
+    REFIID riid,
+    LPVOID *ppvObj)
+{
+    DWORD res;
+    BOOL ret = SendMessageTimeout(hwnd_app, WM_NULL, 0, 0, SMTO_BLOCK, 500, &res);
+    todo_wine { ok(ret, "Timed out sending a message to originating window during RPC call\n"); }
+    return S_FALSE;
+}
+
+static IClassFactoryVtbl TestREClassFactory_Vtbl =
+{
+    Test_IClassFactory_QueryInterface,
+    Test_IClassFactory_AddRef,
+    Test_IClassFactory_Release,
+    TestRE_IClassFactory_CreateInstance,
+    Test_IClassFactory_LockServer
+};
+
+IClassFactory TestRE_ClassFactory = { &TestREClassFactory_Vtbl };
+
+static LRESULT CALLBACK window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+    switch (msg)
+    {
+    case WM_USER:
+    {
+        HRESULT hr;
+        IStream *pStream = NULL;
+        IClassFactory *proxy = NULL;
+        IUnknown *object;
+        DWORD tid;
+        HANDLE thread;
+
+        cLocks = 0;
+
+        hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream);
+        ok_ole_success(hr, CreateStreamOnHGlobal);
+        tid = start_host_object(pStream, &IID_IClassFactory, (IUnknown*)&TestRE_ClassFactory, MSHLFLAGS_NORMAL, &thread);
+
+        ok_more_than_one_lock();
+
+        IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL);
+        hr = CoUnmarshalInterface(pStream, &IID_IClassFactory, (void **)&proxy);
+        ok_ole_success(hr, CoReleaseMarshalData);
+        IStream_Release(pStream);
+
+        ok_more_than_one_lock();
+
+        hr = IClassFactory_CreateInstance(proxy, NULL, &IID_IUnknown, (void **)&object);
+
+        IClassFactory_Release(proxy);
+
+        ok_no_locks();
+
+        end_host_object(tid, thread);
+
+        PostMessage(hwnd, WM_QUIT, 0, 0);
+
+        return 0;
+    }
+    default:
+        return DefWindowProc(hwnd, msg, wparam, lparam);
+    }
+}
+
+static void test_message_reentrancy()
+{
+    WNDCLASS wndclass;
+    MSG msg;
+
+    memset(&wndclass, 0, sizeof(wndclass));
+    wndclass.lpfnWndProc = window_proc;
+    wndclass.lpszClassName = "WineCOMTest";
+    RegisterClass(&wndclass);
+
+    hwnd_app = CreateWindow("WineCOMTest", NULL, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, 0);
+    ok(hwnd_app != NULL, "Window creation failed\n");
+
+    PostMessage(hwnd_app, WM_USER, 0, 0);
+
+    while (GetMessage(&msg, NULL, 0, 0))
+    {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+}
 
 static HRESULT WINAPI TestOOP_IClassFactory_QueryInterface(
     LPCLASSFACTORY iface,
@@ -1394,7 +1576,7 @@ START_TEST(marshal)
     test_proxy_interfaces();
     test_stubbuffer(&IID_IClassFactory);
     /* FIXME: test GIT */
-    /* FIXME: test COM re-entrancy */
+    test_message_reentrancy();
 
 /*    test_out_of_process_com(); */
     CoUninitialize();
--- dlls/ole32/rpc.c  (revision 144)
+++ dlls/ole32/rpc.c  (revision 145)
@@ -187,18 +187,98 @@ static HRESULT WINAPI ClientRpcChannelBu
     return HRESULT_FROM_WIN32(status);
 }
 
+struct rpc_sendreceive_params
+{
+    RPC_MESSAGE *msg;
+    RPC_STATUS   status;
+};
+
+/* this thread runs an outgoing RPC */
+static DWORD WINAPI rpc_sendreceive_thread(LPVOID param)
+{
+    struct rpc_sendreceive_params *data = (struct rpc_sendreceive_params *) param;
+    
+    TRACE("starting up\n");
+
+    /* FIXME: trap and rethrow RPC exceptions in app thread */
+    data->status = I_RpcSendReceive(data->msg);
+
+    TRACE("completed with status 0x%lx\n", data->status);
+    
+    return 0;
+}
+
 static HRESULT WINAPI RpcChannelBuffer_SendReceive(LPRPCCHANNELBUFFER iface, RPCOLEMESSAGE *olemsg, ULONG *pstatus)
 {
     RPC_MESSAGE *msg = (RPC_MESSAGE *)olemsg;
+    HRESULT hr = S_OK;
+    HANDLE thread;
+    struct rpc_sendreceive_params *params;
+    DWORD tid, res;
     RPC_STATUS status;
-    HRESULT hr;
-
+    
     TRACE("(%p)\n", msg);
 
-    status = I_RpcSendReceive(msg);
+    params = HeapAlloc(GetProcessHeap(), 0, sizeof(*params));
+    if (!params) return E_OUTOFMEMORY;
+    
+    params->msg = msg;
+
+    /* we use a separate thread here because we need to be able to
+     * pump the message loop in the application thread: if we do not,
+     * any windows created by this thread will hang and RPCs that try
+     * and re-enter this STA from an incoming server thread will
+     * deadlock. InstallShield is an example of that.
+     */
+    
+    thread = CreateThread(NULL, 0, rpc_sendreceive_thread, params, 0, &tid);
+    if (!thread)
+    {
+        ERR("Could not create RpcSendReceive thread, error %lx\n", GetLastError());
+        return E_UNEXPECTED;
+    }
 
+    while (TRUE)
+    {
+        TRACE("waiting for rpc completion or window message\n");
+        res = MsgWaitForMultipleObjectsEx(1, &thread, INFINITE, QS_ALLINPUT, 0);
+        
+        if (res == WAIT_OBJECT_0 + 1)  /* messages available */
+        {
+            MSG message;
+            while (PeekMessageW(&message, NULL, 0, 0, PM_REMOVE))
+            {
+                /* FIXME: filter the messages here */
+                if (message.message == DM_EXECUTERPC)
+                    TRACE("received DM_EXECUTRPC dispatch request, re-entering ...\n");
+                else
+                    TRACE("received message whilst waiting for RPC: 0x%x\n", message.message);
+                TranslateMessage(&message);
+                DispatchMessageW(&message);
+            }
+        }
+        else if (res == WAIT_OBJECT_0) 
+        {
+            break; /* RPC is completed */
+        }
+        else
+        {
+            ERR("Unexpected wait termination: %ld, %ld\n", res, GetLastError());
+            hr = E_UNEXPECTED;
+            break;
+        }
+    }
+
+    CloseHandle(thread);
+
+    status = params->status;
+    HeapFree(GetProcessHeap(), 0, params);
+    params = NULL;
+    if (hr) return hr;
+    
     if (pstatus) *pstatus = status;
 
+    TRACE("RPC call status: 0x%lx\n", status);
     if (status == RPC_S_OK)
         hr = S_OK;
     else if (status == RPC_S_CALL_FAILED)


More information about the wine-patches mailing list