windows - COM asynkront opkald respekterer ikke meddelelsesfilteret

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg har en STA COM objekt, der implementerer en brugerdefineret interface. Min brugerdefinerede grænseflade har en brugerdefineret proxy stub, der blev bygget fra koden genereret af MIDL-compiler. Jeg vil gerne kunne asynkront foretage opkald til grænsefladen fra andre lejligheder. Jeg finder ud af, at de synkroniserede grænsefladeopkald respekterer OLE-meddelelsesfilteret på opkaldstråden, men de asynkrone grænsefladeopkald gør det ikke. Det betyder, at COM asynkrone opkald ikke kan bruges på en brand og glemme måde, hvis den indkaldende lejlighed har en meddelelsesfilter, der foreslår, at du ringer på opkaldet senere.


Forventes dette? Er der nogen måde omkring dette andet end ikke at bruge et meddelelsesfilter, der ikke bruger brand og glem operationer eller har en separat homegrown komponent bare til at håndtere brand og glem operationer?


For nedenstående kode er MessageFilter en simpel implementering af IMessageFilter, der ringer opkald til lambdas. Hvis jeg ikke bruger meddelelsesfiltre, fungerer de synkrone og asynkrone opkald fint. Hvis jeg bruger meddelelsesfiltrerne vist nedenfor, virker det synkrone opkald (efter at STA-meddelelsesfilteret stopper med at returnere SERVERCALL\_RETRYLATER), men det asynkrone opkald straks fejler og forsøger aldrig igen.


Den vigtigste STA har et meddelelsesfilter, der forsvinder i en vis periode.


// establish deferral time
chrono::time\_point<chrono::system\_clock> defer\_until = ...;

// create message filter
auto message\_filter = new MessageFilter;
message\_filter->AddRef();
message\_filter->handle\_incoming\_call
    = [defer\_until](DWORD, HTASK, DWORD, LPINTERFACEINFO)
      {
          return chrono::high\_resolution\_clock::now() >= defer\_until
              ? SERVERCALL\_ISHANDLED
              : SERVERCALL\_RETRYLATER;
      };

// register message filter
CoRegisterMessageFilter(message\_filter, nullptr);


En anden STA opretter sit eget meddelelsesfilter for at fortælle COM at prøve igen.


// create message filter
auto message\_filter = new MessageFilter;
message\_filter->AddRef();
message\_filter->retry\_rejected\_call
    = [](HTASK, DWORD, DWORD)
      {
          return 0; // retry immediately
      };

// register message filter
CoRegisterMessageFilter(message\_filter, nullptr);


I den sekundære STA får jeg en proxy for objektgrænsefladen fra hoved STA.


// get global interface table
IGlobalInterfaceTablePtr global\_interface\_table;
global\_interface\_table.CreateInstance(CLSID\_StdGlobalInterfaceTable);

// get interface reference
IMyInterfacePtr object\_interface;
global\_interface\_table->GetInterfaceFromGlobal(cookie, \_\_uuidof(IMyInterface), reinterpret\_cast<LPVOID*>(&object\_interface)));


Dette virker:


// execute synchronously
HRESULT hr = object\_interface->SomeMethod();

/* final result, after the deferral period: hr == S\_OK */


Dette virker ikke:


// get call factory
ICallFactoryPtr call\_factory;
object\_interface->QueryInterface(&call\_factory);

// create async call
AsyncIMyInterfacePtr async\_call;
call\_factory->CreateCall(\_\_uuidof(AsyncIMyInterface), nullptr, \_\_uuidof(AsyncIMyInterface), reinterpret\_cast<LPUNKNOWN*>(&async\_call)));

// begin executing asynchronously
async\_call->Begin\_SomeMethod();

// end executing asynchronously
HRESULT hr = async\_call->Finish\_SomeMethod();

/* final result, immediate: hr == RPC\_E\_SERVERCALL\_RETRYLATER */

Bedste reference