0

Edit: So, I see what's happening, I just don't understand why. My Dictionary<string, object> is storing the fonts just fine, but there's a particular function that runs, and when it runs, the Dictionary gets partly wiped out.

Before: enter image description here

After: enter image description here

The code that is running between these images is:

    public static int countLines(string a)
    {
        int count = 0;
        string line;
        System.IO.StreamReader file;

        try
        {
            file = new System.IO.StreamReader(@a);
        }

        catch (Exception ex)
        {
            MessageBox.Show("Missing " + a + "\n\n" + ex.ToString(), "Exception");
            return 0;
        }

        while ((line = file.ReadLine()) != null)
        {

            if (line.Substring(0, 2) == "10") { count++; }

        }

        file.Close();

        return count;
    }

---END EDIT---

I'm getting this tracedump when I use too many custom fonts. I'm using the same solution listed here, and have tried the voted fix (removing the dispose and the one marshalling call) which just leads immediately to a GDI+ error. Actually, I have a modified version of that code, so that I can call and get different fonts:

    #region Font setup
    //Setup code for custom fonts
    [DllImport("gdi32.dll")]
    private static extern IntPtr AddFontMemResourceEx(byte[] pbFont, int cbFont, IntPtr pdv, out uint pcFonts);

    [DllImport("gdi32.dll")]
    internal static extern bool RemoveFontMemResourceEx(IntPtr fh);

    static private IntPtr m_fh = IntPtr.Zero;
    static private PrivateFontCollection m_pfc = null;

    public static Font getFont(string fonttype, float size)
    {
        m_pfc = null;

        Font fnt = null;

        if (null == m_pfc)
        {
            Stream stmFont = null;

            // First load the font as a memory stream

            switch (fonttype)
            {
                case "MICR":
                    stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("QATestFileGenTools.Fonts.MICR.ttf");
                    break;

                case "HAND":
                    Random rnd = new Random(DateTime.Now.Millisecond);
                    int rr = rnd.Next(0, 3);

                    if (rr == 0)
                    {
                        stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("QATestFileGenTools.Fonts.hand1.ttf");
                        size += rnd.Next(0, 4);
                    }

                    if (rr == 1)
                    {
                        stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("QATestFileGenTools.Fonts.hand2.ttf");
                        size += rnd.Next(2, 6);
                    }

                    if (rr == 2)
                    {
                        stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("QATestFileGenTools.Fonts.hand3.ttf");
                        size += rnd.Next(6, 10);
                    }

                    break;

                case "SIG":
                    stmFont = Assembly.GetExecutingAssembly().GetManifestResourceStream("QATestFileGenTools.Fonts.signature.ttf");
                    break;

                default:
                    MessageBox.Show("Bad call to getFont()", "Error");
                    break;
            }

            if (null != stmFont)
            {

                // 
                // GDI+ wants a pointer to memory, GDI wants the memory.
                // We will make them both happy.
                //

                // First read the font into a buffer
                byte[] rgbyt = new Byte[stmFont.Length];
                stmFont.Read(rgbyt, 0, rgbyt.Length);

                // Then do the unmanaged font (Windows 2000 and later)
                // The reason this works is that GDI+ will create a font object for
                // controls like the RichTextBox and this call will make sure that GDI
                // recognizes the font name, later.
                uint cFonts;
                AddFontMemResourceEx(rgbyt, rgbyt.Length, IntPtr.Zero, out cFonts);


                // Now do the managed font
                IntPtr pbyt = Marshal.AllocCoTaskMem(rgbyt.Length);
                if (null != pbyt)
                {
                    Marshal.Copy(rgbyt, 0, pbyt, rgbyt.Length);

                    m_pfc = new PrivateFontCollection();
                    m_pfc.AddMemoryFont(pbyt, rgbyt.Length);
                    Marshal.FreeCoTaskMem(pbyt);
                }
            }
        }

        if (m_pfc.Families.Length > 0)
        {
            // Handy how one of the Font constructors takes a
            // FontFamily object, huh? :-)
            fnt = new Font(m_pfc.Families[0], size);
        }

        m_pfc.Dispose();
        return fnt;
    }
    //End setup for fonts
    #endregion

The GDI+ error I inevitably get if I remove the dispose() and the FreeCoTaskMem() is this.

Neither issue occurs immediately. It appears that I can run the call on average 20-25 times before either exception is thrown.

Any thoughts would be appreciated. When I was JUST using a single font, I never had an error (could generate 1000+ every time). It appears, anecdotally at least, that if I'm only ever calling two fonts, it's also not a problem. But when "HAND" is tossed into the mix, that seems to be the issue.

The method that calls it is:

