Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Interface problem with custom VFP COM component...

Status
Not open for further replies.

rlawrence

Programmer
Sep 14, 2000
182
US
Hi folks,

I have already posted this problem on the ASP forum: thread333-1688356

The problem appeared when I attempted to incorporate a new method in my component--which is being used in a web application. I am at the point where I have verified that the problem is not with ASP or IIS. I've created an EXE using my VFP test program. The test program works fine on my development machine, but when I run it on the server, I get the following message at the point where the new method is called:

OLE IDispatch exception code 0 from ?, ?..

This seems like a straight forward problem with the interface, but I haven't been able to resolve it. I have uninstalled and reinstalled the product. I have used REGSVR32 to unregister and re-register the DLL. I think this is not pertinent, but the DLL is installed as a COM+ component. I have removed and reinstalled the DLL through the Component Manager. None of these steps have changed the behavior.

When I look at the interface through the component manager, I can clearly see the component in question, and the new method that I added.

I have been searching everywhere for strategies for resolving this, so far to no avail. Can anyone here help?

Thanks,
Ron
 
You're not understanding my point. As you see no log entry, the error must occur before the line you check.

You've not added log entries before each line of the proceeding code:
Code:
		IF VarType(rcInvoice.rsOrder.oRecord.Order_Key) != "N"
			=LogError(This.oSession, -1, PROGRAM(), LINENO(), "A valid Invoice cluster was not submitted. ")
			RETURN .F.
		ENDIF
		rcInvoice.rsOrder.cMessage = ""

		* If an exclusive promotion has already been applied, then don't go any further.
		IF "EXCLUSIVE PROMOTION:" $ UPPER(rcInvoice.rsOrder.oRecord.Comment)
			rcInvoice.rsOrder.cMessage = msgPromoExclOther
			RETURN .F.
		ENDIF

		* See if any other promotions have been applied.
		* Note that to get to this point, any previously applied promotion can not be exclusive.
		lFirstPromotion = NOT ("PROMOTIONS: " $ UPPER(rcInvoice.rsOrder.oRecord.Comment))

		* Find the promotion.
                [red]=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Instantiating rmPromotion.")[/red]
		rmPromo = CREATEOBJECT(locRMPromotion)
		rsPromo = rmPromo.CreateResource()
		rsPromo.oRecord.Promo_ID = cPromoId
		IF !rmPromo.locate(rsPromo)
			rcInvoice.rsOrder.cMessage = STRTRAN(msgPromoIDNotFound, "#1", cPromoId)
			RETURN .F.
		ENDIF

You say you don't get an entry at that log position, so you don't see an entry "Instantiating rmPromotion". Isn't that what you said?
Then either your logging fail (less probable) or the error is somewhere before that line (much more probable).

And if you are so sure the error is at CREATEOBJECT(locRMPromotion), then why do you search further? Why don't you fix that line? The first question then must be: "What is the value of locRMPromotion?" Is it a valid class name at that point? Why don't you log that value, to see what's up?

Bye, Olaf.
 
Actually, Olaf, if you look at the log above, (You have to look to the right.) you will see the log entry:

...Line: 287 Message: Instantiating rmPromotion.

What you DON'T see is the log entry after, (i.e. "Obtaining a promotion resource.") I'm pretty sure this narrows the cause of the problem to one line.

This comes AFTER my purposely introduced error. The point there is to demonstrate that the error logging does indeed work, and has nothing to do with the problem at hand.

I have also (since my last entry) replaced locRMPromotion (a constant) with a direct reference to "rmPromotion". The result is the same.

By the way, I can (and do) instantiate rmPromotion elsewhere in the application. Even with the current problem, those functions seem to work fine.

On the other hand, another function that has been in use for years, and has undergone NO change, suddenly fails in the same way as AddPromotion()--with an "80004005 error".

It's beginning to feel like I'm running up against some sort of resource limitation.
 
You say you changed from a constant locRMPromotion to the literal class name 'rmPromotion'. Are you sure that is the class name? If you compile an olepublic class the name typically is dllname.nativeclassname, so it would be 'yourdllname.rmPromotion', wouldn't it? Is it perhaps that simple? In other places, where CREATEOBJECT(locRMPromotion) worked, the constant locRMPromotion was simple defined with the correct full COM name and in this case. Could you please double and triple check that? Could you also please look into the VFP projects project info in the servers tab and see what OLE class name VFP has stored there?

