locked
Can't delete Crystal Report (8.5) file after done using it RRS feed

  • Question

  • User768014394 posted

    We have a bunch of old CR 8.5 reports for historical reasons we need to keep using and I'm converting some old VB6 code that uses them to C# by referencing the CRAXDRT.dll.  I am retrieving the report file (.RPT) from a database, opening it, setting the selection criteria via a parameter, printing it, then deleting the file from disk as it is no longer needed.

    The old VB6 code worked fine but we're getting rid of all VB6/ASP code in our company and it seems that doing this in C# is quite a bit faster.  However, I'm having an issue and I don't know if it's actually Crystal-related or due to improper coding techniques.

    The code is in a try-finally block and I am trying to free up resources by calling Marshal.FinalReleaseComObject and setting the object to null.  After that method exits, the report file needs to be deleted (it's being deleted in the same method it's created) but I get "the file is in use by another process" exception when I try to delete it.

    Because of this, the files (which are uniquely named) are being left in the folder and over time they accumulate.  I put some code in the Application_Start event handler in the global.asax to delete any files more than a day old but I feel this should be unnecessary.

    Do I need to set my object to null after calling Marshal.FinalReleaseComObject?  Or is there something else that's could be holding a lock on the file to where it can't be deleted?  I don't see a Close method or anything else on the report object that ought to be called.

     

    Monday, October 31, 2011 6:20 PM

