Pausing / Resuming Screen recording with Windows Graphics Capture API - c#

I am building a screen recording app in C# using Windows Graphics Capture API. I am using this script. I can select monitor and can record it to mp4 file. I am trying to add Pause/Resume functionality.
Here is code of main Window that initiates Recording
try
{
newFile = GetTempFile();
using (var stream = new FileStream(newFile, FileMode.CreateNew).AsRandomAccessStream())
using (_encoder = new Encoder(_device, item))
{
await _encoder.EncodeAsync(
stream,
width, height, bitrate,
frameRate);
}
}
catch (Exception ex)
{
//
}
And here is the main function from Encoder class, which is used above
private async Task EncodeInternalAsync(IRandomAccessStream stream, uint width, uint height, uint bitrateInBps, uint frameRate)
{
if (!_isRecording)
{
_isRecording = true;
_frameGenerator = new CaptureFrameWait(
_device,
_captureItem,
_captureItem.Size);
using (_frameGenerator)
{
var encodingProfile = new MediaEncodingProfile();
encodingProfile.Container.Subtype = "MPEG4";
encodingProfile.Video.Subtype = "H264";
encodingProfile.Video.Width = width;
encodingProfile.Video.Height = height;
encodingProfile.Video.Bitrate = bitrateInBps;
encodingProfile.Video.FrameRate.Numerator = frameRate;
encodingProfile.Video.FrameRate.Denominator = 1;
encodingProfile.Video.PixelAspectRatio.Numerator = 1;
encodingProfile.Video.PixelAspectRatio.Denominator = 1;
var transcode = await _transcoder.PrepareMediaStreamSourceTranscodeAsync(_mediaStreamSource, stream, encodingProfile);
await transcode.TranscodeAsync();
}
}
}
And finally this is the initializer function in CaptureFrameWait class
private void InitializeCapture(SizeInt32 size)
{
_framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
_device,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
1,
size);
_framePool.FrameArrived += OnFrameArrived;
_session = _framePool.CreateCaptureSession(_item);
_session.IsBorderRequired = false;
_session.StartCapture();
}
How can we modify this to pause the recording? I have tried to dispose the _framepool and _session objects on Pause and initialize them again on Resume in CaptureFrameWait class, like shown below. It works fine, but sometimes TranscodeAsync function terminates during pause and ends recording. How can we avoid that?
bool _paused = false;
public void PauseSession(bool status)
{
if (status) {
_paused = true;
_framePool?.Dispose();
_session?.Dispose();
}
else {
InitializeCapture(_size);
_paused = false;
}
}

One solution is to get a deferral. Doc says:
The MediaStreamSource will then wait for you to supply the
MediaStreamSample until you mark the deferral as complete.
So for example, add two private members to the Encoder class and two methods:
private MediaStreamSourceSampleRequestedEventArgs _args;
private MediaStreamSourceSampleRequestDeferral _def;
public bool IsPaused { get; private set; }
public void Pause()
{
IsPaused = true;
}
public void Resume()
{
IsPaused = false;
// complete the request we saved earlier
OnMediaStreamSourceSampleRequested(_mediaStreamSource, _args);
}
And modify OnMediaStreamSourceSampleRequested methods like this (where I've put comments):
private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
if (_isRecording && !_closed)
{
// if paused get a deferral and save the current arguments.
// OnMediaStreamSourceSampleRequested will not be called again until we complete the deferral
if (IsPaused)
{
_def = args.Request.GetDeferral();
_args = args;
return;
}
try
{
using (var frame = _frameGenerator.WaitForNewFrame())
{
if (frame == null)
{
args.Request.Sample = null;
DisposeInternal();
return;
}
var timeStamp = frame.SystemRelativeTime;
var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
args.Request.Sample = sample;
// when called again (manually by us) complete the work
// and reset members
if (_def != null)
{
_def.Complete();
_def = null;
_args = null;
}
}
}
catch (Exception e)
{
...
}
}
else
{
...
}
}
Another solution is to simply freeze the frames timestamp, so add these members:
private TimeSpan _pausedTimestamp;
public bool IsPaused { get; private set; }
public void Pause()
{
IsPaused = true;
}
public void Resume()
{
IsPaused = false;
}
And modify OnMediaStreamSourceSampleRequested methods like this (where I've put comments):
private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
if (_isRecording && !_closed)
{
try
{
using (var frame = _frameGenerator.WaitForNewFrame())
{
if (frame == null)
{
args.Request.Sample = null;
DisposeInternal();
return;
}
// if paused, "freeze" the timestamp
TimeSpan timeStamp;
if (IsPaused)
{
timeStamp = _pausedTimestamp;
}
else
{
timeStamp = frame.SystemRelativeTime;
_pausedTimestamp = timeStamp;
}
var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, timeStamp);
args.Request.Sample = sample;
}
}
catch (Exception e)
{
...
}
}
else
{
...
}
}

