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
 
Hi Craig,

The error logging is already part of the COM application. It's simply not getting there. It's failing on the call itself. It's as if the method doesn't exist. Yet, clearly it does, since it works on the development machine.
 
That tells me either 1) the parameters are not defined properly in the COM component method 2) The first executable line of code has an error 3) It's an authentication issue 4) COM+ is still using the old component signature.

Did you have VFP generate a new GUID for the component when you rebuilt it? Did you deploy the .tlb file with it?

Craig Berntson
MCSD, Visual C# MVP,
 
I second craig, more interesting is the code of the method called. You already posted the code calling the method. What you do inside it, the COM class code, is of interest, I doubt the method is missing or not olepublic, or something else makes COM fail to call it, I think the error must come from inside and the line of error reported is just the line of it's call by coincidence.

It is unusal to define a class with functions, classes only have events and methods, not procedures and functions, both methods and events are written as procedures, always, even if they return a value, they are procedures. You may try to change that. But I doubt it has any effect.

Bye, Olaf.
 
@Olaf: If a new GUID was not generated, COM+ could be using the old signature. In this case, it would think the new method doesn't exist and return an error.

Craig Berntson
MCSD, Visual C# MVP,
 
craigber said:
@Olaf: If a new GUID was not generated, COM+ could be using the old signature. In this case, it would think the new method doesn't exist and return an error.

That's what I thought--which is why I deleted the components from COM+ and reimported them. You can see the new method in the interface/methods folder in the Component Manager utility. I have also unregistered and re-registered the DLL, and uninstalled and reinstalled the entire application in an attempt to remove any problems with a mismatch in the interface.

I'm sure that something silly is at the root of all of this, but I can't explain why it works on one system and not on the other.

I use my error logging routine for debugging purposes as well. I think my next step will be to add a debugging entry to the log as the first statement in the method and see if it actually gets there. I'm betting it will not.

You want to see the code in the component? Here's the AddPromotion method:

Code:
	FUNCTION AddPromotion
	PARAMETERS rcInvoice, cPromoId
	LOCAL rmPromo, rsPromo, lFirstPromotion

	*	Validates the submitted promotion and adds it to the submitted invoice.
	
		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.
		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 

		IF NOT This.IsPromotionValid(rcInvoice, rsPromo, lFirstPromotion)
			RETURN .F.
		ENDIF 

		* Add the promotion to the invoice.
		* In other words, apply the appropriate discount or deduction.
		* If no discount or deduction is specified, then there is nothing to apply.
		WITH rsPromo.oRecord
		IF NOT (EMPTY(.discount) AND EMPTY(.deduction))
		
			* If there is a title associated with the promotion, discount the item.
			IF !EMPTY(.title_id) OR This.oSession.oSWO.swoItmDisc

				* If an order level discount exists, set it to the item level.
				This.MoveOrderDiscountToItems(rcInvoice)
				This.rmI.DiscountItemsInCursor(rcInvoice.rsItem, .Discount, .deduction, .title_id)
			ELSE
				* A order-level promotion may have either a discount or a deduction.
				* The deduction should take precedence.
				IF .deduction != 0
					rcInvoice.rsOrder.oRecord.Adj_Type_1 = "PROMOTION"
					rcInvoice.rsOrder.oRecord.Adjustmnt1 = -.deduction
				ELSE
					* Otherwise, apply the discount to the order.
					rcInvoice.rsOrder.oRecord.discount = .discount
				ENDIF
			ENDIF

		ENDIF
		ENDWITH

		* Update calculated fields
		This.UpdateCalcs(rcInvoice)
		
		* Add the promotion ID to the comments.
		* Note that this segment works based on very specific tokens in the comment of the order.
		* "Exclusive Promotion" indicates that an exclusive promotion has been added.
		* Otherwise, non-exclusive promotion will adde a token of "Promotions: ".  Note the 
		* plural.
		WITH rcInvoice.rsOrder.oRecord
			IF lFirstPromotion
				IF rsPromo.oRecord.lExclusive
					.Comment = "Exclusive Promotion: " + cPromoId + eol + .Comment
				ELSE
					.Comment = "Promotions: " + cPromoId + eol + .Comment
				ENDIF
			ELSE
				.Comment = STRTRAN(.Comment, "Promotions: ", "Promotions: " + cPromoId + ", ")
			ENDIF
		ENDWITH 

	ENDFUNC

 
