Troubleshooting COM error “The parameter is incorr

2019-09-08 02:39发布

问题:

Environment is Delphi XE2 Enterprise.

Relates to use dispInterface in Delphi with no classid. Re-posting as a different question as I have moved well beyond the previous issues (thanks to @EricBrown) and now have a more specific problem.

I've got a .NET dll with a COM interface that I've registered on the computer and have imported as a type library into Delphi. I'm using the tlb.pas file that Delphi created.

Working with a couple of dispInterfaces that I've implemented in IDispatch interfaces and TAutoIntf descendant classes, I've successfully initialized the classes and interfaces that I need to call methods in the COM interface to the .NET library. Here is some code to illustrate the declaration/implementation:

// COM Event Sink GUID
DIID_IResponseListener: TGUID = '{ABC29F08-B628-4747-BA9E-469D408E57B9}';

// *********************************************************************//
// DispIntf:  IResponseListener
// Flags:     (4096) Dispatchable
// GUID:      {ABC29F08-B628-4747-BA9E-469D408E57B9}
// *********************************************************************//
  IResponseListener = dispinterface
    ['{ABC29F08-B628-4747-BA9E-469D408E57B9}']
    procedure RequestCompleted(const requestID: WideString; const responseObj: IResponse); dispid 1610744833;
    procedure RequestFailed(const requestID: WideString; const error: WideString); dispid 1610744834;
    procedure TablesUpdates(const responseObj: IResponse); dispid 1610744835;
 end;

...my implementation:
  IFXResponseListener = interface(IDispatch)
['{3204D3F7-5DF2-4470-89D5-D34F4F6F0381}']
    procedure RequestCompleted(const requestID: WideString; const responseObj: IResponse); dispid 1610744833; safecall;
    procedure RequestFailed(const requestID: WideString; const error: WideString); dispid 1610744834; safecall;
    procedure TablesUpdates(const responseObj: IResponse); dispid 1610744835; safecall;
  end;

  TFXResponseListener = class(TAutoIntfObject,IFXResponseListener)
  private
    FDisp: IDispatch;
    FResp: IResponse;
    function GetResponseListIntf: IResponseListener;
  public
    constructor Create;
    destructor Destroy;
  published
    procedure RequestCompleted(const requestID: WideString; const responseObj: IResponse); safecall;
    procedure RequestFailed(const requestID: WideString; const error: WideString); safecall;
    procedure TablesUpdates(const responseObj: IResponse); safecall;
    property ResponseObj: IResponse read FResp;
    property ListenerDispIntf: IResponseListener read GetResponseListIntf;
  end;

...

{ TFXResponseListener }


constructor TFXResponseListener.Create;
var
  TypeLib: ITypeLib;
begin
  OleCheck(LoadRegTypeLib(LIBID_fxcore2_com,fxcore2_comMajorVersion,fxcore2_comMinorVersion,0,TypeLib));
  inherited Create(TypeLib,DIID_IResponseListener);
end;

destructor TFXResponseListener.Destroy;
begin
  inherited;
end;

function TFXResponseListener.GetResponseListIntf: IResponseListener;
begin
  FDisp := Self as IFXResponseListener;
  FDisp._AddRef;
  Result := IResponseListener(FDisp);
end;

procedure TFXResponseListener.RequestCompleted(const requestID: WideString; const responseObj: IResponse);
begin
  showmessage('Completed: ' + requestID);
  FResp := responseObj;
end;

procedure TFXResponseListener.RequestFailed(const requestID: WideString; const error: WideString);
begin
  showmessage('Failed: ' + requestID);
end;

procedure TFXResponseListener.TablesUpdates(const responseObj: IResponse);
begin
  showmessage('TablesUpdates');
end;

Here is where I attempt to use the interface:

FRespList := TFXResponseListener.Create;
  try
    FSess.subscribeResponse(FRespList.ListenerDispIntf);
  except
    // errors out here with 'The parameter is incorrect'
  end;

Tracing this down into the bowels of System.Win.ComObj, at line 1793 it calls:

Status := Dispatch.Invoke(DispID, GUID_NULL, 0, InvKind, DispParams, Result, @ExcepInfo, nil);

At this point, the DispID parameter is valid (1610743816), GUID_NULL is '(0, 0, 0, (0, 0, 0, 0, 0, 0, 0, 0))', InvKind is '1', DispParams is '($2152FE8, nil, 1, 0)', Result is '$12FE24', @ExcepInfo is '$12FDE0'.

In System.pas, line 30133, TInterfacedObject.QueryInterface is called twice. On the second run it returns the result 'E_NOINTERFACE', and the dreaded 'The parameter is incorrect' message appears.

I'm really not sure where to go from here, but I'm hoping a COM expert and/or Delphi expert can review this and see something amiss.

I'm also wondering if there is a .NET framework version or other issue. I'm using framework version 4.5.1; not sure how to determine if the .NET assembly is kosher with this version of the framework or if it really needs an earlier version.

Any relevant .NET/COM debugging techniques would be greatly appreciated.

Also please note, I can duplicate this issue with a different dispinterface/IDispatch/TAutoIntfObj descendant class and similar call, identical to a 'T'.

Thanks.

回答1:

Looks like this one is in the bag. Thanks to @EricBrown and @RemyLebeau, I was able to get this to work.

Remy's suggestion of changing the instantiation of FRespList from:

var
  FRespList: TFXResponseListener;

to:

var 
  FRespList: IFXResponseListener;

...did the trick. So when I instantiate using

FRespList := TFXResponseListener.Create;

...I get the right result, and everything is fat, dumb and happy.

Also I need to point out that the GUID that is assigned to the dispinterface has to be used for the IDispatch interface descendant that I created, otherwise, we're back to 'The parameter is incorrect.'. Wonder if that's documented somewhere...

Also kind of weird, when I'm done with my IDispatch descendant I nil it. That reduces the refcount on the TAutoIntfObject descendant to 2. Which results in a memory leak. I tested and tried different things to get this refcount to dec, finally had to do some weirdness:

**procedure** TMyClass.Destroy; // <- changed from **destructor** TMyClass.Destroy;
begin
  inherited Destroy; // <- had to call this first!
  // then...
  while Self.RefCount > 0 do
    IUnknown(Self)._Release;
end;

...and then call MyClassInstance.Destroy; to get the class to free without AV's nor mem leaks.

If anyone knows why I had 3 references to my class, why FreeAndNil(MyClassInstance) and IMyClass := Nil; leaves a refcount of 2 with an Access Violation and/or a memory leak, why I had to destroy the inherited first, why I had to typecast the class as an IUnknown and manually release the instances, please speak up.

On another interface/class pair I still get a mem leak from TServerEventDispatch. This is one of the classes that Delphi auto-created in the tlb.pas file. If anyone has a suggestion...

COM... ya' gotta love it!

I'd like to give any points to both Eric Brown and Remy Lebeau, but I don't know how.

So... THANKS GUYS!



标签: .net delphi com