2
votes

Suppose I need to implement an out-proc COM server and all the COM-interfaces are Automation-compatible. I can either create and register a proxy/stub combination or create and register a type library and rely on Automation marshaller.

I'm well aware of all the maintenance aspects of the two approaches. This question is about runtime performance only.

I can't find any hard data - only claims like

  1. "Automation marshaller is generic, so it is slower" which I won't believe right away because there're just several Automation-compatible types and so switching between them is not that hard

  2. "Automation marshaller will have to load the type library" which is fair point, but this will have to be done only once and if I have hundred thousand COM-calls afterwards I don't care much of that one-time overhead

Is there any measurement data on which - proxy/stub marshaling or typelib marshaling - is faster in the long run?

1

1 Answers

1
votes

Once the proxy is created, the performance of PSOAInterface should be identical to that of a /Oicf proxy/stub library, according to Don Box in Essential Com (p. 228) and his Microsoft Systems Journal article (Jan 1999).

But as of Windows 8.1, the creation of the PSOAInterface proxies can be very sub-optimal. Don Box claims in the above article that combase!CreateProxyFromTypeInfo and the type library marshaler perform caching. However, in my testing the type library is reloaded from file after every time you release all your interfaces. This is a significant problem with the UIAutomation library, which uses the IGlobalInterfaceTable extensively, causing a bunch of extra system calls.

Here is my test. One apartment instantiates a coclass, and then another apartment unmarshals it into proxies 10000 times. It takes about 5s when using PSOAInterface. If I instead create and register a MIDL proxy/stub, it only takes about 100ms.

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <comdef.h>

// RemoteProxyFactory32 from oleacc.dll {53362c64-a296-4f2d-a2f8-fd984d08340b}
static IID CLSID_RemoteProxyFactory32 = GUID{ 0x53362c64, 0xa296, 0x4f2d, { 0xa2, 0xf8, 0xfd, 0x98, 0x4d, 0x08, 0x34, 0x0b } };
// IRemoteProxyFactory from oleacc.dll {8628f27d-64a2-4ed6-906b-e6155314c16a}
static IID IID_IRemoteProxyFactory = GUID{ 0x8628f27d, 0x64a2, 0x4ed6, { 0x90, 0x6b, 0xe6, 0x15, 0x53, 0x14, 0xc1, 0x6a } };