At first glance your formatting looks, as if a closing END... is missing, but taking the whole function into an editor and beautifying it, it looks okay.

If you still get no trigger of an added Error method the only thing happening in between the call and the first line is marshalling variables from VFP to OLE/COM. I fear you have a problem there. rcInvoice.rsOrder.oRecord.Order_Key looks like a deep hierarchy of objects composed at each other. If I were you I'd rather redesign to let the seperate modules/objects of your app pass on data to each other, but not themselves. Especially if you add fox objects to ole objects. Is rsOrder an ADODB.Recordset, or is that taken from rs=recordset but really just a vfp object?

Bye, Olaf.
 
Well, there's been a little movement. Testing on the server is getting quite tedious, so I dedided to shorten the process by rebuilding the DLL and installing it manually on the server. To do so, I...

[ol 1]
[li]... copied the DLL, TLB, and VBR files to the server.[/li]
[li]... removed the component from COM+, [/li]
[li]... unregistered the DLL from Program Files, [/li]
[li]... copied the DLL, TLB, and VBR to Program Files, [/li]
[li]... re-registered the DLL.
[/ol]

Now, I ran the test procedure on the server. Note that I have not yet reinstalled to COM+.

The test procedure runs successfully!!!

Now I reinstate the component to COM+ and test it out on the web site. It fails with the following:

error '80004005'
/Payment.aspa, line 40

That's the call to AddPromotion().

So, I run the test program again. It now fails on the call to AddPromotion().

Adding the component to COM+ seems to be at the source of the trouble.

But now my procedure is fortified with debugging statements! So, I check the error log. It does, indeed, get into AddPromotion(), but fails inexplicably.

I also added debugging statements to a subprocedure called from AddPromotion() but those statements never fire. What I see in the error log is a very rapid shut down of the entire web application component. No error messages are logged--even though the error method for rmcInvoice is designed to send them to the error log.

Hmmm... more to ponder, but now it's time for dinner.
 
Back again. This is sort of becoming a troubleshooting journal. I hope this is helpful to others. It's kind of helping to keep me sane. (Well, maybe. My sanity was questionable before this problem came up.)

From the testing I did last night, it looks like there must be a programming error in the method. The following questions arise:

[ol 1]
[li]Why would adding the component to COM+ cause the failure?[/li]
[li]What is different about my manual install the DLL vs. my (Wise) Installation procedure?[/li]
[li]Why would this error come up on the server and not on the development machine?[/li]
[li]What is different about this method from the others that use rcInvoice as a parameter?[/li]
[li]Am I the only one who runs into these kind of problems? Are there any similar stories out there?[/li]
[/ol]
 
More information: I've added a few more debugging lines and find that the failure, AFTER adding PAWeb.DLL to COM+, occurs when instantiating another component within the method. Note: The first call to LogError() shows up in the error log. The second call does not.

Code:
* Find the promotion.
=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Instantiating rmPromotion.")
[COLOR=red]rmPromo = CREATEOBJECT(locRMPromotion)
rsPromo = rmPromo.CreateResource()
rsPromo.oRecord.Promo_ID = cPromoId[/color]
=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Attempting to locate the promotion ID: " + cPromoId)

The rmPromotion component is the business logic for the valid promotions part of the database. rsPromotion is the data container. (I hope you are beginning to see the pattern for the architecture of this application.) This practice of instantiating a "resource manager" (what I call the business logic component) and its associated resource for validation is a common practice throughout the application. Before you ask, I go to great lengths to clean up after each component has completed its task.

So, it seems that I have the same problem I had at the beginning--just at a different level. Now it is within the AddPromotion method--rather than the call to the method itself. Apparently, it only comes up after PAWeb is added to COM+. Also, before you ask, I have checked that rmPromotion and its interface are visible through the Component Services manager. rmPromotion is also used in other parts of the application.

It may be that this has all been the same problem from the beginning. It seems that an error within the component causes a general IDispatch error, or an "80004005" error. What I do not understand is why these errors are not showing up in my error log. I also don't understand why this is working on the development machine and not on the server.

