Search code examples
.netgraphicsfontsgdi+embedded-resource

Embedded Font Causes a Crash


I have a WinForm app. I am using a custom font that is in my embedded resources.
It works at first, but then causes the program to crash after a while.
Using the following code as an example, if I keep resizing the form, forcing it to constantly redraw itself, it will crash within a few seconds. The message I get is 'Error in 'Form1_Paint()'. Object is currently in use elsewhere.'.
What am I doing wrong? How can I avoid this?
I got the font from here.
Thanks.

Imports System.Drawing.Text
Imports System.Runtime.InteropServices

Public Class Form1
    Friend Harabara As Font

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        LoadFonts()
    End Sub

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        Try
            e.Graphics.DrawString("This was drawn using the custom font 'Harabara'", Harabara, Brushes.Lime, 10.0F, 10.0F)
        Catch ex As Exception
            MsgBox("Error in Form1_Paint()'" & vbCrLf & ex.Message)
        End Try
    End Sub

    Public Sub LoadFonts()
        Try
            Harabara = GetFontInstance(My.Resources.HarabaraHand, 24.0F, FontStyle.Italic)
        Catch ex As Exception
            MsgBox("Error in 'LoadFonts()'" & vbCrLf & ex.Message)
        End Try
    End Sub

    Private Function GetFontInstance(ByVal data() As Byte, ByVal Size As Single, ByVal Style As FontStyle) As Font
        Dim result As Font
        Try
            Dim pfc = New PrivateFontCollection
            'LOAD MEMORY POINTER FOR FONT RESOURCE
            Dim FontPtr As System.IntPtr = Marshal.AllocCoTaskMem(data.Length)
            'COPY THE DATA TO THE MEMORY LOCATION
            Marshal.Copy(data, 0, FontPtr, data.Length)
            'LOAD THE MEMORY FONT INTO THE PRIVATE FONT COLLECTION
            pfc.AddMemoryFont(FontPtr, data.Length)
            'FREE UNSAFE MEMORY
            Marshal.FreeCoTaskMem(FontPtr)

            result = New Font(pfc.Families(0), Size, Style)
            pfc.Families(0).Dispose()
            pfc.Dispose()
        Catch ex As Exception
            'ERROR LOADING FONT. HANDLE EXCEPTION HERE
            MsgBox("Error in 'GetFontInstance()'" & vbCrLf & ex.Message)
            result = New Font(FontFamily.GenericMonospace, 8)
        End Try
        Return result
    End Function
End Class

Solution

  •         Marshal.FreeCoTaskMem(FontPtr)
    

    The MSDN documentation for PrivateFontCollection is too obtuse about this. But you need to keep the memory for the added font valid until you can no longer use the font. Or to put it another way, AddMemoryFont() does not make a copy of the font. So your program will fall over with a mysterious GDI+ error when it tries to access the font data and it got overwritten by another unmanaged memory allocation.

    Move the FreeCoTaskMem() call to a FormClosed event handler. Or don't bother if closing the form also terminates your program.