Drag and Drop large virtual files with IStream using VirtualFileDataObject - c#

I am successfully using VirtualFileDataObject code from Delay's blog, but i want to avoid streaming the entire file into memory.
I found this previously answered question on Stack Overflow Drag and Drop large virtual files from c# to Windows Explorer The question was answered by matthieu, by changing the signature of the SetData method.
Here is my problem, after changing the signature of the SetData method, other places that call it are still looking for the old signature.
Here is the original SetData;
public void SetData(short dataFormat, int index, Action<Stream> streamData)
{
_dataObjects.Add(
new DataObject
{
FORMATETC = new FORMATETC
{
cfFormat = dataFormat,
ptd = IntPtr.Zero,
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = index,
tymed = TYMED.TYMED_ISTREAM
},
GetData = () =>
{
// Create IStream for data
var ptr = IntPtr.Zero;
var iStream = NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true);
if (streamData != null)
{
// Wrap in a .NET-friendly Stream and call provided code to fill it
using (var stream = new IStreamWrapper(iStream))
{
streamData(stream);
}
}
// Return an IntPtr for the IStream
ptr = Marshal.GetComInterfaceForObject(iStream, typeof(IStream));
Marshal.ReleaseComObject(iStream);
return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
},
});
}
matthieu suggested to change it to;
public void SetData(short dataFormat, int index, Stream stream)
{
...
var iStream = new StreamWrapper(stream);
...
// Ensure the following line is commented out:
//Marshal.ReleaseComObject(iStream);
return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
...
}
After I make these changes the following call will not work; ( and this is where i need help)
How do i fix this call;
foreach (var fileDescriptor in fileDescriptors)
{
**SetData(FILECONTENTS, index, fileDescriptor.StreamContents);**
index++;
}
Basically changing "Action streamData" To "Stream stream" is causing my problems. I am not sure on how to call it after the changes are made.
All this code comes from Delays VirtualFileDataObject. I don't know if i should post it on here or not. But if you follow the link above it will take you to the blog so you can view it.
I am so close, just can't figure this last step out, thanks for taking a look

I've had exactly the same problem. Here is what I did to fix this issue (which as you say has not been fully addressed in the other answer)
1) Modify FileDescriptor's StreamContents property from this:
public Action<Stream> StreamContents { get; set; }
to this:
public Func<Stream> StreamContents { get; set; }
(instead of passing a Stream the client can write, we'll expect a Stream we can read from, which is exactly how Explorer works and what it expects)
2) Modify the SetData method overload from this:
public void SetData(short dataFormat, int index, Action<Stream> streamData)
to this:
public void SetData(short dataFormat, int index, Func<Stream> streamData)
3) change SetData code's GetData lambda to this:
GetData = () =>
{
ManagedIStream istream = null;
if (streamData != null)
{
Stream stream = streamData();
if (stream != null)
{
istream = new ManagedIStream(stream);
}
}
IntPtr ptr = istream != null ? Marshal.GetComInterfaceForObject(istream, typeof(IStream)) : IntPtr.Zero;
return new Tuple<IntPtr, int>(ptr, NativeMethods.S_OK);
},
4) add this ManagedIStream class to the code (you can also delete the IStreamWrapper class completely)
private class ManagedIStream : IStream
{
private Stream _stream;
public ManagedIStream(Stream stream)
{
_stream = stream;
}
public void Clone(out IStream ppstm)
{
throw new NotImplementedException();
}
public void Commit(int grfCommitFlags)
{
throw new NotImplementedException();
}
public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten)
{
throw new NotImplementedException();
}
public void LockRegion(long libOffset, long cb, int dwLockType)
{
throw new NotImplementedException();
}
public void Read(byte[] pv, int cb, IntPtr pcbRead)
{
int read = _stream.Read(pv, 0, cb);
if (pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, read);
}
}
public void Revert()
{
throw new NotImplementedException();
}
public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition)
{
long newPos = _stream.Seek(dlibMove, (SeekOrigin)dwOrigin);
if (plibNewPosition != IntPtr.Zero)
{
Marshal.WriteInt64(plibNewPosition, newPos);
}
}
public void SetSize(long libNewSize)
{
_stream.SetLength(libNewSize);
}
public void Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
{
const int STGTY_STREAM = 2;
pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG();
pstatstg.type = STGTY_STREAM;
pstatstg.cbSize = _stream.Length;
pstatstg.grfMode = 0;
if (_stream.CanRead && _stream.CanWrite)
{
const int STGM_READWRITE = 0x00000002;
pstatstg.grfMode |= STGM_READWRITE;
return;
}
if (_stream.CanRead)
{
const int STGM_READ = 0x00000000;
pstatstg.grfMode |= STGM_READ;
return;
}
if (_stream.CanWrite)
{
const int STGM_WRITE = 0x00000001;
pstatstg.grfMode |= STGM_WRITE;
return;
}
throw new IOException();
}
public void UnlockRegion(long libOffset, long cb, int dwLockType)
{
throw new NotImplementedException();
}
public void Write(byte[] pv, int cb, IntPtr pcbWritten)
{
_stream.Write(pv, 0, cb);
if (pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt32(pcbWritten, cb);
}
}
}
That's it. Now you can use the code like this (using the same sample as in the original article available here: http://dlaa.me/blog/post/9913083):
new VirtualFileDataObject.FileDescriptor
{
Name = "Alphabet.txt",
Length = 26,
ChangeTimeUtc = DateTime.Now.AddDays(-1),
StreamContents = () =>
{
var contents = Enumerable.Range('a', 26).Select(i => (byte)i).ToArray();
MemoryStream ms = new MemoryStream(contents); // don't dispose/using here, it would be too early
return ms;
}
};