Any new thoughts would be greatly appreciated.

 
Well, well... there is some light here. There is no reason that component needs to be in COM+. It isn't using any COM+ features. Just dropping a COM component into COM+ is not the way it should be done. Have you read my COM+ whitepaper?
Craig Berntson
MCSD, Visual C# MVP,
 
Hi Craig,

very interesting whitepaper, thanks for posting that. That still doesn't explain to me, why exactly the method errors, but it has to do with the nature of COM+ and the additional layers of it. As the pragmatical solution, simply don't register your COM server with COM+, then.

Bye, Olaf.
 
My oh my, Craig, I wish I had your white paper about the time you published it. It could have saved me a lot of headache.

In my case, I am using COM+ for security purposes only. I know that I am not using it for much of what it has to offer. Essentially, I want to allow anonymous users to access web sites--each with their own database. However, I want to specifically deny access to the database by the anonymous user. COM+ was a mechanism whereby I could provide access to components that perform specific operations on the data, but as a completely different user. That's really the limit of how and why I am using COM+. That's also why I use COM+ only on the web server.

Another major objective for the application is to use the same components (the same engine, if you will) in both desktop applications as well as the web applications. This has worked out rather well. No doubt my understanding COM and COM+ could be stronger. Like I said, I could have used your white paper years ago. Nonetheless, the architecture has worked out pretty well.

The change I have just introduced is doesn't work any differently than what I have been doing for several years now. That's what is so maddening. I'm feeling as though I can't trust the interface.

All things considered, I think this problem has been so difficult to chase down because the error handling doesn't seem to be working correctly. The error method clearly captures an error and sends it to an error log. I have not used COMRETURNERROR(). Basically, I handle returning error information on my own. In any case, it should be in the error log--but it is not.

COM+ is unique to the server. Clearly, adding the component to COM+ is exacerbating this problem. Yet even this is unique to the new method, which does nothing that many other methods do. I have been programming too long to insist that I can't be doing anything wrong. Yet I still can't see what I have introduced--even if it's a problem with my code--that should allow this function to work on one machine and not on the other, or to work without COM+ but not with it.

So, having explained all of this, what would you suggest as a next move?
 
Error handling can be tricky, which is why I suggested logging things locally. Not an error log, but specific lines saying "Started method", "Processing X", "Processing Y", etc. I've put these logging messages on every line of code before, just to see what is happening.


Craig Berntson
MCSD, Visual C# MVP,
 
Yes, that's what I am doing. I just use the same logging mechanism that I use to log errors. That's also how I have uncovered what I have learned so far. Here, I'll attach the error log from the last run. Error code zero means that the message was logged for debugging purposes. I changed the extention to HTM so the file would present. Debugging can be turned on or off in the configuration file for the application.

My error method in rmcInvoice is pretty straight forward:

Code:
PROCEDURE Error
LPARAMETERS nError, cMethod, nLine, cMessage
LOCAL cErrorLog

	IF EMPTY(cMessage) AND nError > 0
		cMessage = MESSAGE()
	ENDIF

	* Handle an error in referencing a node in the aResource array property.
	IF INLIST(nError, 11, 232)  AND "NODECOUNT" $ UPPER(cMethod)
		This.AddToArray(oCluster)
		cMessage = [Error handling the cluster array.]
		RETRY
	ENDIF

	* If the error number is greater than zero, add the name of this object to the method.
	IF nError > 0
		cMethod = This.Name + [.] + cMethod
	ENDIF
	=LogError(This.oSession, nError, cMethod, nLine, cMessage)

*!*	* Make the error information available to the calling procedure.
*!*	COMreturnerror(cMethod+' err#='+str(nError,5)+'line='+str(nline,6)+' '+message(),_VFP.ServerName)

ENDPROC

You can see that I have commented out the use of COMReturnError() because that function doesn't work when the component is used directly. I'm capturing all the pertinent information in the error log anyway.

So, why wouldn't an error calling another component be logged? It's like the error method doesn't get called. Now, I do get some real--though not catastrophic errors logged under normal circumstances. Why not this one? And what is causing the error to begin with?
 
 http://www.pubassist.com/articles/samplepaweberrorlog.htm
