Link to home
Start Free TrialLog in
Avatar of rwilson032697
rwilson032697

asked on

Problem casting an interface from a COM object

I have the weirdest problem when instantiating a COM object...

I have a program installed, lets call it X. X provides a COM interface and a TLB file which I can import into Delphi.

I am writing another program, lets call it Y and want to use the COM interface in X.

I import the TLB file and create ActiveX components from it and use these from within my program. Everything works!

I later decide to construct the interface dynamically, rather than when the form is created and switch to creating the ActiveX components on the fly. Everything works!

I then install my program (Y), along with program X onto another computer, that does not have Delphi. I get an interface not supported exception. I install the two programs on a third computer that does have delphi with the same result. The program works on my machine (regardless of whether the Delphi IDE is running) but not on the other two :-(

After much tinkering I decide to try importing the TLB file onto the other machine that has Delphi installed. Once I did that my program (Y) runs happily getting the interface from program X. Note I did not recompile my program after installing the ActiveX components on that machine.

Now it gets stranger. Thinking that the issue was the ActiveX classes were not registered when the components were not a part of the form I explicitly registered them in my code (program Y), then uninstalled the package containing the activeX components then ran the modified program on both computers with Delphi installed - it worked! I happily installed the two programs again on the computer without Delph installed and got the interface not supported exception again!!!

Now, after some more tinkering I have this snippet of code, that works correctly and with no interface not supported exception:

(* works! *) FREQUESTOBJECT := ItRequestDispatchObject(CreateOLEObject('ProgramX.REQUESTOBJECT'));
(* works! *) FSTATUSOBJECT := ItStatusDispatchObject(CreateOLEObject(''ProgramX.STATUSOBJECT'));

This working snippet is derived from this non working snippet:

  FREQUESTOBJECT := CreateOLEObject('ProgramX.REQUESTOBJECT') as ItRequestDispatchObject;
  FSTATUSOBJECT := CreateOLEObject('ProgramX.STATUSOBJECT') as ItStatusDispatchObject;

I can't figure out why the hard cast works, and the soft cast does not. I would have thought that the soft cast version throwing an exception would have meant the interfaces were toast and would not work at all, but this isn't the case!!!

If anyone could throw some light on this I would like to hear it!

Cheers,

Raymond.








Avatar of rwilson032697
rwilson032697

ASKER

Oops, forgot to mention I am using D6.02 Professional.

Cheers,

Raymond.
ASKER CERTIFIED SOLUTION
Avatar of andrewjb
andrewjb
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
(There are no stupid questions!)

Andrew,

Both packages are compiled as release binaries (debug binaries also show the problem) and all required DLLs etc are present. None of the applications use packages.

One complicating factor I omitted to mention in order to keep the problem description short is that there is a third application (lets call it Z) that uses application X for the same purposes that my application Y wants to (when the X, Y, and Z's get too confusiing yell!). Application Z performs happily on all three machines and should be instantiating application Y in an identical manner (or so I think)).

Programs X, Y and Z are all WIN32 .EXE's.

The installer for application X does appear to register the application (that is, entries for its CLASSID GUID and object name mappings do appear in the registry in the expected locations. I am attempting to verify from the installer of program X just how it does this registration to see if it forgot anything.

One theory I have is that the installer for program X is somehow only doing a partial registration (how, I don't know yet) and when I created the ActiveX components in Delphi from the interfaces exported in the TLB file for application X Delphi somehow finished off the regsitration.

Now, program X is not an ActiveX server or an OLE object per se. It is simply an application that exposes some interfaces.

Robn,

The installer for program X should register it, and I suspect Delphi did too when I created the ActiveX components for the interfaces. The interfsce being sent back is not nil, which is the really puzzling bit. The hard cast give me an interface that works, while the soft cast complains the interface is not supported.


To summarise. Program Y running on a machine with Delphi installed into which the interfaces from program X have been imported as ActiveX components works perfectly. The exact some code running on a machine where this has not been done does not work, unless I use the hard cast.

Cheers,

Raymond.

These are executable COM objects correct? (not .DLL)
Are you registering them like this C:\X.EXE /regserver ?
robn,

Yes, we have tried that on the machine that does not have delphi installed. Unfortunately it makes no difference!

Cheers,

Raymond.
In another scary twist, I have changed my code so it looks like this:

var
      FREQUESTOBJECT : Variant; // ItRequestDispatchObject;
      FSTATUSOBJECT : Variant ; // ItStatusDispatchObject;

...

  FREQUESTOBJECT := CreateOLEObject('ProgramX.REQUESTOBJECT');
  FSTATUSOBJECT := CreateOLEObject('ProgramX.STATUSOBJECT');    

This code appears to work perfectly on the machine without delphi installed (ie: all the called interface methods function and there are no complaints of unsupported interfaces when the interfaces are instantiate)! And I can't explain why!!

I would prefer to use interfaces directly as the variant approach is a little clunky...

Cheers,

Raymond.
When you say 'it works', do you mean the casting / assignmetn works, or that you can then go on to use the interface pointer or variant to actually call methods of the COM object, and get back the results you're expecting?
This is a very interesting bug. It seems as though you do get an interface back but the interface is either a different version than the one you have compiled in Delphi or you are getting an empty interface back.
I have noticed with Delphi that when creating COM objects, Delphi will create the _TLB.pas file in the folder where your COM object is created. When you import the type lib, there will be another _TLB.pas file created in the Borland/DelphiX/Imports folder. Is it possible that your application is linking in the wrong interface definition? It should be linking to the one in the Import folder. Delete any other _TLB.pas files that your application is using and recompile your application again.

Hope this helps,
Rob
Andrew,

When I say 'it works' I mean I can successfully call all the methods on the interface and they give back sensible answers (in fact, many of the methods return complex binary structures that are parsed on the calling side).

Rob,

I checkd for duplicate instances of the TLB file, but there were none. I have noticed the problem you indicated in the past and am now careful to specify the path to save the TLB file :-)

Cheers,

Raymond.
Don't think this'll be it, but...

Do _all_ the methods of your interface contain parameters that are Dispatch-able? Delphi doesn't let you IDispatch interface pointers other than IUnknown and IDispatch, and it doesn't like some of the other types, either.

I've had an odd problem before. I couldn't QueryInterface for something or other, and it turned out that this was because one of the methods had an IMyInterface parameter, which can't be dispatched. Even though I wasn't actually trying to use IDispatch, it wouldn't QI the _interface_. Maybe it's worth checking that all parameters are simple types. Delphi help tells you what can be automatically marshalled - under 'automation' somewhere, I think.
A common practice that I do is to create an interface that is basically abstract. Then I create a COM object that impliments this interface. This allows me to swap in and out different COM objects without having to compile the application. However, the base interface must be registered before the COM objects can be registered. Also, you cannot create this interface directly as there is no implimentation code written. Are you doing something like this and not registering the base interface?
Andrew,

I'll go through and check the interfaces in a few days (I've been diverted :-( ). I have a suspicion one of the methods in one of the interfaces provided by Program Y has a return type of Boolean (which should be WordBool), but the other parameter types look on the surface to be pretty kosher. The interfaces are defintely IDispatch interfaces.

Rob,

I thought that was what interfaces were, effectively. One can swap COM objects that implement the same defined interface with no need to alter the calling application. We don not have any additional 'abstract' interface involved...

Cheers,

Raymond.
The reason we did this is because our application x and supporting dll's, y, z would use this COM object (one of many). We didn't want to have to rewrite the interface when we wanted a new COM object because the interface was huge. This way the descendant COM objects would simply inherited from the base interface and force you to implement it that way. It works very well but you need to take care when using it. It's definitely harder to set up initially but the payoff is huge.

I wasn't sure if you were using the same techniques.

Regards,
Rob
Back again...

I havn't yet been able to verify potential parameter issues, but I have run across yet another strange aspect to this problem!

There is a method on one of the interfaces called AddRequest. This has a single argument which is an OleVariant defined as an array of bytes by the code that creates it and passes it to the method when called.

ie: It is created like this:

  DataToSend : OleVariant;
...
  DataToSend := VarArrayCreate([0, BytesNeeded - 1], varByte);

The implementation of the method in program x it explicitly checks that the variant parameter is an array of bytes. If not it just fails, like this:

BOOL tRequestDispatchObject::AddRequest(const VARIANT FAR& RequestData)
{
  if (RequestData.vt == (VT_UI1 | VT_ARRAY))
  {
// Do Something

      return TRUE;
  }
  return FALSE;
}

On my machine (where everything has always worked!), when calling the method on the interface constructed like this:

 FREQUESTOBJECT := CreateOLEObject('ProgramX.REQUESTOBJECT');

and where FREQUESTOBJECT is declared as a variant the method fails because the variant parameter in the implementation in program X is defined as an Array of Variant.

When I revert the code to creating the interface like this:

  FREQUESTOBJECT := CreateOLEObject('ProgramX.REQUESTOBJECT') as ItRequestDispatchObject;

and where FREQUESTOBJECT is declared as a ItRequestDispatchObject then the variant is recognised as an Array of byte and the method succeeds!

I'm getting ready to sign up for the Foreign Legion here!

Cheers,

Raymond.
I just realised I made a mistake in the previous comment. In the implementation of the AddRequest method, the variant array of byte that was given to the method by the caller, appears as a VT_VARIANT | VT_BYREF, rather than a VT_VARIANT | VT_ARRAY (ie array of variant) as I mentioned above.

Cheers,

Raymond.
Hmmm...

Further investigation reveals when I call the method, and the RequestObject is defined as the ItRequestDispatchObject dispatch interface, then Delphi calls this procedure to call the method:

procedure DispCallByID(Result: Pointer; const Dispatch: IDispatch;
  DispDesc: PDispDesc; Params: Pointer); cdecl;

However, when I call the same method with RequestObject  defined as a Variant, then it calls

procedure VarDispInvoke(Result: PVariant; const Instance: Variant;
  CallDesc: PCallDesc; Params: Pointer); cdecl;

(both calls are in comobj.pas).

DispCallByID is implemented as a basic strightforward call the dispatch method. Whereas VarDispInvoke does some messing around with the Variant and seems to think it should define it as a ByREF Variant rather than as an Array of Byte.

Investigation continues :-)

Raymond.

OK - found it!

Program X, while it registers itself as a COM object, does not register its type library! Once you run TRegSrv with the type library everything is hunky-dory using the interfaces declared as the Delphi dispatch interface classes.

Andrew, Rob: Thanks for help - I'll split the points between you for your efforts.

Cheers,

Raymond.
Oh - I should elaborate:  Program X didn't actually have a type library contained within it which explains why it didn't register it, and why /regserver didn't have an effect either.

Cheers,

Raymond.
So you had a seperate TLB file that contained the interface?
Yes, there was. Unfortunately I didn't know that Program X did not have the type library within it (I had originally just used the .TLB file to create the interfaces because it was handy :-)

Cheers,

Raymond.
This is what we do with some of our COM objects as well (the ones we want to easily replace).
This is why I asked because you need to register the TLB files BEFORE the COM object otherwise it will fail as you found out ;-)

Glad to hear you got everything to work!

Regards,
Rob