title: How do you access an image's metadata without causing a leak in native memory?
body: <p>The below use of BitmapDecoder, BitmapPropertySet, and BitmapTypedValue causes a leak in native memory. I'm hoping I'm just using them incorrectly... What is the correct way to use them? They don't implement IDisposable, so I'm not sure how to tell them to release the native memory they own.</p>
  
  <p>There's a chance this leak cannot be avoided. If so, how would I shield my UWP app from its affects?</p>
  
  <p>A Valloc trace  from Windows Performance Recorder/Analyzer shows many allocations with no Decommit Time. Commit stacks for these objects indicate they are related to fetching metadata or are pieces of metadata.</p>
  
  <p><a href="https://i.stack.imgur.com/WZ2dI.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/WZ2dI.png" alt="WPR-WPA Valloc trace of running the below test code"></a></p>
  
  <pre><code>using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Threading.Tasks;
  using Windows.Graphics.Imaging;
  using Windows.Storage;
  using Windows.Storage.Search;
  
  namespace Tests
  {
      [TestClass]
      public class TestBitmapDecoderLeak
      {
          // Path to a folder containing jpeg's that have metadata.
          private static readonly string ImageFolderPath = @"path to folder of jpegs";
  
          // The subset of the metadata you want to get
          private static readonly string [] MetadataPolicies = 
          {
              "System.Copyright",
              "System.SimpleRating",
              "System.Photo.ShutterSpeed",
              "System.Photo.Aperture",
              "System.Photo.CameraModel",
              "System.Photo.CameraManufacturer",
              "System.Photo.DateTaken",
              "System.Photo.ExposureTime",
              "System.Photo.Flash",
              "System.Photo.FlashEnergy",
              "System.Photo.ISOSpeed",
              "System.GPS.Longitude",
              "System.GPS.Latitude"
          };
  
          [TestMethod]
          public async Task RunTest()
          {
              // Start your profiler
              Debugger.Break();
  
              var imageFiles = await GetJpegFiles();
  
              // Get some metadata for each image, but do nothing with it.
              foreach (var imageFile in imageFiles)
              {
                  using (var fileStream = await imageFile.OpenReadAsync())
                  {
                      var decoder = await BitmapDecoder.CreateAsync(fileStream);
  
                      var DpiX            = decoder.DpiX;
                      var DpiY            = decoder.DpiY;
                      var PixelSizeWidth  = decoder.OrientedPixelWidth;
                      var PixelSizeHeight = decoder.OrientedPixelHeight;
                      var DecoderId       = decoder.DecoderInformation.CodecId;
  
                      var properties = await decoder.BitmapProperties.GetPropertiesAsync(MetadataPolicies);
  
                      var ShutterSpeed    = NullOrValType&lt;double&gt;(properties, "System.Photo.ShutterSpeed");
                      var Copyright       = NullOrRefType&lt;string&gt;(properties, "System.Copyright");
                      var SimpleRating    = NullOrValType&lt;UInt16&gt;(properties, "System.SimpleRating");
                      var Aperture        = NullOrValType&lt;double&gt;(properties, "System.Photo.Aperture");
                      var CameraModel     = NullOrRefType&lt;string&gt;(properties, "System.Photo.CameraModel");
                      var DateTaken       = NullOrValType&lt;DateTimeOffset&gt;(properties, "System.Photo.DateTaken");
                      var ExposureTime    = NullOrValType&lt;double&gt;(properties, "System.Photo.ExposureTime");
                      var Flash           = NullOrValType&lt;UInt16&gt;(properties, "System.Photo.Flash");
                      var FlashEnergy     = NullOrValType&lt;double&gt;(properties, "System.Photo.FlashEnergy");
                      var IsoSpeed        = NullOrValType&lt;UInt16&gt;(properties, "System.Photo.ISOSpeed");
                      var Longitude       = NullOrRefType&lt;double[]&gt;(properties, "System.GPS.Longitude");
                      var Latitude        = NullOrRefType&lt;double[]&gt;(properties, "System.GPS.Latitude");
                  }
              }
  
              // Remove these so they don't add noise to the trace
              imageFiles = null;
  
              // Ensure everything is cleaned up to the best of the GC's abilities
              GC.Collect();
              GC.WaitForPendingFinalizers();
  
              // Stop your profiler
              Debugger.Break();
          }
  
          private static async Task&lt;IEnumerable&lt;StorageFile&gt;&gt; GetJpegFiles()
          {
              var folder = await StorageFolder.GetFolderFromPathAsync(ImageFolderPath);
  
              var queryOptions = new QueryOptions
              {
                  FolderDepth = FolderDepth.Deep,
                  IndexerOption = IndexerOption.DoNotUseIndexer,
              };
              queryOptions.FileTypeFilter.Add(".jpg");
              queryOptions.FileTypeFilter.Add(".jpeg");
              queryOptions.FileTypeFilter.Add(".jpe");
  
              var folderQuery = folder.CreateFileQueryWithOptions(queryOptions);
              return await folderQuery.GetFilesAsync();
          }
  
          private static T NullOrRefType&lt;T&gt;(IDictionary&lt;string, BitmapTypedValue&gt; properties, string policy)
              where T : class
          {
              if (properties == null || !properties.ContainsKey(policy))
                  return null;
  
              return (T)properties[policy].Value;
          }
  
          private static T? NullOrValType&lt;T&gt;(IDictionary&lt;string, BitmapTypedValue&gt; properties, string policy)
              where T : struct
          {
              if (properties == null || !properties.ContainsKey(policy))
                  return null;
  
              return (T)properties[policy].Value;
          }
      }
  }
  </code></pre>