extremeandy extremeandy - 15 days ago 5
ASP.NET (C#) Question

.NET PrivateFontCollection - how to release file locks when finished

I have a function which returns a PrivateFontCollection:

Public Shared Function GetCustomFonts() As PrivateFontCollection
Dim result = New PrivateFontCollection

Dim customFontFiles = {"Garamond.TTF", "Garamond-Bold.TTF", "Garamond-Italic.TTF", "EurostileExtended-Roman-DTC.TTF"}

For Each fontFile In customFontFiles
result.AddFontFile(Hosting.HostingEnvironment.MapPath("/Includes/" & fontFile))
Next

Return result
End Function


I then use the function as follows:

Using customFonts = Common.GetCustomFonts()
' Do some stuff here
End Using


I would expect that the files would be released, but they are still locked: I get the following error: 'The action can't be completed because the file is open in System. Close the file and try again.'

Shutting down the website in IIS doesn't help; we have to recycle the app pool for it to release.

Can anyone advise on how to use PrivateFontCollection in such a way that the files are released inbetween uses?

Answer

As a workaround, I loaded the fonts into memory and used 'AddMemoryFont' instead. See code below. Bear in mind this is the first time I've touched unmanaged resources in .NET so I can't guarantee that the memory management below is OK.

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

Public Class CustomFontService
    Implements IDisposable

    Dim _fontBuffers As List(Of IntPtr)
    Dim _collection As PrivateFontCollection

    Public Sub New()
        _collection = New PrivateFontCollection
        _fontBuffers = New List(Of IntPtr)

        Dim customFontFiles = {"Garamond.TTF", "Garamond-Bold.TTF", "Garamond-Italic.TTF", "EurostileExtended-Roman-DTC.TTF"}

        For Each fontFile In customFontFiles
            Dim fontBytes = System.IO.File.ReadAllBytes(Hosting.HostingEnvironment.MapPath("/Includes/" & fontFile))

            Dim fontBuffer As IntPtr = Marshal.AllocHGlobal(fontBytes.Length)
            Marshal.Copy(fontBytes, 0, fontBuffer, fontBytes.Length)

            _fontBuffers.Add(fontBuffer)

            _collection.AddMemoryFont(fontBuffer, fontBytes.Length)
        Next
    End Sub

    Public Function GetCustomFonts() As PrivateFontCollection
        Return _collection
    End Function

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            For Each buf In _fontBuffers
                If (buf <> IntPtr.Zero) Then
                    Marshal.FreeHGlobal(buf)
                End If
            Next

            ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
    Protected Overrides Sub Finalize()
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(False)
        MyBase.Finalize()
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class