2

How can I implement IServiceProvider in the class which I inherit other interfaces from so their methods actually get called? Right now I always get E_NOINTERFACE back from QueryInterface.

    TPassthrough = class(TComObject, IInternetProtocolRoot, IInternetProtocolSink, IInternetProtocol, IServiceProvider)
    private
      FDefaultSink: IInternetProtocol;
      FProtSink: IInternetProtocolSink;
      FBindInfo: IInternetBindInfo;
    public
    { IServiceProvider }
    function QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;

    { IInternetProtocolSink }
    function Switch(const ProtocolData: TProtocolData): HResult; stdcall;
    function ReportProgress(ulStatusCode: ULONG; szStatusText: LPCWSTR): HResult; stdcall;
    function ReportData(grfBSCF: DWORD; ulProgress, ulProgressMax: ULONG): HResult; stdcall;
    function ReportResult(hrResult: HResult; dwError: DWORD; szResult: LPCWSTR): HResult; stdcall;

    { IInternetProtocolRoot }
    function Start(szUrl: LPCWSTR; OIProtSink: IInternetProtocolSink; OIBindInfo: IInternetBindInfo; grfPI, dwReserved: DWORD): HResult; stdcall;
    function Continue(const ProtocolData: TProtocolData): HResult; overload; stdcall;
    function Abort(hrReason: HResult; dwOptions: DWORD): HResult; stdcall;
    function Terminate(dwOptions: DWORD): HResult; stdcall;
    function Suspend: HResult; stdcall;
    function Resume: HResult; stdcall;

    { IInternetProtocol }
    function Read(pv: Pointer; cb: ULONG; out cbRead: ULONG): HResult; stdcall;
    function Seek(dlibMove: LARGE_INTEGER; dwOrigin: DWORD; out libNewPosition: ULARGE_INTEGER): HResult; stdcall;
    function LockRequest(dwOptions: DWORD): HResult; stdcall;
    function UnlockRequest: HResult; stdcall;
    end;

...

function TPassthrough.Start(szUrl: LPCWSTR; OIProtSink: IInternetProtocolSink; OIBindInfo: IInternetBindInfo; grfPI, dwReserved: DWORD): HResult; stdcall;
begin
  if (FDefaultSink = nil) then
  OleCheck(CoCreateInstance(CLSID_HttpProtocol, nil, CLSCTX_INPROC_SERVER, IUnknown, FDefaultSink));

  FBindInfo := OIBindInfo;
  FProtSink := OIProtSink;

  if (Assigned(FDefaultSink)) then
    Result := (FDefaultSink as IInternetProtocolRoot).Start(szUrl, Self, Self, grfPI, dwReserved)
  else
    Result := E_NOTIMPL;
end; 

function TPassthrough.QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;
begin
  Result := E_NOINTERFACE;
  Pointer(Obj) := nil;

  if Succeeded(QueryInterface(rsid, Obj)) and
    Assigned(Pointer(Obj))
  then
    Result := S_OK;
end; 
John Lewis
  • 337
  • 3
  • 12
  • Hard to see how `TPassthrough.QueryInterface` could fail to return the interface. Are you sure that `QI` is being called on the right object? – David Heffernan Jan 22 '15 at 15:05
  • Who calls QueryService? Other part of you code or external code? Stupid question but are you sure rsid (iid) param of QueryService is equal to IID_IInternetProtocolRoot or IID_IInternetProtocolSink or IID_IInternetProtocol? – Denis Anisimov Jan 22 '15 at 17:00
  • You've not shown the relevant code. I think we can assume that `QI` works. Either you are calling `QI` on the wrong object, or passing the wrong IID. – David Heffernan Jan 22 '15 at 17:07

2 Answers2

9

The purpose of QueryService is different from the purpose of QueryInterface.

QueryService should return a pointer to the object itself if it provides the requested service (through rsid), otherwise it should delegate to some other object, typically a container and with another QueryService call.

If no such service is found, it should return E_NOTIMPL.

The returned interface pointer will be of the requested type (through iid), so if the service is found, the object that implements the service is finally QueryInterfaced.

If no such interface is implemented by the object, it should return E_NOINTERFACE.

It so happens that many service IDs (SID) are the same GUID as the typically implemented interface ID (IID), for instance, SID_SInternetSecurityManager and IID_IInternetSecurityManager. Although this is a widely used convention, it is not a rule. For instance, you can ask for the SID_SWebBrowserApp service as a IID_IWebBrowser2 interface pointer.

So, analysing your original code:

function TPassthrough.QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;
begin
  Result := E_NOINTERFACE;
  Pointer(Obj) := nil;

  if Succeeded(QueryInterface(rsid, Obj)) and
    Assigned(Pointer(Obj))
  then
    Result := S_OK;
end;

You're kind of just transforming QueryService into QueryInterface using a service ID (rsid), which is similar but not necessarily related to interface IDs (iid). As such, if the two differ, the caller may get an incompatible interface pointer.

Then, the changed code:

function TMonitor.QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;
begin
  Result := E_NOINTERFACE;
  Pointer(Obj) := nil;

  if Succeeded(QueryInterface(iid, Obj)) and
    Assigned(Pointer(Obj))
  then
    Result := S_OK;
end;

This is saying that the object implements every imaginable service, so it merely delegates to QueryInterface, but at least correctly with iid.

This is what you should do:

function TMonitor.QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;
begin
  if Not Assigned(@Obj) then
  begin
    Result := E_POINTER;
    Exit;
  end;

  Pointer(Obj) := nil;
  Result := E_NOTIMPL;

  if Assigned(FServiceProvider) then
    Result := FServiceProvider.QueryService(rsid, iid, Obj);
end;

Should you want to provide your own services, you can optionally use the following code:

function TMonitor.QueryService(const rsid, iid: TGuid; out Obj): HResult; stdcall;
begin
  if Not Assigned(@Obj) then
  begin
    Result := E_POINTER;
    Exit;
  end;

  Pointer(Obj) := nil;
  Result := E_NOTIMPL;

  if IsEqualGUID(rsid, SID_SOverrideService1) or
     IsEqualGUID(rsid, SID_SOverrideService2)
  then
    Result := QueryInterface(iid, Obj);

  if Result = E_NOTIMPL and
     Assigned(FServiceProvider)
  then
    Result := FServiceProvider.QueryService(rsid, iid, Obj);

  if Result = E_NOTIMPL and
     (IsEqualGUID(rsid, SID_SFallbackService1) or
      IsEqualGUID(rsid, SID_SFallbackService2))
  then
    Result := QueryInterface(iid, Obj);
end;

Where SID_OverrideServiceN are the service IDs you want to override and SID_FallbackServiceN are the service IDs you want to fallback to. Note that I changed Succeeded(Result) to Result <> E_NOTIMPL, because I don't want to keep looking for services if one was actually found but some other error happened, e.g. the requested interface was not implemented (E_NOINTERFACE).

Community
  • 1
  • 1
acelent
  • 7,965
  • 21
  • 39
3

You're querying for the service ID (rsid), not interface ID (iid).

Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128