Related

Why is it not possible to delete files quickly (<60s) across threads in aspnet?

I get error
System.IO.IOException: 'The process cannot access the file 'xxx' because it is being used by another process.'
when I try to delete a temp file in a background worker service in aspnet core.
I am eventually allowed to delete the file after about a minute (52s, 73s).
If I change garbage collection to workstation mode, I may instead delete after ~1s (but still, a delay).
I have tried a combination of FileOptions to no avail, including FileOptions.WriteThrough.
When the controller writes the file, I use
FlushAsync(), Close(), Dispose() and 'using' (I know it's overkill.)
I also tried using just File.WriteAllBytesAsync, with same result.
In the background reader, I as well use Close() and Dispose().
(hint: background reader will not allow me to use DeleteOnClose,
which would have been ideal.)
As I search stackoverflow for similar 'used by another process' issues,
all those I have found eventually resolve to
'argh it turns out I/he still had an extra open instance/reference
he forgot about',
but I have not been able to figure out that I am doing that.
Another hint:
In the writing controller, I am able to delete the file immediately
after writing it, I presume because I am still on the same thread?
Is there some secret knowledge I should read somewhere,
about being able to delete recently open files, across threads?
UPDATE: Here relevant(?) code snippets:
// (AspNet Controller)
[RequestSizeLimit(9999999999)]
[DisableFormValueModelBinding]
[RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)]
[HttpPost("{sessionId}")]
public async Task<IActionResult> UploadRevisionChunk(Guid sessionId) {
log.LogWarning($"UploadRevisionChunk: {sessionId}");
string uploadFolder = UploadFolder.sessionFolderPath(sessionId);
if (!Directory.Exists(uploadFolder)) { throw new Exception($"chunk-upload failed"); }
var cr = parseContentRange(Request);
if (cr == null) { return this.BadRequest("no content range header specified"); }
string chunkName = $"{cr.From}-{cr.To}";
string saveChunkPath = Path.Combine(uploadFolder,chunkName);
await streamToChunkFile_WAB(saveChunkPath); // write-all-bytes.
//await streamToChunkFile_MAN(saveChunkPath); // Manual.
long crTo = cr.To ?? 0;
long crFrom = cr.From ?? 0;
long expected = (crTo - crFrom) + 1;
var fi = new FileInfo(saveChunkPath);
var dto = new ChunkResponse { wrote = fi.Length, expected = expected, where = "?" };
string msg = $"at {crFrom}, wrote {dto.wrote} bytes (expected {dto.expected}) to {dto.where}";
log.LogWarning(msg);
return Ok(dto);
}
private async Task streamToChunkFile_WAB(string saveChunkPath) {
using (MemoryStream ms = new MemoryStream()) {
Request.Body.CopyTo(ms);
byte[] allBytes = ms.ToArray();
await System.IO.File.WriteAllBytesAsync(saveChunkPath, allBytes);
}
}
// stream reader in the backgroundService:
public class MyMultiStream : Stream {
string[] filePaths;
FileStream curStream = null;
IEnumerator<string> i;
ILogger log;
QueueItem qItem;
public MyMultiStream(string[] filePaths_, Stream[] streams_, ILogger log_, QueueItem qItem_) {
qItem = qItem_;
log = log_;
filePaths = filePaths_;
log.LogWarning($"filepaths has #items: {filePaths.Length}");
IEnumerable<string> enumerable = filePaths;
i = enumerable.GetEnumerator();
i.MoveNext();// necessary to prime the iterator.
}
public override bool CanRead { get { return true; } }
public override bool CanWrite { get { return false; } }
public override bool CanSeek { get { return false; } }
public override long Length { get { throw new Exception("dont get length"); } }
public override long Position {
get { throw new Exception("dont get Position"); }
set { throw new Exception("dont set Position"); }
}
public override void SetLength(long value) { throw new Exception("dont set length"); }
public override long Seek(long offset, SeekOrigin origin) { throw new Exception("dont seek"); }
public override void Write(byte[] buffer, int offset, int count) { throw new Exception("dont write"); }
public override void Flush() { throw new Exception("dont flush"); }
public static int openStreamCounter = 0;
public static int closedStreamCounter = 0;
string curFileName = "?";
private FileStream getNextStream() {
string nextFileName = i.Current;
if (nextFileName == null) { throw new Exception("getNextStream should not be called past file list"); }
//tryDelete(nextFileName,log);
FileStream nextStream = new FileStream(
path:nextFileName,
mode: FileMode.Open,
access: FileAccess.Read,
share: FileShare.ReadWrite| FileShare.Delete,
bufferSize:4096, // apparently default.
options: 0
| FileOptions.Asynchronous
| FileOptions.SequentialScan
// | FileOptions.DeleteOnClose // (1) this ought to be possible, (2) we should fix this approach (3) if we can fix this, our issue is solved, and our code much simpler.
); // None); // ReadWrite); // None); // ReadWrite); //| FileShare.Read);
log.LogWarning($"TELLUS making new stream [{nextFileName}] opened:[{++openStreamCounter}] closed:[{closedStreamCounter}]");
curFileName = nextFileName;
++qItem.chunkCount;
return nextStream;
}
public override int Read(byte[] buffer, int offset, int count) {
int bytesRead = 0;
while (true) {
bytesRead = 0;
if (curStream == null) { curStream = getNextStream(); }
try {
bytesRead = curStream.Read(buffer, offset, count);
log.LogWarning($"..bytesRead:{bytesRead} [{Path.GetFileName(curFileName)}]"); // (only show a short name.)
} catch (Exception e) {
log.LogError($"failed reading [{curFileName}] [{e.Message}]",e);
}
if (bytesRead > 0) { break; }
curStream.Close();
curStream.Dispose();
curStream = null;
log.LogWarning($"TELLUS closing stream [{curFileName}] opened:[{openStreamCounter}] closed:[{++closedStreamCounter}]");
//tryDelete(curFileName); Presumably we can't delete so soon.
bool moreFileNames = i.MoveNext();
log.LogWarning($"moreFileNames?{moreFileNames}");
if (!moreFileNames) {
break;
}
}
return bytesRead;
}
..
// Background worker operating multistream:
public class BackgroundChunkWorker: BackgroundService {
ILogger L;
ChunkUploadQueue q;
public readonly IServiceScopeFactory scopeFactory;
public BackgroundChunkWorker(ILogger<int> log_, ChunkUploadQueue q_, IServiceScopeFactory scopeFactory_) {
q = q_; L = log_;
scopeFactory = scopeFactory_;
}
override protected async Task ExecuteAsync(CancellationToken cancel) { await BackgroundProcessing(cancel); }
private async Task BackgroundProcessing(CancellationToken cancel) {
while (!cancel.IsCancellationRequested) {
try {
await Task.Delay(1000,cancel);
bool ok = q.q.TryDequeue(out var item);
if (!ok) { continue; }
L.LogInformation($"item found! {item}");
await treatItemScope(item);
} catch (Exception ex) {
L.LogCritical("An error occurred when processing. Exception: {#Exception}", ex);
}
}
}
private async Task<bool> treatItemScope(QueueItem Qitem) {
using (var scope = scopeFactory.CreateScope()) {
var ris = scope.ServiceProvider.GetRequiredService<IRevisionIntegrationService>();
return await treatItem(Qitem, ris);
}
}
private async Task<bool> treatItem(QueueItem Qitem, IRevisionIntegrationService ris) {
await Task.Delay(0);
L.LogWarning($"TryAddValue from P {Qitem.sessionId}");
bool addOK = q.p.TryAdd(Qitem.sessionId, Qitem);
if (!addOK) {
L.LogError($"why couldnt we add session {Qitem.sessionId} to processing-queue?");
return false;
}
var startTime = DateTime.UtcNow;
Guid revisionId = Qitem.revisionId;
string[] filePaths = getFilePaths(Qitem.sessionId);
Stream[] streams = filePaths.Select(fileName => new FileStream(fileName, FileMode.Open)).ToArray();
MyMultiStream multiStream = new MyMultiStream(filePaths, streams, this.L, Qitem);
BimRevisionStatus brs = await ris.UploadRevision(revisionId, multiStream, startTime);
// (launchDeletes is my current hack/workaround,
// it is not part of the problem)
// await multiStream.launchDeletes();
Qitem.status = brs;
return true;
}
..

How to get Unicast, Dns and Gateway Address in UWP?

I'm trying to find Unicast, Dns and Gateway Address in windows IOT.
Normally I can access these values with NetworkInterface.GetAllNetworkInterfaces() method.
But in UWP, that method is missing.
Is there any alternative for getting these values?
You could try to PInvoke methods from Iphlpapi.dll. There are several methods that may contain the Unicast, Dns and Gateway info you're looking for, like GetInterfaceInfo(), GetAdaptersInfo(), GetAdaptersAdresses(), etc. Please see a complete list of available methods here: IP Helper Functions - MSDN. Eventually more than one method might be necessary.
As an example on how to do it, here's some sample code from PInvoke.Net retrieving interface names in my local computer, implemented as a standard UWP app:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
List<string> list = new List<string>();
IP_INTERFACE_INFO ips = Iphlpapi.GetInterfaceInfo();
list.Add(string.Format("Adapter count = {0}", ips.NumAdapters));
foreach (IP_ADAPTER_INDEX_MAP ip in ips.Adapter)
list.Add(string.Format("Index = {0}, Name = {1}", ip.Index, ip.Name));
listView1.ItemsSource = list;
}
}
// PInvoke
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IP_ADAPTER_INDEX_MAP
{
public int Index;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = PInvokes.MAX_ADAPTER_NAME)]
public String Name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct IP_INTERFACE_INFO
{
public int NumAdapters;
public IP_ADAPTER_INDEX_MAP[] Adapter;
public static IP_INTERFACE_INFO FromByteArray(Byte[] buffer)
{
unsafe
{
IP_INTERFACE_INFO rv = new IP_INTERFACE_INFO();
int iNumAdapters = 0;
Marshal.Copy(buffer, 0, new IntPtr(&iNumAdapters), 4);
IP_ADAPTER_INDEX_MAP[] adapters = new IP_ADAPTER_INDEX_MAP[iNumAdapters];
rv.NumAdapters = iNumAdapters;
rv.Adapter = new IP_ADAPTER_INDEX_MAP[iNumAdapters];
int offset = sizeof(int);
for (int i = 0; i < iNumAdapters; i++)
{
IP_ADAPTER_INDEX_MAP map = new IP_ADAPTER_INDEX_MAP();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(map));
Marshal.StructureToPtr(map, ptr, false);
Marshal.Copy(buffer, offset, ptr, Marshal.SizeOf(map));
//map = (IP_ADAPTER_INDEX_MAP)Marshal.PtrToStructure(ptr, typeof(IP_ADAPTER_INDEX_MAP));
map = Marshal.PtrToStructure<IP_ADAPTER_INDEX_MAP>(ptr);
Marshal.FreeHGlobal(ptr);
rv.Adapter[i] = map;
offset += Marshal.SizeOf(map);
}
return rv;
}
}
}
internal class PInvokes
{
public const int MAX_ADAPTER_NAME = 128;
public const int ERROR_INSUFFICIENT_BUFFER = 122;
public const int ERROR_SUCCESS = 0;
[DllImport("Iphlpapi.dll", CharSet = CharSet.Unicode)]
public static extern int GetInterfaceInfo(Byte[] PIfTableBuffer, ref int size);
[DllImport("Iphlpapi.dll", CharSet = CharSet.Unicode)]
public static extern int IpReleaseAddress(ref IP_ADAPTER_INDEX_MAP AdapterInfo);
}
public class Iphlpapi
{
public static IP_INTERFACE_INFO GetInterfaceInfo()
{
int size = 0;
int r = PInvokes.GetInterfaceInfo(null, ref size);
Byte[] buffer = new Byte[size];
r = PInvokes.GetInterfaceInfo(buffer, ref size);
if (r != PInvokes.ERROR_SUCCESS)
throw new Exception("GetInterfaceInfo returned an error.");
IP_INTERFACE_INFO info = IP_INTERFACE_INFO.FromByteArray(buffer);
return info;
}
}
Try this code Snippet I found here:
https://social.msdn.microsoft.com/Forums/en-US/27a8b7a8-8071-4bc1-bbd4-e7c1fc2bd8d7/windows-10-iot-core-how-do-you-create-a-tcp-server-and-client?forum=WindowsIoT
public static string GetDirectConnectionName()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp != null)
{
if(icp.NetworkAdapter.IanaInterfaceType == EthernetIanaType)
{
return icp.ProfileName;
}
}
return null;
}
public static string GetCurrentNetworkName()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp != null)
{
return icp.ProfileName;
}
var resourceLoader = ResourceLoader.GetForCurrentView();
var msg = resourceLoader.GetString("NoInternetConnection");
return msg;
}
public static string GetCurrentIpv4Address()
{
var icp = NetworkInformation.GetInternetConnectionProfile();
if (icp != null && icp.NetworkAdapter != null && icp.NetworkAdapter.NetworkAdapterId != null)
{
var name = icp.ProfileName;
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
if (hn.IPInformation != null &&
hn.IPInformation.NetworkAdapter != null &&
hn.IPInformation.NetworkAdapter.NetworkAdapterId != null &&
hn.IPInformation.NetworkAdapter.NetworkAdapterId == icp.NetworkAdapter.NetworkAdapterId &&
hn.Type == HostNameType.Ipv4)
{
return hn.CanonicalName;
}
}
}
var resourceLoader = ResourceLoader.GetForCurrentView();
var msg = resourceLoader.GetString("NoInternetConnection");
return msg;
}
As jstreet commented, Invoke is the solution. I tried with a Lumia 950, and GetAdaptersInfo() works, not as IcmpSendEcho().
With the following link you should do it:
http://www.pinvoke.net/default.aspx/iphlpapi/GetAdaptersInfo.html

