cancel
Showing results for 
Search instead for 
Did you mean: 

Memory Leak using SAP Crystal Reports Runtime for Visual Studio

alberto_quiros
Discoverer
0 Kudos

Hello,

We are using SAP Crystal Reports for Visual Studio Runtime (Service Pack 15) as our printing engine in a .NET 4.6 Windows Service and we have found out a memory leak that we cannot avoid and it produces a serious memory increase in the long run.

The fact is that it seems that there are several unmanaged objects from the runtime (C++) that are not disposed / free and produces the memory leak because we have not found any memory problem in .NET objects (we have observed it using a Memory Profiler to analyse the process memory).

I've reviewed the code and apply a Fix we found for the previous runtime (CR 11 Runtime versión) but the memory leak persists .

The code is quite simple and it assures the dipose of the CR objects, I attached the code used to execute the report:


public GReportResult PrintReport(string xmldata, int copies, string layoutpath, string outputpath, string outputformat, string filename, string document, string pathSchema)

        {

            int result = 0;

            string error = string.Empty;

            try

            {

                using (SafeReportDocument report = new SafeReportDocument())

                {

                    using (DataSet reportData = new DataSet())

                    {

                        reportData.Locale = CultureInfo.InvariantCulture;

                        try

                        {

                            reportData.EnforceConstraints = false;

                            using (var xmlSchemaReader = new System.IO.StreamReader(Environment.ExpandEnvironmentVariables(pathSchema)))

                            {

                                reportData.ReadXmlSchema(xmlSchemaReader);

                                using (var xmlDataReader = new System.IO.StringReader(xmldata))

                                {

                                    reportData.ReadXml(xmlDataReader);

                                    try

                                    {

                                        report.Load(Environment.ExpandEnvironmentVariables(layoutpath));

                                        try

                                        {

                                            report.SetDataSource(reportData);

                                            try

                                            {

                                                switch (outputformat)

                                                {

                                                    case "Raw":

                                                        report.PrintOptions.PrinterName = outputpath;

                                                        report.PrintToPrinter(copies, false, 0, 0);

                                                        break;

                                                    case "Pdf":

                                                        report.ExportToDisk(ExportFormatType.PortableDocFormat, filename);

                                                        break;

                                                    case "Excel":

                                                        report.ExportToDisk(ExportFormatType.Excel, filename);

                                                        break;

                                                    case "Rtf":

                                                        report.ExportToDisk(ExportFormatType.EditableRTF, filename);

                                                        break;

                                                    case "Html":

                                                        report.ExportToDisk(ExportFormatType.HTML40, filename);

                                                        break;

                                                }

                                            }

                                            catch (Exception prn)

                                            {

                                                //PRINTERROR

                                                result = 1;

                                                error = prn.Message;

                                            }

                                        }

                                        catch (Exception data)

                                        {

                                            //PMDATASOURCEERROR

                                            result = 2;

                                            error = data.Message;

                                        }

                                    }

                                    catch (Exception load)

                                    {

                                        //PMLAYOUTERROR

                                        result = 3;

                                        error = load.Message;

                                    }

                                    }

                                }

                            }

                        }

                        catch (Exception px)

                        {

                            //PMXMLDATAERROR

                            result = 4;

                            error = px.Message;

                        }

                    }

                }

                GC.Collect();

                GC.WaitForPendingFinalizers();

                GC.Collect();

            }

            catch (Exception ex)

            {

                //PMERRINSTANCECMDOCUMENT

                result = 5;

                error = ex.Message;

            }

            GReportResult reportResult = new GReportResult(result, error);

            return reportResult;

        }

And the SafeReportDocument.class (used to fix the memory leak in CR RT 11):


public class SafeReportDocument : ReportDocument, IDisposable

    {

        private void CleanGlobalEvents()

        {

            Delegate domainUnloadDelegate = (Delegate)typeof(AppDomain).GetField("_domainUnload",

                BindingFlags.Instance | BindingFlags.NonPublic).GetValue(AppDomain.CurrentDomain);

            Delegate[] invocationList = domainUnloadDelegate.GetInvocationList();

            Delegate ev;

            for (short i = 0; i < invocationList.Length; i++)

            {

                ev = invocationList[i];

                if (ev.Target != null && ev.Target.Equals(this))

                {

                    AppDomain.CurrentDomain.DomainUnload -= (EventHandler)ev;

                }

            }

            Delegate processExitDelegate = (Delegate)typeof(AppDomain).GetField("_processExit",

                BindingFlags.Instance | BindingFlags.NonPublic).GetValue(AppDomain.CurrentDomain);

            invocationList = processExitDelegate.GetInvocationList();

            for (short i = 0; i < invocationList.Length; i++)

            {

                ev = invocationList[i];

                if (ev.Target != null && ev.Target.Equals(this))

                {

                    AppDomain.CurrentDomain.ProcessExit -= (EventHandler)ev;

                }

            }

        }

        /// <summary>

        /// Cleans up resources

        /// </summary>

        /// <param name="disposing"></param>

        protected override void Dispose(bool disposing)

        {

            if (disposing)

            {

                this.CleanGlobalEvents();

            }

            base.Dispose(disposing);

        }

    }