struct register_interface_thread {
    HANDLE hInterfaceRegistered;
    DWORD dwCookie;
    HANDLE hShouldClose;
};
DWORD WINAPI RegisterInterfaceThread(_In_  LPVOID lpParameter) {
    struct register_interface_thread& state = *(struct register_interface_thread*)(lpParameter);
    HRESULT hr;
    if (FAILED(hr = CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
        fprintf(stderr, "Error CoInitializeEx: 0x%08x\n", hr);
    }
    else {
        IUnknown *pv;
        if (FAILED(hr = CoCreateInstance(CLSID_RemoteProxyFactory32, NULL, CLSCTX_LOCAL_SERVER, IID_IRemoteProxyFactory, (LPVOID*)&pv))) {
            fprintf(stderr, "CocCreateInstance(RemoteProxyFactory32 of oleacc.dll) failed with hresult 0x%x\n", hr);
        }
        else {
            IGlobalInterfaceTable *pIGlobalInterfaceTable;
            if (FAILED(hr = CoCreateInstance
                (
                CLSID_StdGlobalInterfaceTable,
                NULL,
                CLSCTX_INPROC_SERVER,
                IID_IGlobalInterfaceTable,
                (void **)&pIGlobalInterfaceTable
                ))) {
                fprintf(stderr, "CocCreateInstance(StdGlobalInterfaceTable) failed with hresult 0x%x\n", hr);
            }
            else {
                DWORD dwCookie;
                if (FAILED(hr = pIGlobalInterfaceTable->RegisterInterfaceInGlobal(pv, IID_IRemoteProxyFactory, &dwCookie))) {
                    fprintf(stderr, "RegisterInterfaceInGlobal failed with hresult 0x%x\n", hr);
                }
                else {
                    fprintf(stdout, "Successfully registered interface; cookie=0x%x\n", dwCookie);
                    state.dwCookie = dwCookie;
                    if (!SetEvent(state.hInterfaceRegistered)) {
                        DWORD err = GetLastError();
                        fprintf(stderr, "Error SetEvent(hInterfaceRegistered): 0x%x\n", err);
                    }
                    else {
                        DWORD waitResult;
                        if (WAIT_OBJECT_0 != (waitResult = WaitForSingleObject(state.hShouldClose, INFINITE))) {
                            DWORD err = GetLastError();
                            fprintf(stderr, "Error WaitForSingleObject: returned 0x%x; error=0x%08x\n", waitResult, err);
                            hr = err;
                        }
                        else {
                            fprintf(stdout, "Successfully joined thread; dwCookie=0x%x\n", state.dwCookie);
                        }
                    }
                }
                pIGlobalInterfaceTable->Release();
            }
            if (pv != NULL)
                pv->Release();
        }
        CoUninitialize();
        fprintf(stdout, "Thread going away\n");
    }
    return 0;
}

int main(int argc, char* argv[]) {
    HRESULT hr;
    if (FAILED(hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) {
        fprintf(stderr, "Error CoInitializeEx: 0x%08x\n", hr);
    }
    else {
        struct register_interface_thread state;
        state.dwCookie = 0;
        state.hInterfaceRegistered = CreateEventEx(NULL, TEXT("hInterfaceRegistered"), CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
        state.hShouldClose = CreateEventEx(NULL, TEXT("hShouldClose"), CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);

        HANDLE hThread = CreateThread(NULL, 0, RegisterInterfaceThread, &state, 0, NULL);
        if (hThread == NULL) {
            DWORD err = GetLastError();
            fprintf(stderr, "Error CreateThread: 0x%08x\n", err);
            hr = err;
        }
        else {
            DWORD waitResult;
            if (WAIT_OBJECT_0 != (waitResult = WaitForSingleObject(state.hInterfaceRegistered, INFINITE))) {
                DWORD err = GetLastError();
                fprintf(stderr, "Error WaitForSingleObject: returned 0x%x; error=0x%08x\n", waitResult, err);
                hr = err;
            }
            else {
                fprintf(stdout, "Successfully waited for hInterfaceRegistered; dwCookie=0x%x\n", state.dwCookie);
                IGlobalInterfaceTable *pIGlobalInterfaceTable;
                if (FAILED(hr = CoCreateInstance
                    (
                    CLSID_StdGlobalInterfaceTable,
                    NULL,
                    CLSCTX_INPROC_SERVER,
                    IID_IGlobalInterfaceTable,
                    (void **)&pIGlobalInterfaceTable
                    ))) {
                    fprintf(stderr, "CoCreateInstance(StdGlobalInterfaceTable) failed with hresult 0x%x\n", hr);
                }
                else {
                    IUnknown *pv = NULL;
                    DWORD start_time = GetTickCount();
                    DWORD i;
                    for (i = 0; i != 10000; i++) {
                        if (FAILED(hr = pIGlobalInterfaceTable->GetInterfaceFromGlobal(state.dwCookie, IID_IRemoteProxyFactory, (LPVOID*)&pv))) {
                            fprintf(stderr, "GetInterfaceFromGlobal failed with hresult 0x%x\n", hr);
                            break;
                        }
                        else {
                            pv->Release();
                        }
                    }
                    DWORD end_time = GetTickCount();
                    DWORD difference = end_time - start_time;
                    fprintf(stdout, "%u iterations completed in %ums\n", i, difference);
                    pIGlobalInterfaceTable->Release();
                }
                if (!SetEvent(state.hShouldClose)) {
                    DWORD err = GetLastError();
                    fprintf(stderr, "SetEvent(hShouldClose) failed; err=0x%x\n", err);
                    hr = err;
                }
                else {
                    if (WAIT_OBJECT_0 != (waitResult = WaitForSingleObject(hThread, INFINITE))) {
                        DWORD err = GetLastError();
                        fprintf(stderr, "Error WaitForSingleObject(hThread): returned 0x%x; error=0x%08x\n", waitResult, err);
                        hr = err;
                    }
                    else {
                        printf("successfully joined thread.\n");
                    }
                }
            }
        }
    }
    return hr;
}

Running it in windbg confirms that it reloads the type library 10000 times.

bp KERNELBASE!CreateFileW "r $t0 = @$t0 + 1; g"
bp OLEAUT32!LoadTypeLibEx "r $t1 = @$t1 + 1; g"
g
r $t0, $t1
$t0=000000000000c35c $t1=0000000000002712