Streaming video from an external service

I am working on a project (server side) where i need to stream data (videos, large files) to clients.
This worked perfect using ByteRangeStreamContent, as i was serving files from disk and could create a seekable stream (FileStream).
if (Request.Headers.Range != null)
{
try
{
HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
partialResponse.Content = new ByteRangeStreamContent(fs, Request.Headers.Range, mediaType);
return partialResponse;
}
catch (InvalidByteRangeException invalidByteRangeException)
{
return Request.CreateErrorResponse(invalidByteRangeException);
}
}
else
{
response.Content = new StreamContent(fs);
response.Content.Headers.ContentType = mediaType;
return response;
}
But, i moved the file provider from disk to an external service. The service allows me to get chunks of data (Range{0}-{1}).
Of course, it's not possible to download whole file in memory and then use a MemoryStream for ByteRangeStreamContent because of the obvious reasons (too many concurrent downloads will consume all the available memory at some point).
I found this article https://vikingerik.wordpress.com/2014/09/28/progressive-download-support-in-asp-net-web-api/ where the author says:
A change request I got for my library was to support reading only the
necessary data and sending that out rather than opening a stream for
the full data. I wasn’t sure what this would buy until the user
pointed out they are reading their resource data from a WCF stream
which does not support seeking and would need to read the whole stream
into a MemoryStream in order to allow the library to generate the
output.
That limitation still exists in this specific object but there is a
workaround. Instead of using a ByteRangeStreamContent, you could
instead use a ByteArrayContent object instead. Since the majority of
RANGE requests will be for a single start and end byte, you could pull
the range from the HttpRequestMessage, retrieve only the bytes you
need and send it back out as a byte stream. You’ll also need to add
the CONTENT-RANGE header and set the response code to 206
(PartialContent) but this could be a viable alternative (though I
haven’t tested it) for users who do not want or can’t easily get a
compliant stream object.
So, my question basically is: how can i do that ?
I finally managed to do it.
Here's how:
Custom implementation of a stream:
public class BufferedHTTPStream : Stream
{
private readonly Int64 cacheLength = 4000000;
private const Int32 noDataAvaiable = 0;
private MemoryStream stream = null;
private Int64 currentChunkNumber = -1;
private Int64? length;
private Boolean isDisposed = false;
private Func<long, long, Stream> _getStream;
private Func<long> _getContentLength;
public BufferedHTTPStream(Func<long, long, Stream> streamFunc, Func<long> lengthFunc)
{
_getStream = streamFunc;
_getContentLength = lengthFunc;
}
public override Boolean CanRead
{
get
{
EnsureNotDisposed();
return true;
}
}
public override Boolean CanWrite
{
get
{
EnsureNotDisposed();
return false;
}
}
public override Boolean CanSeek
{
get
{
EnsureNotDisposed();
return true;
}
}
public override Int64 Length
{
get
{
EnsureNotDisposed();
if (length == null)
{
length = _getContentLength();
}
return length.Value;
}
}
public override Int64 Position
{
get
{
EnsureNotDisposed();
Int64 streamPosition = (stream != null) ? stream.Position : 0;
Int64 position = (currentChunkNumber != -1) ? currentChunkNumber * cacheLength : 0;
return position + streamPosition;
}
set
{
EnsureNotDisposed();
EnsurePositiv(value, "Position");
Seek(value);
}
}
public override Int64 Seek(Int64 offset, SeekOrigin origin)
{
EnsureNotDisposed();
switch (origin)
{
case SeekOrigin.Begin:
break;
case SeekOrigin.Current:
offset = Position + offset;
break;
default:
offset = Length + offset;
break;
}
return Seek(offset);
}
private Int64 Seek(Int64 offset)
{
Int64 chunkNumber = offset / cacheLength;
if (currentChunkNumber != chunkNumber)
{
ReadChunk(chunkNumber);
currentChunkNumber = chunkNumber;
}
offset = offset - currentChunkNumber * cacheLength;
stream.Seek(offset, SeekOrigin.Begin);
return Position;
}
private void ReadNextChunk()
{
currentChunkNumber += 1;
ReadChunk(currentChunkNumber);
}
private void ReadChunk(Int64 chunkNumberToRead)
{
Int64 rangeStart = chunkNumberToRead * cacheLength;
if (rangeStart >= Length) { return; }
Int64 rangeEnd = rangeStart + cacheLength - 1;
if (rangeStart + cacheLength > Length)
{
rangeEnd = Length - 1;
}
if (stream != null) { stream.Close(); }
stream = new MemoryStream((int)cacheLength);
var responseStream = _getStream(rangeStart, rangeEnd);
responseStream.Position = 0;
responseStream.CopyTo(stream);
responseStream.Close();
stream.Position = 0;
}
public override void Close()
{
EnsureNotDisposed();
base.Close();
if (stream != null) { stream.Close(); }
isDisposed = true;
}
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
{
EnsureNotDisposed();
EnsureNotNull(buffer, "buffer");
EnsurePositiv(offset, "offset");
EnsurePositiv(count, "count");
if (buffer.Length - offset < count) { throw new ArgumentException("count"); }
if (stream == null) { ReadNextChunk(); }
if (Position >= Length) { return noDataAvaiable; }
if (Position + count > Length)
{
count = (Int32)(Length - Position);
}
Int32 bytesRead = stream.Read(buffer, offset, count);
Int32 totalBytesRead = bytesRead;
count -= bytesRead;
while (count > noDataAvaiable)
{
ReadNextChunk();
offset = offset + bytesRead;
bytesRead = stream.Read(buffer, offset, count);
count -= bytesRead;
totalBytesRead = totalBytesRead + bytesRead;
}
return totalBytesRead;
}
public override void SetLength(Int64 value)
{
EnsureNotDisposed();
throw new NotImplementedException();
}
public override void Write(Byte[] buffer, Int32 offset, Int32 count)
{
EnsureNotDisposed();
throw new NotImplementedException();
}
public override void Flush()
{
EnsureNotDisposed();
}
private void EnsureNotNull(Object obj, String name)
{
if (obj != null) { return; }
throw new ArgumentNullException(name);
}
private void EnsureNotDisposed()
{
if (!isDisposed) { return; }
throw new ObjectDisposedException("BufferedHTTPStream");
}
private void EnsurePositiv(Int32 value, String name)
{
if (value > -1) { return; }
throw new ArgumentOutOfRangeException(name);
}
private void EnsurePositiv(Int64 value, String name)
{
if (value > -1) { return; }
throw new ArgumentOutOfRangeException(name);
}
private void EnsureNegativ(Int64 value, String name)
{
if (value < 0) { return; }
throw new ArgumentOutOfRangeException(name);
}
}
Usage:
var fs = new BufferedHTTPStream((start, end) =>
{
// return stream from external service
}, () =>
{
// return stream length from external service
});
HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
partialResponse.Content = new ByteRangeStreamContent(fs, Request.Headers.Range, mediaType);
partialResponse.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = fileName
};
return partialResponse;

