an example Re: Connecting to running win32com server

Alex Martelli aleaxit at yahoo.com
Wed Oct 11 05:45:08 EDT 2000


"Alex Martelli" <aleaxit at yahoo.com> wrote in message
news:8rvqku093a at news1.newsguy.com...
    [snip]
> > things to communicate, but it seems that the C++ program always starts
> > a new python process to handle the request, rather than using the
> > already running one.  This won't work for me, as the purpose is to
> > grab data being collected by the already running python script.
> >
> > Is there some way around this, other than tricks like shared memory
> > between the running python process and the win32com server?  The
> > C++ code and the data collection program run on different computers.
>
> Sure.  The server must register itself with the system as
> REGCLS_MULTIPLEUSE (in the CoRegisterClassObject API call),
> so that only one instance of its process will be run (and the
> classobject it registers must return either references to one
> and the same instance [singleton], or, my preference [for
> reasons that may be slightly obscure to explain], instances
> that all share their state [delegating it to a shared singleton
> internal non-com-exposed object in the process]).
>
> (I thought that's what win32com.server.factory did by
> default... the multi-use part, I mean... are you sure that
> multiple _processes_ are starting, rather than just there
> being multiple instances within the same Python process?)

I checked, and this is indeed so -- local servers created
by Python are indeed registered as multiple-use.

So, your problem is probably related to actually having
multiple instances (within the same Python process!), not,
as it seemed to you, to having multiple processes.

Here's a test you can try.  Go to the win32com/servers
directory and copy, for example, dictionary.py to a file
named shareddictionary.py in the same directory.  Then,
do the following changes:

add a line
shared_dict = {}
before the definition of class DictionaryPolicy;

change the classes' registration attributes slightly, to:
  _reg_desc_ = 'Python Shared-Dictionary'
  _reg_clsid_ = '{8760aa40-9f4e-11d4-9e41-0060b0eb1d67}'
  _reg_progid_ = 'Python.SharedDictionary'
  _reg_verprogid_ = 'Python.SharedDictionary.1'
  _reg_policy_spec_ = 'win32com.servers.shareddictionary.DictionaryPolicy'

change the very first line of its _CreateInstance method to:
    self._wrap_(shared_dict)
(instead of similarly wrapping a new empty dictionary)

and run the script with /regserver to register it.


Basically, what we're doing here is, let a new COM object
be generated (from the same class-factory, i.e. in the same
process, thanks to the multiple-use registration of the
class-factory object that the Python infrastructure will
do for us) for each request; BUT, let all the COM objects
thus generate SHARE STATE, since they all wrap the same
underlying process-unique Python object (the shared_dict)
rather than each wrapping a separate instance.

You _could_ do it differently (caching and returning the
same COM object every time a create-instance is requested)
but, personally, I prefer this style (there are issues with
COM singletons, and with singletons in general, that are
well finessed by this alternate shared-state-pattern).

Now, in some long-running Python process, e.g. in IDLE, do:

>>> import win32com.client
>>> import pythoncom
>>>
sd1=win32com.client.Dispatch("Python.SharedDictionary",clsctx=pythoncom.CLSC
TX_LOCAL_SERVER)
>>> sd1["foo"]="Bar"

and from another process, e.g. PythonWin, you can see that
you're getting to the same server process:

>>>
sd1=win32com.client.Dispatch("Python.SharedDictionary",clsctx=pythoncom.CLSC
TX_LOCAL_SERVER)
>>> sd1
<COMObject Python.SharedDictionary>
>>> sd1.Count
1
>>> sd1.Item("Foo")
u'Bar'
>>>

Here's a simple VC++ console application to check the same
thing:

#include "stdafx.h"

#undef GetFreeSpace
#import "scrrun.dll" rename("FreeSpace","SpaceFree")

int step = 0;

int work(int argc, char* argv[])
{
    HRESULT hr;
    CLSID clsid;
++step;
    hr = CLSIDFromProgID(L"Python.SharedDictionary", &clsid);
    if(FAILED(hr)) _com_raise_error(hr);
++step;
    IClassFactoryPtr pClassFactory;
    hr = CoGetClassObject(clsid,CLSCTX_LOCAL_SERVER,NULL,
        IID_IClassFactory,(void**)&pClassFactory);
    if(FAILED(hr)) _com_raise_error(hr);
++step;
    IDispatchPtr pDisp;
    hr = pClassFactory->CreateInstance(NULL,IID_IDispatch,(void**)&pDisp);
    if(FAILED(hr)) _com_raise_error(hr);
++step;
    printf("Created OK\n");

    LPOLESTR name = L"Count";
    DISPID dispid = DISPID_UNKNOWN;
    hr = pDisp->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_NEUTRAL, &dispid);
    if(FAILED(hr) || dispid==DISPID_UNKNOWN) _com_raise_error(hr?hr:13);
++step;

    VARIANT varResult; VariantInit(&varResult);
    DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

    hr = pDisp->Invoke(dispid, IID_NULL, LOCALE_NEUTRAL,
        DISPATCH_METHOD|DISPATCH_PROPERTYGET,
        &dispparamsNoArgs, &varResult, NULL, NULL);
    if(FAILED(hr)) _com_raise_error(hr);
++step;
    hr = VariantChangeType(&varResult, &varResult, 0, VT_I4);
    if(FAILED(hr)) _com_raise_error(hr);
++step;
    printf("Count is %d\n", varResult.lVal);

    VariantClear(&varResult);

    return 0;
}

int main(int argc, char* argv[])
{
    CoInitialize(0);
    int rc = 0;
    try {
        rc = work(argc, argv);
        printf("Terminated OK, rc=%d\n", rc);
    } catch(_com_error& er) {
        const char* ermes = er.ErrorMessage();
        printf("step=%d, COM error (%x): %s\n",
            step, er.Error(), ermes);
    }
    CoUninitialize();

 return rc;
}


Building and running this will also confirm that you're accessing
the same underlying dictionary-object (you can double check by
runs of this with the sd1 "dictionary" in various states, even
though the only thing this shows is the "Count"...).


Let us know...

Alex






More information about the Python-list mailing list