Related

How to set Timer in winform for executing 10+ command?

I have around 10 commands of Tx,Rx with time interval of 100 millisecond for each. So Basically a TxRx pair should be executed within every 100 milliseconds. unless the stop button is clicked. Where am I getting Wrong?
Form1 btn_Start
set the time Interval to request and receive the message
private void btn_Start_Click(object sender, EventArgs e)
{
Start();
}
private void Start()
{
btn_Start.Enabled = false;
btn_Stop.Enabled = true;
int Interval = 100;
if (CANTimer.Enabled == false)
{
// CAN Timer confinguration
CANTimer.Enabled = true;
CANTimer.Interval = Convert.ToInt32(fCANInterval); //Interval;
CANTimer.Tick += new System.EventHandler(this.CANTimer_Tick);
CANTimer.Start();
}
else if (CANTimer.Enabled && Program_config_num == 0)
{
CANTimer.Stop();
CANTimer.Interval = Convert.ToInt32(fCANInterval); //Interval;
CANTimer.Enabled = true;
CANTimer.Start();
}
}
Form1 CANTimer_Tick
Execute the command to Send and Receive Messages in every 100 milliseconds, till the btn_Stop is clicked
private void CANTimer_Tick(object sender, EventArgs e)
{
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThread);
int unit_index = 1;
if(IsConnected == false)
{
IsConnected = CANClass.connect();
lbl_Status.Text = "Connected";
}
else
{
Req1_Send = CAN.Send_Req1(unit_index, CANClass); //Sends request for Msg1 to MCU through CANClass
if ((Req1_Send == true) && (Msg1_Received == false))
{
ThreadPool.QueueUserWorkItem(ReadMsg1); //Reads the message and display
}
Req2_Send = CAN.Send_Req2(unit_index, CANClass); //Sends request for Msg2 to MCU through CANClass
if ((Req2_Send == true) && (Msg2_Received == false))
{
ThreadPool.QueueUserWorkItem(ReadMsg2); //Reads the message and display
}
Req3_Send = CAN.Send_Req3(unit_index, CANClass); //Sends request for Msg3 to MCU through CANClass
if ((Req3_Send == true) && (Msg3_Received == false))
{
ThreadPool.QueueUserWorkItem(ReadMsg3); //Reads the message and display
}
............... till the Msg10
}
}
btn_Stop
Stop the connection
private void btn_Stop_Click(object sender, EventArgs e)
{
Stop();
}
private void Stop()
{
btn_Start.Enabled = true;
btn_Stop.Enabled = false;
CanPacket.CanStop();
IsConnected = false;
}
```
The thing with hardware controllers is that (usually) you don't know exactly how long they'll take to fill up the RX Buffer so that you can get the packet response from the TX-RX pair. If you're doing it on a timer tick you're between the terrible risk of buffer underrun (possibly hanging the controller) or wasting time by patting the wait time with margins.
Often it's better to loop in a Task where you perform the IO synchronously (hopefully this involves actual handshaking) for your "10+ Commands" and then get the "100 ms spacing" after all the TX-RX pairs have completed and before the next iteration of the loop.
private async void checkBoxRun_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxRun.Checked)
{
richTextBox.Clear();
await Task.Run(() => loop());
labelIndicator.BackColor = Color.LightGray;
}
}
private async Task loop()
{
do
{
await readCanBusSingle();
if (!checkBoxRun.Checked) return;
// Here's where the spacing of 100 ms or whatever happens.
// I made it longer for test.
await Task.Delay(TimeSpan.FromSeconds(1));
}
while (checkBoxRun.Checked);
}
private async Task readCanBusSingle()
{
string text;
if (_busController.TryConnect())
{
BeginInvoke((MethodInvoker)delegate
{
labelIndicator.BackColor = Color.LightGreen;
richTextBox.SelectionFont = new Font(richTextBox.Font, FontStyle.Bold);
richTextBox.AppendText($"{Environment.NewLine}{DateTime.Now}: REQUEST{Environment.NewLine}");
richTextBox.SelectionFont = new Font(richTextBox.Font, FontStyle.Regular);
});
foreach (RequestID requestID in Enum.GetValues(typeof(RequestID)))
{
var packet = new Packet { RequestID = requestID };
// HARWARE CONTROLLER: Push some bytes into TXQueue
_busController.SendReq(unitIndex: 1, packet: packet);
// HARWARE CONTROLLER: Wait for RXQueue bytes to be present
// Often this means a spin (not an await) until the full
// reapi=onse is available in the MCU.
await Task.Run(() =>
{
while(_busController.Busy)
{
Task.Delay(10).Wait();
}
});
if (packet.MockResponse is uint[] bytes)
{
text = string.Join(" ", bytes.Select(_ => _.ToString("X4")));
}
else
{
text = $"{packet.MockResponse}";
}
BeginInvoke((MethodInvoker)delegate
{
switch (packet.RequestID)
{
case RequestID.REQ1:
richTextBox.SelectionColor = Color.Navy;
break;
case RequestID.REQ2:
richTextBox.SelectionColor = Color.DarkGreen;
break;
case RequestID.REQ3:
richTextBox.SelectionColor = Color.Maroon;
break;
default: throw new InvalidOperationException();
}
richTextBox.AppendText($"{text}{Environment.NewLine}");
richTextBox.ScrollToCaret();
});
}
}
else
{
BeginInvoke((MethodInvoker)delegate
{
labelIndicator.BackColor = Color.LightSalmon;
richTextBox.SelectionColor = Color.Red;
richTextBox.AppendText($"{Environment.NewLine}Connection lost{Environment.NewLine}");
richTextBox.ScrollToCaret();
});
}
}
MOCK
Where:
public enum RequestID { REQ1, REQ2, REQ3 }
public class McuController
{
internal bool TryConnect()
{
switch (Rando.Next(10))
{
case 0:
// The mock controller fails every now and then
return false;
default:
return true;
}
}
internal async void SendReq(int unitIndex, Packet packet)
{
Busy = true;
await packet.SetMockResponse(TimeSpan.FromMilliseconds(Rando.Next(10, 51)));
Busy = false;
}
public static Random Rando { get; } = new Random();
public bool Busy { get; private set; }
}
public class Packet
{
public RequestID RequestID { get; set; }
public int UnitID { get; set; }
// MOCK
public object MockResponse { get; set; }
public async Task SetMockResponse(TimeSpan mockDelay)
{
await Task.Delay(mockDelay);
switch (RequestID)
{
case RequestID.REQ1:
// String
MockResponse =
$"Unit {UnitID}";
break;
case RequestID.REQ2:
// ByteArray
MockResponse =
Enumerable.Range(0, 8)
.Select(_ => (uint)Rando.Next(45000, 55000))
.ToArray();
break;
case RequestID.REQ3:
MockResponse =
mockDelay;
break;
default: throw new InvalidOperationException();
}
}
}

Using NAudio to record fixed length wav, it crashes with "System.AccessViolationException" after the first file

I'm using NAudio to record a fixed length audio file, but it crashes after the first file at the TimerElapsed method.
I'm using a Timer that stops and starts a new recording after the Interval has elapsed.
The filename is changed after each new recording.
This is the error I get.
System.AccessViolationException
HResult=0x80004003
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Source=<Cannot evaluate the exception source>
StackTrace:
<Cannot evaluate the exception stack trace>
The class code is here :
namespace AudioService
{
public class AudioRecorder
{
public string Location
{
get => location;
set
{
location = value;
Filename = $"{Location}/test001.wav";
}
}
public string Filename { get; set; }
public double Interval { get; set; } = 10000;
public int DeviceNumber { get; set; } = 0;
private WaveInEvent waveSource = null;
private WaveFileWriter waveFile = null;
public bool LoopRecord { get; set; } = true;
public bool Recording { get; set; } = false;
private System.Timers.Timer timer;
private string location;
public AudioRecorder()
{
Location = Environment.GetEnvironmentVariable("userprofile");
Filename = $"{Location}/test001.wav";
if (LoopRecord) setTimer();
}
private void setTimer()
{
timer = new System.Timers.Timer(Interval);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
/// ****************
/// CRASHES HERE
/// ****************
waveSource?.StopRecording();
Filename = GetNewFileName();
StartRecording(Filename);
}
public void StartRecording()
{
if (timer != null)
timer.Enabled = true;
StartRecording(Filename);
}
public async void StartRecording(string filename)
{
if (waveSource == null)
{
waveSource = new WaveInEvent
{
DeviceNumber = DeviceNumber,
WaveFormat = new WaveFormat(44100, 1)
};
waveSource.DataAvailable += new EventHandler<WaveInEventArgs>(waveSource_DataAvailable);
waveSource.RecordingStopped += new EventHandler<StoppedEventArgs>(waveSource_RecordingStopped);
}
waveFile = await Task.Run(() => new WaveFileWriter(filename, waveSource.WaveFormat));
waveSource.StartRecording();
Recording = true;
}
private void waveSource_RecordingStopped(object sender, StoppedEventArgs e)
{
if (waveSource != null)
{
waveSource.Dispose();
waveSource = null;
}
if (waveFile != null)
{
waveFile.Dispose();
waveFile = null;
}
Recording = false;
}
private void waveSource_DataAvailable(object sender, WaveInEventArgs e)
{
if (waveFile != null)
{
waveFile.Write(e.Buffer, 0, e.BytesRecorded);
waveFile.Flush();
}
}
public void StopRecording()
{
waveSource?.StopRecording();
timer.Enabled = false;
}
public string GetNewFileName()
{
var tempLocation = $"{Location}/{DateTime.Now:yyMMdd}/";
bool folderExists = Directory.Exists(tempLocation);
if (!folderExists)
Directory.CreateDirectory(tempLocation);
string date = DateTime.Now.ToString("HH.mm.ss");
return $"{tempLocation}{date}.wav";
}
}
}
Thanks for helping
That exception basically means that you are crashing the 3rd party unmanaged code in NAudio, probably by trying to access an object that it is busy using or that it has already closed.
Your timer is going to fire every time your interval elapses, but you don't have any code to make sure that the previous recording is complete before starting a new one. In other words, StartRecording will get called over and over one the timer is started. If you want StartRecording to fire ONCE, then timer.AutoReset should be false.
A good way to troubleshoot these kind of issues is to replace all the calls to NAudio objects with console.writelines describing what is happening, and then run your program and observe the output.

Design solution for working with multiple instantiations at the same time

I don't know if my title is correct. But here's what I want to know.
I have a Download class that returns certain events and has a couple of methods. Each instance of Download class can download a single file. And all those events and methods are related to the file being downloaded.
As it's a multi file downloader, multiple instantiations are required when more than a single file needs to be downloaded.
Each download has a download id, but that is not supplied to the Download class, to keep it independent from the other classes.
Now getting all the info from each instance of the file download and being able to control a single download, is the problem. How do I know which download is which?
Any solutions? Or design patterns you could recommend? I've hit a roadblock.
Download class:
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading;
namespace Mackerel_Download_Manager
{
public class Download
{
public event EventHandler<DownloadStatusChangedEventArgs> ResumablityChanged;
public event EventHandler<DownloadProgressChangedEventArgs> ProgressChanged;
public event EventHandler Completed;
public bool stop = true; // by default stop is true
public bool paused = false;
SemaphoreSlim pauseLock = new SemaphoreSlim(1);
string filename;
public void DownloadFile(Uri DownloadLink, string Path)
{
filename = System.IO.Path.GetFileName(Path);
stop = false; // always set this bool to false, everytime this method is called
var fileInfo = new FileInfo(Path);
long existingLength = 0;
if (fileInfo.Exists)
existingLength = fileInfo.Length;
var request = (HttpWebRequest)HttpWebRequest.Create(DownloadLink);
request.Proxy = null;
request.AddRange(existingLength);
try
{
using (var response = (HttpWebResponse)request.GetResponse())
{
long fileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded
bool downloadResumable; // need it for not sending any progress
if ((int)response.StatusCode == 206) //same as: response.StatusCode == HttpStatusCode.PartialContent
{
//Console.WriteLine("Resumable");
downloadResumable = true;
}
else // sometimes a server that supports partial content will lose its ability to send partial content(weird behavior) and thus the download will lose its resumability
{
if (existingLength > 0)
{
if (ResumeUnsupportedWarning() == false) // warn and ask for confirmation to continue if the half downloaded file is unresumable
{
return;
}
}
existingLength = 0;
downloadResumable = false;
}
OnResumabilityChanged(new DownloadStatusChangedEventArgs(downloadResumable));
using (var saveFileStream = fileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write))
using (var stream = response.GetResponseStream())
{
byte[] downBuffer = new byte[4096];
int byteSize = 0;
long totalReceived = byteSize + existingLength;
var sw = Stopwatch.StartNew();
while (!stop && (byteSize = stream.Read(downBuffer, 0, downBuffer.Length)) > 0)
{
saveFileStream.Write(downBuffer, 0, byteSize);
totalReceived += byteSize;
float currentSpeed = totalReceived / (float)sw.Elapsed.TotalSeconds;
OnProgressChanged(new DownloadProgressChangedEventArgs(totalReceived, fileSize, (long)currentSpeed));
pauseLock.Wait();
pauseLock.Release();
}
sw.Stop();
}
}
if (!stop) OnCompleted(EventArgs.Empty);
}
catch (WebException e)
{
System.Windows.MessageBox.Show(e.Message, filename);
}
}
public void pause()
{
if (!paused)
{
paused = true;
// Note this cannot block for more than a moment
// since the download thread doesn't keep the lock held
pauseLock.Wait();
}
}
public void unpause()
{
if (paused)
{
paused = false;
pauseLock.Release();
}
}
public void StopDownload()
{
stop = true;
this.unpause(); // stop waiting on lock if needed
}
public bool ResumeUnsupportedWarning()
{
var messageBoxResult = System.Windows.MessageBox.Show("When trying to resume the download , Mackerel got a response from the server that it doesn't support resuming the download. It's possible that it's a temporary error of the server, and you will be able to resume the file at a later time, but at this time Mackerel can download this file from the beginning.\n\nDo you want to download this file from the beginning?", filename, System.Windows.MessageBoxButton.YesNo);
if (messageBoxResult == System.Windows.MessageBoxResult.Yes)
{
return true;
}
else
{
return false;
}
}
protected virtual void OnResumabilityChanged(DownloadStatusChangedEventArgs e)
{
var handler = ResumablityChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnProgressChanged(DownloadProgressChangedEventArgs e)
{
var handler = ProgressChanged;
if (handler != null)
{
handler(this, e);
}
}
protected virtual void OnCompleted(EventArgs e)
{
var handler = Completed;
if (handler != null)
{
handler(this, e);
}
}
}
public class DownloadStatusChangedEventArgs : EventArgs
{
public DownloadStatusChangedEventArgs(bool canResume)
{
ResumeSupported = canResume;
}
public bool ResumeSupported { get; private set; }
}
public class DownloadProgressChangedEventArgs : EventArgs
{
public DownloadProgressChangedEventArgs(long totalReceived, long fileSize, long currentSpeed)
{
BytesReceived = totalReceived;
TotalBytesToReceive = fileSize;
CurrentSpeed = currentSpeed;
}
public long BytesReceived { get; private set; }
public long TotalBytesToReceive { get; private set; }
public float ProgressPercentage
{
get
{
return ((float)BytesReceived / (float)TotalBytesToReceive) * 100;
}
}
public float CurrentSpeed { get; private set; } // in bytes
public TimeSpan TimeLeft
{
get
{
var bytesRemainingtoBeReceived = TotalBytesToReceive - BytesReceived;
return TimeSpan.FromSeconds(bytesRemainingtoBeReceived / CurrentSpeed);
}
}
}
}
Download class is instantiated inside a Mackerel class, that starts the download for the given downloads.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
namespace Mackerel_Download_Manager
{
public static class Mackerel
{
//Main Menu functions
public static void ResumeDownload(string[] DownloadIDs)
{
foreach (var DownloadID in DownloadIDs)
{
var itemToResume = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
if (itemToResume.Running == false)
{
itemToResume.Running = true;
var download = new Download();
download.DownloadFile(itemToResume.DownloadLink, itemToResume.SaveTo);
var window = new Dialogs.DownloadProgress(itemToResume);
window.Show();
double progress = 0;
itemToResume.Status = string.Format("{0:0.00}%", progress);
Downloads.DownloadEntries.CollectionChanged += delegate
{
if (!itemToResume.Running) window.Close();
};
}
}
}
public static void StopDownload(string[] DownloadIDs)
{
foreach (var DownloadID in DownloadIDs)
{
var itemToStop = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
if (itemToStop.Running == true)
itemToStop.Running = false;
}
}
public static void StopAllDownloads()
{
foreach (var itemToStop in Downloads.DownloadEntries.Where(download => download.Running == true))
itemToStop.Running = false;
}
public static void RemoveDownload(string[] DownloadIDs) // this method is able to delete multiple downloads
{
foreach (var DownloadID in DownloadIDs)
{
// delete from the download list
var selectedDownload = Downloads.DownloadEntries.Where(download => download.DownloadID == DownloadID).FirstOrDefault();
var selectedDownloadIndex = Downloads.DownloadEntries.IndexOf(selectedDownload);
Downloads.DownloadEntries.RemoveAt(selectedDownloadIndex);
//delete from the harddrive
if (File.Exists(selectedDownload.SaveTo))
File.Delete(selectedDownload.SaveTo);
}
Downloads.Serialize(); // save current state of object
}
public static void RemoveCompletedDownloads() // this method just removes all completed downloads from Mackerel's download list (it doesn't delete them from the hard drive)
{
foreach (var itemToRemove in Downloads.DownloadEntries.Where(download => download.Status == "Complete").ToList())
{
Downloads.DownloadEntries.Remove(itemToRemove);
}
}
// Context Menu
public static void OpenDownloadProperties(string DownloadID) // Open "Download Properties" for the given download ID
{
var DownloadProperties = new Dialogs.Context_Menu.DownloadProperties(DownloadID);
DownloadProperties.Owner = Application.Current.MainWindow; // so that this dialog centers to its parent window, as its window is set to WindowStartupLocation="CenterOwner"
DownloadProperties.ShowDialog();
}
}
}
Full source code is here: https://github.com/Expenzor/mackerel-download-manager
Sounds like your making the download itself an object but are using "downloader" as the name instead. I would maybe suggest an array of downloaded objects or similar. The downloader class can have a method which creates a new object - perhaps a download object. Using an array or linked list etc will give you the opportunity to address the object and call them independently - know which is which.
Posting your code would help as well.

Reconnect vpn. Windows service

I've been trying to implement a windows service that would keep vpn connection alive. I've found that it is possible to achieve using DotRas library by subscribing to RasConnectionWatcher.Disconnected event:
public class SampleService {
public SampleService() {
this.shutdownEvent = new ManualResetEvent(false);
this.connectionWatcher = new RasConnectionWatcher();
this.connectionWatcher.Disconnected += onVpnDisconnected;
}
// redial
void onVpnDisconnected(Object sender, RasConnectionEventArgs e) {
this.DialUp();
}
void DialUp() {
// connection setup is omitted
// keep the handle of the connection
this.connectionWatcher.Handle = dialer.Dial();
}
public void Start() {
this.thread = new Thread(WorkerThreadFunc);
this.thread.IsBackground = true;
this.thread.Start();
}
public void Stop() {
this.shutdownEvent.Set();
if(!this.thread.Join(3000)) this.thread.Abort();
}
private void WorkerThreadFunc() {
this.DialUp();
while(!this.shutdownEvent.WaitOne(0)) Thread.Sleep(1000);
}
}
When I start the service vpn connection opens without any problem, but when I manually interrupt the connection it seems that Disconnected event doesn't fire up.
solution 1
Found similar question/answer here:
http://social.msdn.microsoft.com/Forums/en-US/56ab2d0d-2425-4d76-81fc-04a1e1136141/ras-connection-application-and-service?forum=netfxnetcom.
solution 2
Got an answer from Jeff Winn yesterday:
https://dotras.codeplex.com/discussions/547038
public class VpnKeeperService : IService {
private ManualResetEvent shutdownEvent;
private RasConnectionWatcher connWatcher;
private Thread thread;
public VpnKeeperService() {
this.shutdownEvent = new ManualResetEvent(false);
this.connWatcher = new RasConnectionWatcher();
this.connWatcher.EnableRaisingEvents = true;
this.connWatcher.Disconnected += (s, args) => { this.DialUp(); };
}
Boolean DialUp() {
try {
using(var phoneBook = new RasPhoneBook()) {
var name = VpnConfig.GetConfig().ConnectionName;
var user = VpnConfig.GetConfig().Username;
var pass = VpnConfig.GetConfig().Password;
var pbPath = VpnConfig.GetConfig().PhoneBookPath;
phoneBook.Open(pbPath);
var entry = phoneBook.Entries.FirstOrDefault(e => e.Name.Equals(name));
if(entry != null) {
using(var dialer = new RasDialer()) {
dialer.EntryName = name;
dialer.Credentials = new NetworkCredential(user, pass);
dialer.PhoneBookPath = pbPath;
dialer.Dial();
}
}
else throw new ArgumentException(
message: "entry wasn't found: " + name,
paramName: "entry"
);
}
return true;
}
catch {
// log the exception
return false;
}
}
public void Start() {
this.thread = new Thread(WorkerThreadFunc);
this.thread.Name = "vpn keeper";
this.thread.IsBackground = true;
this.thread.Start();
}
public void Stop() {
this.shutdownEvent.Set();
if(!this.thread.Join(3000)) {
this.thread.Abort();
}
}
private void WorkerThreadFunc() {
if(this.DialUp()) {
while(!this.shutdownEvent.WaitOne(0)) {
Thread.Sleep(1000);
}
}
}
}
Hope it helps someone.

Accessing WebBrowsers From Threadpool

I'm trying to access information on a web browser from another thread. When trying to access the browser.DocumentTitle, I get this error:
The name DocumentTitle does not exist in the current context
I can successfully navigate to webpages inside the DoWork or ProcessWebPage methods but I cannot access the GetTitle function without crashing. I have been working on this part alone for days and simply cannot figure it out.
Here is the problem code:
BROWSER CODE
class BrowserInterface : Form
{
WebBrowser browser;
Thread thread;
State state;
public State State { get { return state; } }
public BrowserInterface()
{
Initialize();
}
void Initialize()
{
browser = new WebBrowser();
state = State.Null;
state = State.Initializing;
thread = new Thread(StartThread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
while (state == State.Initializing) Thread.Sleep(20);
}
void StartThread()
{
browser = new WebBrowser();
browser.Dock = DockStyle.Fill;
browser.Name = "webBrowser";
browser.ScrollBarsEnabled = false;
browser.TabIndex = 0;
browser.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(this.Web_Completed);
Form form = new Form();
form.Controls.Add(browser);
form.Name = "Browser";
state = State.Null;
Application.Run(form);
}
public void Navigate(string url)
{
state = State.Navigating;
if (browser.IsDisposed)
Initialize();
browser.Navigate(url);
}
public string GetTitle()
{
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => GetTitle()));
}
return browser.DocumentTitle;
}
private void Web_Completed(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var br = sender as WebBrowser;
if (br.Url == e.Url)
state = State.Completed;
}
}
enum State
{
Initializing,
Null,
Navigating,
Completed
}
OTHER THREAD
class Controller
{
public int ThreadsAllowed;
private ManualResetEvent[] resetEvent;
private BrowserInterface[] browser;
static Thread mainThread;
bool run;
bool exit;
public Controller(int threadsAllowed)
{
ThreadsAllowed = threadsAllowed;
resetEvent = new ManualResetEvent[ThreadsAllowed];
browser = new BrowserInterface[ThreadsAllowed];
for (int i = 0; i < ThreadsAllowed; i++)
{
resetEvent[i] = new ManualResetEvent(true);
browser[i] = new BrowserInterface();
}
ThreadPool.SetMaxThreads(ThreadsAllowed, ThreadsAllowed);
mainThread = new Thread(RunThread);
mainThread.Start();
run = false;
exit = false;
}
public void Run()
{
run = true;
}
void RunThread()
{
while (true)
{
while (!run) Thread.Sleep(20);
while (mode == ScoutMode.Off) Thread.Sleep(100);
//wait for the last set to complete
WaitHandle.WaitAll(resetEvent);
if (exit)
break;
for (int i = 0; i < ThreadsAllowed; i++)
ThreadPool.QueueUserWorkItem(DoWork, i);
}
}
void DoWork(object o)
{
int i = (int)o;
if(browser[i].state == State.null)
{
…
… navigation code that works …
…
return;
}
else if(browser[i].state == State.Completed)
ProcessWebPage(i);
}
void ProcessWebPage(int i)
{
string title;
try
{
title = browser[i].GetTitle();
}
catch { return; }
}
}
What hurts my eye is your GetTitle function. When using MethodInvoker, you're dealing with methods of void type, that is, you cannot get return value from the function. That's why you need a delegate which will return you the value.
Also, you have to have else statement, so to not try to return the value when invoking is in fact required.
class BrowserInterface : Form
{
/* ... */
private delegate string StringDelegate();
public string GetTitle()
{
/*
if (InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => GetTitle()));
}
return browser.DocumentTitle;
*/
if (InvokeRequired)
{
object result = Invoke(new StringDelegate(GetTitle));
return (string)result;
}
else
return browser.DocumentTitle;
}
/* ... */
}
At first, use browsers invoke instead of forms one. And the main problem that after invokation you will return to code and try to access browser.DocumentTitle as background thread. To avoid this, add else construction.
public string GetTitle()
{
if (this.browser.InvokeRequired)
{
this.browser.Invoke(new MethodInvoker(() => GetTitle()));
}
else
{
return browser.DocumentTitle;
}
}

Categories

Resources