Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 4115
  • Last Modified:

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.








0
rwilson032697
Asked:
rwilson032697
  • 12
  • 7
  • 3
2 Solutions
 
rwilson032697Author Commented:
Oops, forgot to mention I am using D6.02 Professional.

Cheers,

Raymond.
0
 
andrewjbCommented:
The obvious reason why it didn't work on a non-Delphi machine is that you've compiled it to use packages or debug libraries, and don't have them installed. OK, so you get a COM error, but that might not be strictly accurate...

Use dependency walker to check what dlls are required by the applications, and whether they exist on the other machines.

(Though, of course, that doesn't really explain yet why re-importing the type library helps).



How was your 'x' created? As a proper com library? i.e. does it correctly have it's interfaces supported?


After moving 'X' over to a new machine, it needs to get registered, either by
a) Running X at least once
b) Running with /regserver option

(providing you've used the Delphi wizard to create it in the first place).


Did the classes and interfaces exist in the registry when you were getting the 'not supported' problem?


Andrew.
0
 
RobnCommented:
Stupid question, but did you register the ActiveX object on the second machine? A hard cast will work if the interface comes back as nil, queryinterface (soft cast) will not.
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
rwilson032697Author Commented:
(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.

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

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

Cheers,

Raymond.
0
 
rwilson032697Author Commented:
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.
0
 
andrewjbCommented:
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?
0
 
RobnCommented:
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
0
 
rwilson032697Author Commented:
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.
0
 
andrewjbCommented:
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.
0
 
RobnCommented:
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?
0
 
rwilson032697Author Commented:
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.
0
 
RobnCommented:
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
0
 
rwilson032697Author Commented:
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.
0
 
rwilson032697Author Commented:
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.
0
 
rwilson032697Author Commented:
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.

0
 
rwilson032697Author Commented:
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.
0
 
rwilson032697Author Commented:
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.
0
 
RobnCommented:
So you had a seperate TLB file that contained the interface?
0
 
rwilson032697Author Commented:
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.
0
 
RobnCommented:
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
0

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

  • 12
  • 7
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now