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.
--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
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With