What's wrong with this code? (Answer)

A few days ago, I posted some code with a problem in it. Here's the segment I posted:

IWMStreamConfig iStreamConfig = ...; // set somewhere else...
DirectShow.IWMMediaProps mediaProperties = (DirectShow.IWMMediaProps) iStreamConfig;

uint typeSize = 0;
mediaProperties.GetMediaType(null, ref typeSize); // call to get size

byte[] buffer = new byte[typeSize];
mediaProperties.GetMediaType(buffer, ref typeSize); // call to fetch values

fixed (byte* pBuffer = buffer)
{
mediaType = *((DirectShow._WMMediaType*) pBuffer);
waveFormatEx = *((DirectShow.WAVEFORMATEX*)
(mediaType.pbFormat.ToPointer()));
}

The problem is that I've used the IntPtr that came back in pbFormat, but it's an internal pointer. In other words, the buffer that I pass looks like this:

_WMMediaType structure

WAVEFORMATEX structure

There's a small chance that a garbage collection will happen between the time I call GetMediaType and the time I get into the fixed block, and if that happens, buffer will be moved. That will leave pbFormat pointing at where the WAVEFORMATEX used to be, not where it is now.

The basic rule here is "be suspicious whenever you get a pointer back from interop". In this case, you can't use the pointer at all, and need to depend on the size of the structure. In this case, since we know the two structures are allocated contiguously, we can use:

int mediaTypeSize = Marshal.SizeOf(mediaType);
waveFormatEx = *((DirectShow.WAVEFORMATEX*) (pBuffer + mediaTypeSize));

and things will be fine.

Interestingly, after I wrote the post, I came up with another method that also returns an array of the same structure, but instead of the "make the structures longer" approach, it uses two separate unmanaged allocations. That makes it safe to follow the pointer, but it also means that you need to call Marshal.FreeCoTaskMem() on the pointers to clean up after you're done with them.