All replies

  • User768014394 posted

    Here's a code sample (assume .RPT is already on disk):

    private Section _sectionDetails = null;
    private Collection<IReportObject> _pictures = new Collection<IReportObject>();
    
    private void PrintReport()
    {
    	Application application = null;
    	Report report = null;
    
    	try
    	{
    		application = new Application();
    		report = application.OpenReport(reportPath);
    	
    		_sectionDetails = null;
    
    		foreach (Section reportSection in report.Sections)
    		{
    			int reportObjectCount = reportSection.ReportObjects.Count;
    
    			for (int i = 1; i <= reportObjectCount; i++)
    			{
    				if (((IReportObject)reportSection.ReportObjects[i]).Kind == CRObjectKind.crOleObject)
    				{
    					// Find the images which are suppressed
    					if (((IReportObject)reportSection.ReportObjects[i]).Suppress)
    					{
    						if (_sectionDetails == null)
    						{
    							// The section with the suppressed images is the one that needs
    							// to be set up with the formatEventHandler
    							_sectionDetails = report.Sections[reportSection.Name];
    							_sectionDetails.format += new ISectionEvent_formatEventHandler(SectionDetails_Format);
    						}
    
    						// Unsuppress the image so it is visible
    						((IReportObject)reportSection.ReportObjects[i]).Suppress = false;
    
    						// Store to be accessed in SectionDetails_Format event handler
    						_pictures.Add((IReportObject)reportSection.ReportObjects[i]);
    					}
    				}
    			}
    		}
    		
    		report.PrintOut(false, 1, false);
    	}
    	finally
    	{
    		if (_pictures != null)
    		{
    			foreach (IReportObject picture in _pictures)
    			{
    				if (picture != null)
    					Marshal.FinalReleaseComObject(picture);
    			}
    
    			_pictures.Clear();
    		}
    
    		if (_sectionDetails != null)
    		{
    			_sectionDetails.format -= SectionDetails_Format;
    
    			Marshal.FinalReleaseComObject(_sectionDetails);
    			_sectionDetails = null;
    		}
    
    		if (report != null)
    		{
    			Marshal.FinalReleaseComObject(report);
    			report = null;
    		}
    
    		if (application != null)
    		{
    			Marshal.FinalReleaseComObject(application);
    			application = null;
    		}
    	}
    }
    
    private void SectionDetails_Format(object pFormattingInfo)
    {
    	// code omitted
    }

     

    Monday, October 31, 2011 6:34 PM
  • User1532945920 posted

     protected void CrystalReportViewer1_Unload(object sender, EventArgs e)
        {
            if (report != null)
            {
                report.Close();
                report.Dispose();
            }
        }

    Thursday, November 17, 2011 7:14 AM
  • User768014394 posted

    I'm not using the viewer

    Thursday, November 17, 2011 10:12 AM
  • User768014394 posted

    I need to very meticulous in my usage of the COM objects.  The reason the file cannot be deleted it because there are still references to some of those objects.

    From what I've found researching this, you cannot reference a property of a COM object if that property is a COM object too.  You have to declare a variable and assign it that property object and then use that variable.  When you're done with it, you need to call Marshal.FinalReleaseComObject and if you do that will all the objects you're using, you can now delete the file.  In the code below, look at the usage of the databse, tables, and table objects.

    That's fine, except now I can't get the event handler to execute.  It seems that when I try to release "section", the variable "_sectionDetails" which was a copy of "section" gets wiped out and that's the reason.  Below is my updated code:

    private string PrintReport(string reportPath, string database, string username, string password)
    {
    	ApplicationClass application = null;
    	Report report = null;
    	Database database = null;
    	DatabaseTables tables = null;
    	DatabaseTable table = null;
    
    	try
    	{
    		application = new ApplicationClass();
    		report = application.OpenReport(reportPath);
    
    		// There is only one table...
    		// This is a VB collection, which is 1-based; using index 0 will result in an exception
    		database = report.Database;
    		tables = database.Tables;
    		table = tables[1];
    		table.SetLogOnInfo(database, database, username, password);
    
    		_sectionDetails = null;
    
    		if (includeImages)
    		{
    			#region Set up images
    
    			Sections sections = report.Sections;
    
    			try
    			{
    				int reportSectionCount = sections.Count;
    
    				for (int i = 1; i <= reportSectionCount; i++)
    				{
    					Section section = sections[i];
    
    					try
    					{
    						int reportObjectCount = section.ReportObjects.Count;
    
    						for (int j = 1; j <= reportObjectCount; j++)
    						{
    							IReportObject reportObject = (IReportObject)section.ReportObjects[j];
    
    							if (reportObject.Kind == CRObjectKind.crOleObject)
    							{
    								// Find the images which are suppressed
    								if (reportObject.Suppress)
    								{
    									if (_sectionDetails == null)
    									{
    										// The section with the suppressed images is the one that needs
    										// to be set up with the formatEventHandler
    										_sectionDetails = section;
    											_sectionDetails.format += SectionDetails_Format;
    									}
    
    									// Unsuppress the image so it is visible
    									reportObject.Suppress = false;
    
    									// Store to be accessed in SectionDetails_Format event handler
    									_pictures.Add(reportObject);
    								}
    							}
    						}
    					}
    					finally
    					{
    						// Can't do this or the format event handler won't execute
    						//if (section != null)
    						//{
    						//    Marshal.FinalReleaseComObject(section);
    						//    section = null;
    						//}
    					}
    				}
    			}
    			finally
    			{
    				// Can't do this or the format event handler won't execute
    				//if (sections != null)
    				//{
    				//		Marshal.FinalReleaseComObject(sections);
    				//		sections = null;
    				//}
    			}
    
    			#endregion
    		}
    		
    		report.PrintOut(false, 1, false); 
    	}
    	finally
    	{
    		if (table != null)
    		{
    			Marshal.FinalReleaseComObject(table);
    			table = null;
    		}
    
    		if (tables != null)
    		{
    			Marshal.FinalReleaseComObject(tables);
    			tables = null;
    		}
    
    		if (database != null)
    		{
    			Marshal.FinalReleaseComObject(database);
    			database = null;
    		}
    
    		if (report != null)
    		{
    			Marshal.FinalReleaseComObject(report);
    			report = null;
    		}
    
    		if (application != null)
    		{
    			Marshal.FinalReleaseComObject(application);
    			application = null;
    		}
    	}
    }

    If I don't release the section and sections, I back to where I can't delete the report file, but the resulting printed report has the images loaded into it.  However, if I do release them I can delete the report file but the images won't be loaded because the event handler won't execute.

    Is there any way to get the references freed so I can delete the file but still have the Section_Format event handler execute so my images get loaded into the report?  My class does implement IDisposable so I can free them up in the dispose method, but I'd prefer to do them in the PrintReport method because that's where most of these variables are used.

    Wednesday, December 7, 2011 5:11 PM
  • User768014394 posted

    It seems that the first line of code inside the outer for loop is the problem here.  It's the line that says reportSection = reportSections[i].  If I comment everything within that loop, I am able to delete the report files.  Just enabling that one line however prevents that from happening.

    I'm reading the book ".NET and COM, The Complete Interoperability Guide" by Don Box, Sams Publishing.  In Chapter 20, page 954, it says that:

    All the adapter objects used by the .NET Framework's custom marshalers implement ICustomAdapter.  For an example of how this can be useful, consider a COM enumerator object (implementing IEnumVARIANT) that holds onto limited resources until it's destroyed.  Suppose you want to release it as soon as you're finished.  For example in C#:

    IEnumerator enumerator = comObj.GetEnumerator();

    try
    {
    	while (enumerator.MoveNext())
    	{
    		...
    	}
    }
    finally
    {
    	// How do I release the COM enumerator object here?
    }

    Calling Marshal.ReleaseComObject on the enumerator object returned by GetEnumerator doesn't work because it's not the original COM object.  It's not even a COM object at all; it's a .NET adapter object returne by the built-in IEnumVARIANT/IEnumerator custom marshaler!  To release the original COM object, you must cast the object implemting IEnumerator to ICustomAdapter.  Then you can call Marshal.ReleaseComObject on the object returned by the GetUnderlyingObject:

    finally
    {
    	// Here's how I release the COM enumerator object
    	ICustomAdapter adapter = (ICustomAdapter)enumerator;
    	Marshal.ReleaseComObject(adapter.GetUnderlyingObject());
    }
    
    

    Using C#'s foreach statement or VB .NET's For Each statement should not be used for such COM enumerators, because the object implementing IEnumerator is never directly exposed to you.

    This makes me question whether I am accessing the items in the collection properly.  Should this be the way you access contents of a COM collection instead of using a for loop?  I've tried using that code and I'm still having the problem of the "file in use by another process" when I try to delete the file.

    Tuesday, January 24, 2012 4:45 PM