Besides that, I also second Craig, let VFP generate new GUIDs via the build option "Regenerate ComponentIDs". Perhaps even change the class name so you can be sure to get the new class version in your ASP code.

>What you DON'T see is the log entry after, (i.e. "Obtaining a promotion resource.")
I not only don't see that, I also don't see a log of "Obtaining a promotion resource.", because you introduced an error on purpose to demonstrate your errorhandling.

If you want us to look at your errorlog of the real method, then post that, and not your demonstration run. I know now your error logging works, but it will stop from the moment an error occurs, like your demo log also ends, the rest isn't executed.

I still stay with my point, make a log call in any line, even the ones you're sure will work, add variable values to the log message perhaps. It seems that this is already failing, doesn't it?

What's so hard to do this, despite of the effort to introduce so many logerror calls?

Beside I and Marco (mplaza) pointed you to Rick Strahls whitepaper on debugging COM components, if you follow that you can even single step through your code and see live what line really fails. You also didn't respond to that for a few posts.

Not getting to some line doesn't mean it's the line of error, it instead means the errors is somewhere, anywhere *before* that line, doesn't it mean that? And you seem to ignore that fact for the third time now. Your argument is: "I'm pretty sure this narrows the cause of the problem to one line." Never be sure about this. Even if you know that line is not executed or not generating something expected, like a class instance, then this can alsways simply be a follow up error. Even just as simple as taking the wrong, incomplete, non OLE class name.

Bye, Olaf.
 
Olaf,

It seems like you are losing patience with me over something I have clearly demonstrated. Either I am missing something in what you are saying...or you are. Let's try to understand which.

Olaf said:
I not only don't see that, I also don't see a log of "Obtaining a promotion resource.", because you introduced an error on purpose to demonstrate your errorhandling.