How to get list of projects in current Visual studio solution?

When we open Package Manager Console in any open solution, it shows all the projects of that solution. How it is loading all the projects of the same solution.
When I tried with below shown code it is fetching me projects of the first solution which I have opened.
private List<Project> GetProjects()
{
var dte = (DTE)Marshal.GetActiveObject(string.Format(CultureInfo.InvariantCulture, "VisualStudio.DTE.{0}.0", targetVsVersion));
var projects = dte.Solution.OfType<Project>().ToList();
return projects;
}
Here are a various set of functions that allow you to enumerate projects in a given solution. This is how you would use it with the current solution:
// get current solution
IVsSolution solution = (IVsSolution)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(IVsSolution));
foreach(Project project in GetProjects(solution))
{
....
}
....
public static IEnumerable<EnvDTE.Project> GetProjects(IVsSolution solution)
{
foreach (IVsHierarchy hier in GetProjectsInSolution(solution))
{
EnvDTE.Project project = GetDTEProject(hier);
if (project != null)
yield return project;
}
}
public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution)
{
return GetProjectsInSolution(solution, __VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION);
}
public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution, __VSENUMPROJFLAGS flags)
{
if (solution == null)
yield break;
IEnumHierarchies enumHierarchies;
Guid guid = Guid.Empty;
solution.GetProjectEnum((uint)flags, ref guid, out enumHierarchies);
if (enumHierarchies == null)
yield break;
IVsHierarchy[] hierarchy = new IVsHierarchy[1];
uint fetched;
while (enumHierarchies.Next(1, hierarchy, out fetched) == VSConstants.S_OK && fetched == 1)
{
if (hierarchy.Length > 0 && hierarchy[0] != null)
yield return hierarchy[0];
}
}
public static EnvDTE.Project GetDTEProject(IVsHierarchy hierarchy)
{
if (hierarchy == null)
throw new ArgumentNullException("hierarchy");
object obj;
hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out obj);
return obj as EnvDTE.Project;
}
There may be a nicer way but I had a quick go at this and found this to work (it assumes you have a way of knowing the solution name). According to this post, GetActiveObject does not guarantee the current instance of VS which is why you're getting results from another instance. Instead, you can use the GetDTE method shown there:
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
public static DTE GetDTE(int processId)
{
string progId = "!VisualStudio.DTE.10.0:" + processId.ToString();
object runningObject = null;
IBindCtx bindCtx = null;
IRunningObjectTable rot = null;
IEnumMoniker enumMonikers = null;
try
{
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
bindCtx.GetRunningObjectTable(out rot);
rot.EnumRunning(out enumMonikers);
IMoniker[] moniker = new IMoniker[1];
IntPtr numberFetched = IntPtr.Zero;
while (enumMonikers.Next(1, moniker, numberFetched) == 0)
{
IMoniker runningObjectMoniker = moniker[0];
string name = null;
try
{
if (runningObjectMoniker != null)
{
runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
}
}
catch (UnauthorizedAccessException)
{
// Do nothing, there is something in the ROT that we do not have access to.
}
if (!string.IsNullOrEmpty(name) && string.Equals(name, progId, StringComparison.Ordinal))
{
Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject));
break;
}
}
}
finally
{
if (enumMonikers != null)
{
Marshal.ReleaseComObject(enumMonikers);
}
if (rot != null)
{
Marshal.ReleaseComObject(rot);
}
if (bindCtx != null)
{
Marshal.ReleaseComObject(bindCtx);
}
}
return (DTE)runningObject;
}
If you know the solution name in advance, you can find it in the MainWindowTitle property of Process and pass the ProcessID to the method above.
var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);
Whilst the above code worked, I encountered a COM error which I fixed by using the MessageFilter class shown here.
From that post, this is what the MessageFilter class looks like
public class MessageFilter : IOleMessageFilter
{
// Class containing the IOleMessageFilter
// thread error-handling functions.
// Start the filter.
public static void Register()
{
IOleMessageFilter newFilter = new MessageFilter();
IOleMessageFilter oldFilter = null;
CoRegisterMessageFilter(newFilter, out oldFilter);
}
// Done with the filter, close it.
public static void Revoke()
{
IOleMessageFilter oldFilter = null;
CoRegisterMessageFilter(null, out oldFilter);
}
//
// IOleMessageFilter functions.
// Handle incoming thread requests.
int IOleMessageFilter.HandleInComingCall(int dwCallType,
System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
{
//Return the flag SERVERCALL_ISHANDLED.
return 0;
}
// Thread call was rejected, so try again.
int IOleMessageFilter.RetryRejectedCall(System.IntPtr
hTaskCallee, int dwTickCount, int dwRejectType)
{
if (dwRejectType == 2)
// flag = SERVERCALL_RETRYLATER.
{
// Retry the thread call immediately if return >=0 &
// <100.
return 99;
}
// Too busy; cancel call.
return -1;
}
int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee,
int dwTickCount, int dwPendingType)
{
//Return the flag PENDINGMSG_WAITDEFPROCESS.
return 2;
}
// Implement the IOleMessageFilter interface.
[DllImport("Ole32.dll")]
private static extern int
CoRegisterMessageFilter(IOleMessageFilter newFilter, out
IOleMessageFilter oldFilter);
}
[ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleMessageFilter
{
[PreserveSig]
int HandleInComingCall(
int dwCallType,
IntPtr hTaskCaller,
int dwTickCount,
IntPtr lpInterfaceInfo);
[PreserveSig]
int RetryRejectedCall(
IntPtr hTaskCallee,
int dwTickCount,
int dwRejectType);
[PreserveSig]
int MessagePending(
IntPtr hTaskCallee,
int dwTickCount,
int dwPendingType);
}
Then you can access the project names like this
var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);
MessageFilter.Register();
var projects = dte.Solution.OfType<Project>().ToList();
MessageFilter.Revoke();
foreach (var proj in projects)
{
Debug.WriteLine(proj.Name);
}
Marshal.ReleaseComObject(dte);
I believe you can use something like this:
var dte = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
if (dte != null)
{
var solution = dte.Solution;
if (solution != null)
{
// get your projects here
}
}