public static byte[] GenCheckImage(string outputPath, string frontBack, string MICRline, string amount, string[] imageOptions, Dictionary<string, object> dictCfgIn, string names = "John Smith", string date = null, string lar = null)//, string payeeName, string date)
    {

        System.Reflection.Assembly thisExe;
        thisExe = System.Reflection.Assembly.GetExecutingAssembly();

        if (date == null)
        {
            date = DateTime.Now.AddDays(-4).ToString("d");
        }

        if (lar == null)
        {
            lar = "Some Dollars & xx/100";
        }

        if (frontBack == "F")
        {
            //Open the front image to frontImage Image source from embedded resource

            System.IO.Stream file;

            Random rnd = new Random(DateTime.Now.Millisecond);
            int a = rnd.Next(imageOptions.Length);

           // if (a == 0)  {file = thisExe.GetManifestResourceStream("QATestFileGenTools.checkFront1.bmp");}
           // else if (a == 1)  {file = thisExe.GetManifestResourceStream("QATestFileGenTools.checkFront2.bmp");}
            file = thisExe.GetManifestResourceStream(imageOptions[a]);
            Bitmap b;

            using (System.Drawing.Image frontImage = System.Drawing.Image.FromStream(file))//;
            {


            byte[] binaryData = new Byte[file.Length];
            long bytesRead = file.Read(binaryData, 0,
                                    (int)file.Length);

            Bitmap b2 = new Bitmap(frontImage);

            file.Close();

            Font micrFont = getFont("MICR", 18);

            Font objFont;
            if ((bool)dictCfgIn["Handwriting"])
                objFont = getFont("HAND", 20);
            else
                objFont = new System.Drawing.Font("Arial", 20);

            Font sigFont = getFont("SIG", 30);
            string[] sigName = NSCreateRAW.CreateRaw.genName();
            string sigString = sigName[0] + sigName[1] + rnd.Next(1, 6).ToString();

            amount = amount.TrimStart('0'); //Insert(2, "XYZ")
            amount = amount.Insert((amount.Length - 2), ".");
            //Draw something
            //Bitmap b = new Bitmap(frontImage);
            b = new Bitmap(frontImage);
            Graphics graphics = Graphics.FromImage(b);

            if (imageOptions[a] == "QATestFileGenTools.checkFront1.bmp")  // ProfitStars Check Template
            {
                graphics.DrawString(date, objFont, Brushes.Black, 690, 150);
                graphics.DrawString(lar, objFont, Brushes.Black, 150, 280);
                graphics.DrawString(names, objFont, Brushes.Black, 200, 215);
                graphics.DrawString(MICRline, micrFont, Brushes.Black, 200, 490);
                graphics.DrawString(amount, objFont, Brushes.Black, 985, 230);
                graphics.DrawString(sigString, sigFont, Brushes.Black, 680, 400);
            }

            else if (imageOptions[a] == "QATestFileGenTools.checkFront2.bmp")  // TWS Check Template
            {
                graphics.DrawString(date, objFont, Brushes.Black, 735, 105);
                graphics.DrawString(lar, objFont, Brushes.Black, 160, 250);
                graphics.DrawString(names, objFont, Brushes.Black, 200, 165);
                graphics.DrawString(MICRline, micrFont, Brushes.Black, 200, 490);
                graphics.DrawString(amount, objFont, Brushes.Black, 1020, 185);
                graphics.DrawString(sigString, sigFont, Brushes.Black, 690, 380);
            }

            else if (imageOptions[a] == "QATestFileGenTools.checkFront3.bmp")  // "Blank" Check Template
            {
                graphics.DrawString(date, objFont, Brushes.Black, 935, 110);
                graphics.DrawString(lar, objFont, Brushes.Black, 180, 275);
                graphics.DrawString(names, objFont, Brushes.Black, 220, 210);
                graphics.DrawString(MICRline, micrFont, Brushes.Black, 220, 470);
                graphics.DrawString(amount, objFont, Brushes.Black, 1000, 210);
                graphics.DrawString(sigString, sigFont, Brushes.Black, 690, 395);
            }

            else
            {
                //graphics.DrawString(MICRline, micrFont, Brushes.Black, 200, 490);
                //graphics.DrawString(amount, objFont, Brushes.Black, 985, 230);
            }

            if ((bool)dictCfgIn["Bad Images"] && (bool)dictCfgIn["Skew Images"])
            {
                Point[] destinationPoints = {
                    new Point(0, 50),   // destination for upper-left point of  
                  // original 
                    new Point(1170, 0),  // destination for upper-right point of  
                  // original 
                    new Point(30, 585)};
                graphics.DrawImage(b, destinationPoints);
            }

            if ((bool)dictCfgIn["Bad Images"] && (bool)dictCfgIn["Bar Images"])
            {
                Pen penWide = new Pen(Color.Black, 45);
                Pen penNarrow = new Pen(Color.Black, 2);
                Point[] points1 =
                {
                     new Point(0   , 250),
                     new Point(1200, 250)
                };
                Point[] points2 =
                {
                    new Point (0   , 280),
                    new Point (1200, 280)
                };
                graphics.DrawLines(penWide, points1);
                graphics.DrawLines(penNarrow, points2);
            }
            }
            //Convert to TIF - requires BitMiracle.LibTiff.Classic
            byte[] tiffBytes = GetTiffImageBytes(b, false);

            //File.WriteAllBytes("checkfront.tif", tiffBytes);

            //Encoding enc = Encoding.GetEncoding(1252);
            //string result = enc.GetString(tiffBytes);

            //File.Write("check2.tif", result);

            return tiffBytes;
        }

The using wrapper in the second bit of code is new - I thought it might help, but it didn't change the outcome at all.

Community
  • 1
  • 1
Jesse Williams
  • 653
  • 7
  • 21

1 Answers1

0

In the end, it turns out that the unmanaged memory was just a bad idea. Other processes were needing memory, and the pointer was becoming invalid.

I ended up using a PrivateFontCollection instead. I still used the dictionary from that collection simply because it made access easier and kept a lot of my code working in a similar fashion. The code sample I started with was just too volatile when working with large data sets.

I modified the solution here: http://www.emoticode.net/c-sharp/load-custom-ttf-font-file-and-use-it-on-any-winform-controls.html

Jesse Williams
  • 653
  • 7
  • 21
  • It was a simple bug, you can't call Marshal.FreeCoTaskMem() while the Font objects are in use. PrivateFontCollection has the same requirement. – Hans Passant Aug 12 '15 at 14:35
  • @HansPassant I actually removed the FreeCoTaskMem() and the Dispose() and ended up with the same issues. – Jesse Williams Aug 12 '15 at 16:55