Olaf, look at the line AFTER the red in the log above. The red lines demonstrate that the error logging works. The error is logged, but the application continues on. (That's a desireable behavior as far as I am concerned.)

The next line in the log is what demonstrates that the call to LogError() BEFORE CreateObject() works.

Time: 07/27/12 10:20:53 AM Error: 0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION Line: 287 Message: Instantiating rmPromotion.

Here's the code segment again...

Code:
* Find the promotion.
=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Instantiating rmPromotion.")
rmPromo = CREATEOBJECT("rmPromotion")
=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Obtaining a promotion resource.")

Clearly, the first call to LogError() is working. The procedure never get's to the second one.

As far as referencing the component as "rmPromotion", this is INSIDE the DLL. RmPromotion is a procedure that has been included with "ADD PROCEDURE..." I don't think I want to reference the component using "PAWeb.rmPromotion" in this context. In any case, the procedure call works fine--EXCEPT when I introduce it to COM+.

The last line in the log segment above indicates the beginning of the application shutdown. The WebManger's Destroy() method is executing. I didn't include the whole rest of the log, because it's not pertinent.

____________________________________

Next, I just regenerated component ID's. To be more specific,

[ol 1]
[li]I rebuilt the DLL using the "Regenerate Component ID's" option.[/li]
[li]I then moved the DLL to the server. [/li]
[li]Removed the compoent from COM+.[/li]
[li]Unregistered the prior DLL.[/li]
[li]Moved the new DLL into place on the server.[/li]
[li]Re-registered the DLL.[/li]
[li]At this point, the test procedure works with the new DLL.[/li]
[li]Reinstated the DLL to COM+[/li]
[li]Tested again and received the same failure.[/li]
[/ol]

___________________________________

Finally, Rick Strahl's article: First of all, I love Rick Strahl. His work has helped me enormously in getting into this development space. I read through this article, however, and my eyes began to glaze over. I'm sure that if I step through it carefully, I can probably make this work. For me, this would be dealving into a new set of unknowns.

But why would I want to set up debugging in ASP (or ASP.NET) when clearly I have demonstrated a problem without introducing ASP at all? Furthermore, it seems to me that if I am successful at setting this up, I'll be able to step through my code only to find out...
"Yep, the program fails on this call to CreatObject("rmPromotion")". Having run into this type of problem before--even when the debugger is available to me, it generally doesn't give you any more useful information for this type of failure. Usually, Foxpro just crashes.

Ron
 
That's news to me: So "rmPromotion" is a class in a prg included in the DLL, and you do just a normal native instanciation? Did I understand that correctly?
Then what should it's instanciation have to do with COM or COM+ at all? (Just a general question, not that I expect you to know, you just observe that). But that's indeed very important, anything fiddling with the registry is nonsense then. The question why a native class works without COM+ registration is a totally new question then, as you just instanciate a native class inside your COM class instance, which is totally valid.

OK, now that I understand that:

There is a possibility the instanciation fails and nothing happens: You return False from Init() of the class or do nodefault in there, then the class is not instanciated.
Least probable: The class definition loaded into memory is corrupt, you could do CLEAR CLASS rmInvoice before CREATEOBJECT to fix that.
Even less probable: Some properties of your rmInvoice class are set to expressions which evaluate even before Init() and may fail.

Something you need to know In conjunction with CLEAR CLASS: Such expression are just evaluated once when the class is loaded into memory for the first, further instances get the same property value. So if you expect current value in some properties of the rmInvoice object and refuse to instciate, if that's not the case, then that may be the reason you don't get an instance and destroy is the next thing happening.

So why not add logging calls into the Init event of the rmInvoice Class to see what happens during instanciation?

Some other things:
RmPromotion is a procedure that has been included with "ADD PROCEDURE..."
The way to add a prg file for CREAEOBJECT to find classes in it is SET PROCEDURE...ADDITIVE, not ADD PROCEDURE, there is no such thing as ADD PROCEDURE. Or are you talking about something different than a class defined in a prg? Well, I assume you mean a SET PROCEDURE, because if the prg is not listed in SET('PROCEDURE') you get a simple 'class definition is not found', which is the case with NOTAREALCOMPONENT.

Having run into this type of problem before--even when the debugger is available to me, it generally doesn't give you any more useful information for this type of failure.
I disagree, you'll either see how far in Init() it get's, and if the debugger does not even enter the init method of the class, you know it must be something happening even before init, either loading of the class or evaluating class properties.

...the [red]procedure call[/red] works fine--EXCEPT when I introduce it to COM+[/quit]

CREATEOBJECT("rmInvoice") is called instanciation, no matter if the class is in a prg or a vcx. A procedure call is a call to a procedure in a prg file or a call of a database stored procedure, but a CREATEOBJECT() never is a procedure call. So what are you talking about here? You neither DO RMINVOICE.PRG nor do you call RMINVOICE().

At this point I would check, whether you have any double in your code, another place where an older version of the rmInvoice class is defined. This may explain COM+ü failing to register correctly at all and certain circumstances might instanciate the correct new class while other circumstance create the old one, failing.

Bye, Olaf.
 
Olaf said:
The way to add a prg file for CREAEOBJECT to find classes in it is SET PROCEDURE...ADDITIVE, not ADD PROCEDURE
O.K. You're right. I just typed it wrong in my response above. I am using SET PROCEDURE...ADDITIVE for several modules in the application.

The other thing is that when the application runs normally, there are all kinds of debugging statements (calls to LogError()) in the instantiation process. I've included an excerpt below. This excerpt is from the error log (with debugging turned on) without COM+. Obviously, with COM+ you get what I posted above. It is clearly not instantiating rmPromotion. The section that comes from the the initialization process for rmPromotion I have highlighted in red.

Code:
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  261 Message: Entering the rmcInvoice.AddPromotion() method.
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  267 Message: Applying promotion to invoice# 1353- 1.
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  283 Message: Purposely introducing an error.
Time: 07/30/12 11:39:35 AM Error:  1733 Procedure: rmcInvoice.addpromotion                            Line:  284 Message: Class definition NOTAREALCOMPONENT is not found. Stack: 
LOGERROR
WEBINVOICE.RMCINVOICE.ERROR
WEBINVOICE.RMCINVOICE.ADDPROMOTION
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  287 Message: Instantiating rmPromotion.
[COLOR=red]Time: 07/30/12 11:39:35 AM Error:     0 Procedure: MYSESSION.PROCESSINI                               Line:  526 Message: Data Path from D:\WEBSITES\DOMAINS\PUBASSIST\PUBASSIST.INI is D:\WEBSITES\DOMAINS\PUBASSIST\DATA.
.
.
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: RESOURCEMGR.INIT                                   Line:  561 Message: Initialized ResourceMgr.
[/color]
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  290 Message: Obtaining a promotion resource.
Time: 07/30/12 11:39:35 AM Error:     0 Procedure: RESOURCEMGR.ODS.INITRESOURCE                       Line: 1530 Message: Initializing a resource.

One last thing. I really appreciate your help on this. I am not expecting you to pour through my code or my logs to find the problem for me. Really, I am looking for ideas from your experience that will help me to develop new strategies to identify the cause of this problem.

So to review...

[ul]
[li]The test program is written in VFP.[/li]
[li]It runs normally on both the development machine and the server without COM+.[/li]
[li]It fails instantiating rmPromotion when the DLL is submitted as a COM+ application.[/li]
[/ul]

Olaf said:
Then what should it's instanciation have to do with COM or COM+ at all?

Precisely! [ponder]
 
>I am looking for ideas from your experience that will help me to develop new strategies to identify the cause of this problem.
Single stepping through code is always the most revealing thing you can do.

>This excerpt is from the error log (with debugging turned on) without COM+. Obviously, with COM+ you get what I posted above. It is clearly not instantiating rmPromotion.
Yes, you said so.

You show "Line: 561 Message: Initialized ResourceMgr." Does that mean the Init procedure has 561 lines? And you only log one other line from it ("Line: 526 Message: Data Path from D:\WEBSITES")? Ir is that just because the class just starts around line 520 of the prg? Use LineNo(1) here.

Still the most intersting woudl be the code in the Init of rmInvoice?
Is it doing anything visual, eg just a wait window or inkey(), even with a timeout?

If registering as COM+ you should compile as mtdll, change runtime to vfp9rt.dll and call SYS(2335,0) to ensure you're getting an error log entry instead of a silent failure whenever anything is done causing a modal state. Just don't think about what could cause it, if there is something causing it you will then get an error log entry.

Bye, Olaf.
 
First, PAWeb is built as an MTDLL.

Olaf said:
Still the most intersting woudl be the code in the Init of rmInvoice?

Actually, it's "rmcInvoice", which stands for "resource manager controller". An RMC in my application is the business logic that spans multiple database tables--like an invoice. The business logic that is specific to a table is implemented in a "resource manager".

Similarly, a "resource" is the data container--generally for a specific table. A "resource cluster" is a collection of resources needed to complete a complex tranaction--like an invoice.

So rmcInvoice is the resource manager controlLer and rcInvoice is the resource cluster for a customer order or invoice transaction. By contrast, rmPromotion and rsPromotion are the resource manager and resource for the Promotion database table (or a single entity).

The "resource" and "resource manager" terminology came from an article I read on DNA way back when. I think it was from an entitiy named, "Code Flash", or something like that. The concept of clusters and controllers are largely my own.

The point here is that instantiatiating data containers and resource managers is common throughout the application. This is really nothing new and has been working for years in both my desktop and web applications.

You asked for the Init() method of "rmInvoice". Assuming you meant rmcInvoice, you should know that the Init() method is implemented in a class called, "ResMgrControl". All RMC's are based on this class.

Having explained the above, here's the Init method from ResMgrControl:

Code:
PROCEDURE Init
PARAMETERS cPath

	* Insure that we have a session object.
	IF TYPE("oSession") = "O"
		This.oSession = oSession
	ELSE
		This.oSession = CREATEOBJECT("mySession", cPath)
	ENDIF
	=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Initialized " + This.Name + "...")

ENDPROC

It's looking for a session object (which is where I keep the context for the application thread). If it finds it, it stores a reference to it. If not, it instantiates the session object. At the end, of course, you see my call to LogError().

I'll share with you that I learned the hard way that a multi-threaded application shares public variables across threads! So, the session variable has to be declared PRIVATE, and each component much check to see if it is available.

The Init() procedure for ResourceMgr (the class on which rmPromotion is based) is very similar. These components don't know whether they will be instantiated individually or as part of an RMC.

In either case, other than instantiating the Session object, there's not much of interest. Clearly, the Session Object is successfully instantiated from within rmcInvoice. When attempting to instantiate rmPromotion after adding the DLL to COM+, there is no indication that an attempt is made to instantiate the session object.

Olaf said:
You show "Line: 561 Message: Initialized ResourceMgr." Does that mean the Init procedure has 561 lines? And you only log one other line from it ("Line: 526 Message: Data Path from D:\WEBSITES")? Ir is that just because the class just starts around line 520 of the prg? Use LineNo(1) here.

"Line 526" is line 526 of MySession.PRG. "Line 561" is line 561 of ResourceMgr.PRG.
This indicates that the message or error was on that line of the PRG--not the method.

Ron
 
Oops! Forgot...

Olaf said:
If registering as COM+ you should compile as mtdll, change runtime to vfp9rt.dll and call SYS(2335,0) to ensure you're getting an error log entry instead of a silent failure whenever anything is done causing a modal state. Just don't think about what could cause it, if there is something causing it you will then get an error log entry.

I wasn't familiar with the SYS(2335) function, but when I looked it up, I found the following:

VFP Help said:
Note that SYS(2335) applies only to .exe automation servers for which the StartMode property equals two. Unattended mode is always enabled for in-process .dll automation servers (for which the StartMode property equals three).

Actually for an MTDLL, the StartMode will be 5. I don't test these values. I make it a point NOT to put any user interface functions or commands into any of the DNA components.
 
O.K. Here's a significant new development. My development machine runs Windows Vista. I found the Component Manager under Windows Vista (not obvious) and installed PAWeb.dll as a COM+ application on the development machine. Voila! I now receive the same error on the development machine! So, whatever is going on is most definitely aggravated by COM+. It's probably not a configuration issue on the server.

This error is SO predictable that it HAS to be a programming issue. But there is still no good information on what that issue is. In the past, I have had problems like this only to find out that the issue was something silly, like the name I used for a component or function. No one out there knows of a COM+ or other event log I can look at that might shed more light on the situation?

Ron
 
Oh my God! I may have got to the bottom of this. Since the problem was focused on the instantiation of rmPromotion, after trying a couple of other things, I eventually added rmPromotion as an object property of rmcInvoice. I got the same failure after adding the DLL to COM+, BUT...the error log showed further progress!

I noticed in the error log that there were multiple instances of the session object--despite efforts in my code to avoid that. There were NO errors logged as a result of this, but clearly the process was not as efficient as it could be. So, I poured through rmcInvoice and removed ALL temporary instances of resource managers and added them as object properties to the rmcInvoice class. And viola! It runs under COM+!!!

The handling of the Session object (this is my own session object--not to be confused with VFP or ASP sessions) needs a bit more scrutiny. Instantiating the Session entails reading an INI file to set the context for the application thread. I know that there are some efficiencies to be gained there. But generally, I work to achieve the function I need before I start working on efficiencies.

More testing is needed. I still think this is a resource issue within COM+, but I have no better information on what the actual problem was.

Wow! This was a bad one! Thanks to all who participated in this discussion. You gave me a sounding board to bounce things off from. Any thoughts on how to better be able to peer into this kind of problem?

Ron
 
What is the purpose of the session object? I think you're looking at possible issues when the app scales, but without knowing what the session object does, I can't say for sure.

Craig Berntson
MCSD, Visual C# MVP,
 
O.K. What are "app scales"? More importantly, how do I know if that's a problem?

As I described above, the session object (within the application) sets the context for the application thread. PAWeb is installed as an automation server for our web application. We run several web sites using those same services. Each web site needs to set its own context (i.e. Where is the database? Where is the error log? Who is the business owner? etc.)

Generally speaking, the components look to see if the Session object is available within the context of the component. If it is, the component simply sets a reference to it. If not, then the component will instantiate a session object for its own purposes. Obviously, that's something I would prefer not to happen more than once, but I can see no problem if that happens. Just a loss of efficiency.

Of course, it's possible to envision a situation where multiple nested components will create multiple instances of the session object. Just as a recursive routine can get out of control, you're going to run out of a resource somewhere. But in this case, there are NO errors generated without COM+. Actually, there are no errors generated within COM+. It just says, "Nope. There's something wrong with your application. I quit."

 
An application is scaling, if it works with few and many users, data, requests, ...pit in whatever matters here ...

If it scales bad, you have slow performance as the least problem, timeouts as the next and errors and crashes as the worst. But as you reported your problems the errors were not from too frequent and heavy use.

Bye, Olaf.
 
If you're talking about the COM+ context here, you may be in trouble. Here's why: User one connects, the context is created. The method is run. The user disconnects (This is, after all, TCP/IP). But, the component stays in memory, waiting for the next connection. The next user may or may not get the older instance of the component. If they do, they need a new context, not the one from the first user.

Make sure that the context is being released at the end of the method.

This brings up another question. Why are you creating multiple contexts? If one method calls another, you don't create another context in the next method. You should create the context in the called method, then release it at the end.

Craig Berntson
MCSD, Visual C# MVP,
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top