How to use DangerousAddRef when async native call

When calling a native function that takes IntPtr I understand how to do it with a Sync native function.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public static void Operation(SafeHandle handle)
{
var mustReleaseSafeHandle = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
handle.DangerousAddRef(ref mustReleaseSafeHandle);
if (mustReleaseSafeHandle) WinAPI.Operation(handle.DangerousGetHandle());
}
finally
{
if (mustReleaseSafeHandle) handle.DangerousRelease();
}
}
How do I ensure the DangerousRelease happens when the native function is async?
Here is a pseudo example of my async attempt to add more context as Hans Passant suggests.
public static unsafe void Operation(SafeHandle fileHandle, SafeHandle nativeMemoryHandle, int offset)
{
var mustReleaseSafeHandle = false;
var ioCompletionCallback = new IOCompletionCallback((code, bytes, overlap) =>
{
if (mustReleaseSafeHandle) nativeMemoryHandle.DangerousRelease();
Overlapped.Free(overlap);
});
var overlapped = new Overlapped();
var nativeOverlapped = overlapped.Pack(ioCompletionCallback, null);
nativeMemoryHandle.DangerousAddRef(ref mustReleaseSafeHandle);
if (mustReleaseSafeHandle)
{
var memoryStartLocation = IntPtr.Add(nativeMemoryHandle.DangerousGetHandle(), offset);
if (!WinAPI.Operation(fileHandle, memoryStartLocation, offset, nativeOverlapped))
{
nativeMemoryHandle.DangerousRelease();
Overlapped.Free(nativeOverlapped);
}
}
else
{
Overlapped.Free(nativeOverlapped);
}
}

Categories