Is there any issue and fix regarding this case?

Thanks a lot for your time and your support.

Kind regards,

Yamel.

Accepted Solutions (1)

Accepted Solutions (1)

0 Kudos

Hi Yamel,

I'm having problems inserting your code into my test app.

Can you zip up the complete test app and attach it using the advanced editor?

Rename the zip file to *.txt.

I don't see anywhere where you are closing and disposing of the report object? Is that done somewhere else?

Thanks

Don

alberto_quiros
Discoverer
0 Kudos

Hi Don, Thank you for your answer.

I attached bellow the code of the main print routine. About the CLose and Dispose calls, I used the "using" statment to assure the Disposal of the object, since the Close method only calls the Dispose method also if the report is not marked to be reused (that's not the case).

Thanks in advance for your support.

Kind regards,

Yamel.

.

0 Kudos

Thanks for the file, I don't have time to create a command line app to make the call to the service.

And what happens if you specifically call:

report.close();

This tells the Engine ( crpe32.dll ) to disconnect from the datasource and close the connection and other clean up code including memory allocations and then actually close the report object.

just before you do the GC.Collect... Or you can add it after each export function but be careful because if the report is large and the export takes time it can throw an exception if it's still busy. But technically it the Print/Export functions should not return until the job is complete.

We know there are leaks in most 3rd party dependencies, this engine is very old and we have plugged all of the leaks we have control over.

Printer drivers are nortorious for leaks as well, or the Framework conversion from DEVMODEW to System.Drawing.Printing...

Don

alberto_quiros
Discoverer
0 Kudos

Thanks for your support Don.

I was reviewing the Close and Dispose methods implementation and I've seen that the Close method calls an InternalClose method with a 'true' parameter (called bSetupForNextReport) that free all resources but initalize again the report. On the other, hand calling the Dispose method, this method is also called also but with a 'false' parameter that avoid to initilize again the report. Then, I thought that it should be better not calling the Close method to avoid any object initialization since the report is not being reused and avoid other possible memory leaks. Anyway, I have tried also calling explicity the Close and Dispose methods of the dataset and ReportDocument but the effect is the same...

Maybe, as you are suggesting, the problem is related with the Printer Drivers or the framework conversion is the root cause, since I have seen some objects of System.Drawing.Printing not disposed with a Memory Profiler. Anyway, I will try to print out to file in order to find out if the problem comes from the printing drivers or the fw conversion.

Thanks again for your time and support.

Kind regards,

Yamel.

0 Kudos

OK, continue using report.close(), it is somethign you must do.

Answers (1)

Answers (1)

former_member183750
Active Contributor
0 Kudos

Hi Alberto

I don't know where the SafeReportDocument.class for CR 11 came from(?). And I suspect this is not applicable to CRVS anyhow. First time I hear of mem problems with CRVS...

You say: The code is quite simple and it assures the dispose of the CR objects,

But in your code I do not see an explicit call to .Close and .Dispose of the report object(?). Also, I note that you are using ADO .NET Datasets. These often are a source of memory issue as well and doing a clean up on these (.Close and .Dispose) is something to consider as well.

- Ludek

Senior Support Engineer AGS Product Support, Global Support Center Canada

Follow me on Twitter

Got Enhancement ideas? Use the SAP Idea Place

alberto_quiros
Discoverer
0 Kudos

Hi Ludek,

SafeReportDocument is only a wrapping class that unregister some events to assure correct disposal. I have tested it without this wrapping class but the memory leak is there also.

On the other hand, the code assures objects disposal thanks to the "using" statement (https://msdn.microsoft.com/en-us/library/yh598w02.aspx ).

Thanks for your quick answer. Kind regards,

Yamel.