Solved

Problem casting an interface from a COM object

Posted on 2003-11-26
22
4,059 Views
Last Modified: 2007-12-19
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
Comment
Question by:rwilson032697
  • 12
  • 7
  • 3
22 Comments
 
LVL 12

Author Comment

by:rwilson032697
ID: 9829291
Oops, forgot to mention I am using D6.02 Professional.

Cheers,

Raymond.
0
 
LVL 12

Accepted Solution

by:
andrewjb earned 250 total points
ID: 9830712
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
 
LVL 2

Assisted Solution

by:Robn
Robn earned 250 total points
ID: 9831706
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9833386
(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
 
LVL 2

Expert Comment

by:Robn
ID: 9833771
These are executable COM objects correct? (not .DLL)
Are you registering them like this C:\X.EXE /regserver ?
0
 
LVL 12

Author Comment

by:rwilson032697
ID: 9834111
robn,

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

Cheers,

Raymond.
0
 
LVL 12

Author Comment

by:rwilson032697
ID: 9834133
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
 
LVL 12

Expert Comment

by:andrewjb
ID: 9837230
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
 
LVL 2

Expert Comment

by:Robn
ID: 9838303
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9846226
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
 
LVL 12

Expert Comment

by:andrewjb
ID: 9849081
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
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 2

Expert Comment

by:Robn
ID: 9849998
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9853148
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
 
LVL 2

Expert Comment

by:Robn
ID: 9853522
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9903065
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9906450
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9906571
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9908362
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
 
LVL 12

Author Comment

by:rwilson032697
ID: 9908383
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
 
LVL 2

Expert Comment

by:Robn
ID: 9911943
So you had a seperate TLB file that contained the interface?
0
 
LVL 12

Author Comment

by:rwilson032697
ID: 9924410
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
 
LVL 2

Expert Comment

by:Robn
ID: 9927979
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

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now