asp.netvb.netdisposefilelockprivatefontcollection

.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?


Solution

  • 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