System.AccessViolationException when copying data

2019-04-29 09:21发布

问题:

I am attempting to transfer content to portable device using this code snippet

IPortableDeviceValues values =
            GetRequiredPropertiesForContentType(fileName, parentObjectId);

        IStream tempStream;
        uint optimalTransferSizeBytes = 0;

        content.CreateObjectWithPropertiesAndData(
            values,
            out tempStream,
            ref optimalTransferSizeBytes,
            null);

        System.Runtime.InteropServices.ComTypes.IStream targetStream =
            (System.Runtime.InteropServices.ComTypes.IStream)tempStream;

        try
        {
            using (var sourceStream =
                   new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                var buffer = new byte[optimalTransferSizeBytes];
                int bytesRead;
                do
                {
                    bytesRead = sourceStream.Read(
                        buffer, 0, (int)optimalTransferSizeBytes);
                    IntPtr pcbWritten = IntPtr.Zero;
                    if (bytesRead < (int)optimalTransferSizeBytes)
                    {
                        targetStream.Write(buffer, bytesRead, pcbWritten);
                    }
                    else
                    {
                        targetStream.Write(buffer, (int)optimalTransferSizeBytes, pcbWritten);
                    }

                } while (bytesRead > 0);
            }
            targetStream.Commit(0);
        }
        finally
        {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(tempStream);
        }

When trying to execute targetStream.Write System.AccessViolationException occuried.

This is reproducible only for windows 10, creators update 1703.

Could you please tell me what I am doing wrong?

Thanks in advance.

回答1:

--Skip to the text in bold if you just want a fix!

Investigating further, the issue is in the low level native API: ISequentialStream::Write (which IStream Inherits) The MSDN page for this is: https://msdn.microsoft.com/en-us/library/windows/desktop/aa380014%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

Note the text for the argument: pcbWritten [out] reads 'A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object. The caller can set this pointer to NULL, in which case this method does not provide the actual number of bytes written.'

MS introduced a bug into this API (as called in PortableDeviceApi.dll) --> it is no longer checking whether or not pcbWritten is NULL before attempting to read/write from this variable - rather, it ALWAYS attempts to write the No Of Bytes written into this variable - if the API is called with this variable set to NULL then it BARFS. I have proven this is the case in pure native code by changing the way the API is called:

Works:

DWORD bytesWritten
IStream->Write(objectData, bytesRead, &bytesWritten)))

Fails:

IStream->Write(objectData, bytesRead, NULL))) <-- Note the NULL

Up in .Net, IStream.Write is marshalled thus:

void Write(byte[] pv,int cb,IntPtr pcbWritten)

and the demo code (from where we all likely got our implementations!) was:

 IntPtr pcbWritten = IntPtr.Zero  //<--Eg NULL
 IStream.Write(buffer, bytesRead, pcbWritten);

Proposed Solution - Change code to:

 IntPtr pcbWritten = Marshal.AllocHGlobal(4);
 IStream.Write(buffer, bytesRead, pcbWritten);
 Marshal.FreeHGlobal(pcbWritten);

This works around the issue - Hopefully MS will fix it to avoid us all having to re-distribute our products! The entire code is contained in PortableDeviceApi.dll (also including the stream stuff), which would explain why the entire world is not moaning about this issue and why it was not found during test. NB: For multi block copies in while loop, I suspect the alloc/free can be done outside the while without issue. Should also be safe if MS does fix the bug.

Credit: Alistair Brown (in our office) for fishing about, and allocating what should not need to be allocated and thus finding the issue.

Nigel



回答2:

After banging my head on this for days, calling this on another thread solved the problem:

var t = Task.Run(() => { device.TransferContentToDevice(fileName, parent.Id); });

t.Wait();


回答3:

I have this same issue also, it looks to be a bug/feature in the Creators Edition build 1703 of Windows 10.

The issue has been reported via the 'Visual Studio and .Net Framework' stream on Microsoft Connect: https://connect.microsoft.com/VisualStudio/Feedback/Details/3135829 (there is no specific area for Windows 10)

I would encourage anyone with this issue to head to the above and indicate that you can reproduce it also.

I have a simple repeater for it which may be downloaded here: http://scratch.veletron.com/WPD_FAIL_REPEATER.ZIP