There is error handling precedences:
1. Try..Catch, 2. Error Method, 3. ON ERROR

Just thinking. If an OLE error happens, ON ERROR might not be triggered inside the OLE server, only the OLE client (or IIS using the OLE server) is erroring. I don't know if this really is the case, but it's what you experience. If the error doesn't happen in your main component, but in some other object involved, your ON ERROR handler might never be triggered.

You could try to put your method code in one large or several smaller Try..Catch blocks. But that wouldn't do much better than logging a message before each line of code is executed and save right to a file. You better make it a seperate log file.

You might be better off debugging, there are some pointers on how to do that from ASP:
here and here
Bye, Olaf.
 
Well I've placed my LogError() calls on either side of the statement that instantiates rmPromo. That's definitely the statement that is failing. Of course, now the question is Why? And what do I do about it? But, no error is actually logged. I get the IDispatch Exception code 0--which tells me nothing. I'll toy around with this statement some more tomorrow.

I just want to say, thanks for being out there and paying attention.

Ron
 
>Well I've placed my LogError() calls on either side of the statement that instantiates rmPromo. That's definitely the statement that is failing. Of course, now the question is Why? And what do I do about it? But, no error is actually logged.

If no error is logged, that's a sign it occurs even before your first log call. Or the logging itself fails (try another log file loaction, perhaps). Do log every line.

Bye, Olaf.
 
** Eating popcorn..

I am so intrigued to find out what's the culprit!!!


Ali Koumaiha
TeknoSoft Inc.
Michigan
 
Hi Olaf,

I'm not sure I made myself clear, or perhaps I am missing the significance of your suggestion to post messages elsewhere. But to test your theory, I purposely introduced an error prior to the attempt to instantiate rmPromo. Here's the code segment:


Code:
* Let's check the error logging by purposely introducing an error.		
=LogError(This.oSession, 0, PROGRAM(), LINENO(), "Purposely introducing an error.")
rmPromo = CREATEOBJECT("NotARealComponent")

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

Here is the pertinent portion of the resulting error log. I've highlighted the resulting error in red.

Code:
Time: 07/27/12 10:20:53 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  261 Message: Entering the rmcInvoice.AddPromotion() method.
Time: 07/27/12 10:20:53 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  267 Message: Applying promotion to invoice# 1353- 1.
Time: 07/27/12 10:20:53 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  283 Message: Purposely introducing an error.
[COLOR=red]Time: 07/27/12 10:20:53 AM Error:  1733 Procedure: rmcInvoice.addpromotion                            Line:  284 Message: Class definition NOTAREALCOMPONENT is not found. Stack: 
LOGERROR
WEBINVOICE.RMCINVOICE.ERROR
WEBINVOICE.RMCINVOICE.ADDPROMOTION[/color]
Time: 07/27/12 10:20:53 AM Error:     0 Procedure: WEBINVOICE.RMCINVOICE.ADDPROMOTION                 Line:  287 Message: Instantiating rmPromotion.
Time: 07/27/12 10:20:58 AM Error:     0 Procedure: WEBINVOICE.CLEAR                                   Line:  109 Message: Releasing the current invoice.

So it seems, under normal circumstances, even with COM+, the error logging works. So, I guess this is not a "normal circumstance". Whatever it is, introducing COM+ breaks it. The procedure completes normally without COM+. (I just verified this again. Even with the purposeful error, the test procedure completes without a problem.)
 
I don't know if this is pertinent, but it seems when there is a failure like this, there should be an entry in one of the system event logs. So, I checked the application log using the event viewer. This looks like it comes from when I reinstated PAWeb.dll to COM+. Can anyone tell me what it means?

Event Viewer> Application said:
Controlled registration of this component failed. It has been registered directly. If you are not using partitions you can ignore this warning. If you are using partitions you may need to add support components that are required before controlled registration of this component can succeed. Check your documentation for details.C:\Program Files\PAWeb\PAWeb.DLL

Process Name: dllhost.exe
Comsvcs.dll file version: ENU 2001.12.4720.3959 shp

Also, where else might some useful information about the actual failure be found?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top