Edge.js- -Access to DLL file doesn't work - c#

I want to access the OpenHardwareMonitorLib.dll file with node.js using the edge.js module and monitor the computer's CPU and GPU temperatures. I created a Node.js file in Visual Studio Code and added the DLL file to the references.
var edge = require('edge-js');
var add7 = edge.func({
source: function() {/*
using System;
using System.Data;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenHardwareMonitor.Hardware;
public class Startup
{
public class UpdateVisitor : IVisitor
{
public void VisitComputer(IComputer computer)
{
computer.Traverse(this);
}
public void VisitHardware(IHardware hardware)
{
hardware.Update();
foreach (IHardware subHardware in hardware.SubHardware) subHardware.Accept(this);
}
public void VisitSensor(ISensor sensor) { }
public void VisitParameter(IParameter parameter) { }
}
public async Task Invoke()
{
while (true)
{
await Task.Run(() => GetSystemInfo());
}
}
static async void GetSystemInfo()
{
UpdateVisitor updateVisitor = new UpdateVisitor();
Computer computer = new Computer();
computer.Open();
computer.CPUEnabled = true;
computer.Accept(updateVisitor);
for (int i = 0; i < computer.Hardware.Length; i++)
{
if (computer.Hardware[i].HardwareType == HardwareType.CPU)
{
for (int j = 0; j < computer.Hardware[i].Sensors.Length; j++)
{
if (computer.Hardware[i].Sensors[j].SensorType == SensorType.Temperature)
Console.WriteLine(computer.Hardware[i].Sensors[j].Name + ":" + computer.Hardware[i].Sensors[j].Value.ToString() + "\r");
}
}
}
computer.Close();
}
}
*/},
references: [ 'System.Data.dll','OpenHardwareMonitorLib.dll']
});
I'm pretty sure the C# code works. I created an empty C# console application in Visual Studio and added the DLL file to the references. When I ran this C# code, I reached CPU temperatures without any problems. But in Visual Studio when I run it in my Nodejs project using edge-js, I get the following output;
'app.js' exited with code -1 (0xffffffff)
When I run it in Visual Studio Code, I can't get any output. I don't fully understand why it occurs. I would be glad if you help.
Visual Studio Code Output;

Related

C# in Unity: Calling Promise Style Async method without Blocking the Main Thread

I have a code snippet which is acting like a Grpc Client in Unity. The code style is designed for a console application, which can be called in Main method, blocking it and receiving data all the time. Now, I want to use it in Unity and obviously I want my app to run in Unity at the same time. Also, my end goal is to have something that works like a Udp Client. You call it one time, and all the time will receive data for you, without blocking any part of the host application.
The most important part of this design is that, if there is any event, I will get update, if there is no new event, accordingly, I am not receiving any data. And it happens all in ObserveEvents(channel).Wait();. The problem is Wait();. Which is all the time, keep the main thread, working thread, listening to updates. In Play mode Unity does not respond anymore!
I can bypass it and say, I don't need such a design, I can receive events every other second or every some frames. By doing that, I have my Unity application, but I am losing so many frame rates, regardless of the fact that my data are NOT flowing smoothly to my host application in Unity.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using UnityEngine;
namespace Scripts
{
public class GrpcChannel
{
public void GrpcServer(string ip, int port)
{
var channel = new Channel(ip, port, ChannelCredentials.Insecure);
ObserveEvents(channel).Wait();
channel.ShutdownAsync().Wait();
}
private async Task ObserveEvents(Channel channel)
{
Debug.Log("Observing events...");
var eventService = new EventService.EventServiceClient(channel);
var request = new RegisterOnEvent();
using (var call = eventService.OnEvent(request))
{
var responseStream = call.ResponseStream;
while (await responseStream.MoveNext())
{
//var receivedEvent = responseStream.Current;
// on change of any data, this method will be fired
GetJointPosition(channel, "Flower_id_22134");
}
}
}
private void GetJointPosition(Channel channel, string flowerName)
{
var client = new JointPositionService.JointPositionServiceClient(channel);
var request = new GetJointPositionRequest
{
FlowerName = flowerName
};
var response = client.GetJointPositionAsync(request);
SavePositions(response.ResponseAsync.Result.Positions);
}
private void SavePositions(IEnumerable<JointPosition> positions)
{
var jointPositions = positions.ToList();
for (var i = 0; i < Instance.Ref.AxesValues.Length; i++)
{
var axeValueDegree = jointPositions[i].Value * 180 / Math.PI;
Instance.Ref.AxesValues[i] = (float)axeValueDegree;
}
}
}
}
And I am calling it like:
var grpcChannel = new GrpcChannel();
grpcChannel.GrpcServer("192.168.123.16", 30201);
in Update() method. Unfortunately, it does not work in Start() method. And yes, apparently, every frame it needs to create a new Channel, otherwise it will not work.
And the current implementation is like this, which is calling it every 7 frames without using that special wait for events design:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using TMPro;
using UnityEngine;
namespace Assets.Scripts
{
public class AxisValuesService : MonoBehaviour
{
public TextMeshProUGUI[] AxesValuesTexts;
[HideInInspector] public Dictionary<uint, float> AxisValues = new Dictionary<uint, float>();
[HideInInspector] private int counter = 0;
private void Update()
{
counter++;
if (counter == 7)
{
try
{
var channel = new Channel("192.168.123.16", 30201, ChannelCredentials.Insecure);
GetJointPosition(channel, "Flower_id_22134");
//ObserveEvents(channel).Wait();
channel.ShutdownAsync().Wait();
}
catch (RpcException e)
{
Debug.LogError("Connection Error: " + e);
}
counter = 0;
}
}
private void GetJointPosition(Channel channel, string robotName)
{
// Request Axis Values
var client = new JointPositionService.JointPositionServiceClient(channel);
var request = new GetJointPositionRequest { RobotName = robotName };
var response = client.GetJointPositionAsync(request);
// Fill Dictionary
foreach (var i in response.ResponseAsync.Result.Positions)
{
double value = toDegree((double)i.Value);
AxisValues[i.Index] = (float)Math.Round(value, 2);
}
try
{
AxesValuesTexts[0].text = AxisValues[1].ToString();
AxesValuesTexts[1].text = AxisValues[2].ToString();
AxesValuesTexts[2].text = AxisValues[3].ToString();
AxesValuesTexts[3].text = AxisValues[4].ToString();
AxesValuesTexts[4].text = AxisValues[5].ToString();
AxesValuesTexts[5].text = AxisValues[6].ToString();
}
catch (Exception e)
{
Debug.Log("Dictionary problem.");
}
}
private double toDegree(double rad)
{
return (float)(180 / Math.PI) * rad;
}
}
}
My Question is that, first, if this method is completely async, why does it still block the application in Unity, also how I can redesign it to achieve something like Udp or Tcp style?
Thanks to #Ilian, #zambari, #Jan Tattermusch and of course my colleague Rainer, the creator of the Grpc Api connection interface for us. I changed my structure to some degree that now works very performant both on Sender and Receiver computer. Below, I am going to explain what I have changed.
I have two classes, both are attached to one gameObject in Unity hierarchy: GrpcSetup.cs and AxeValuesConnectionInterface.cs. I commented on scripts, I hope it helps.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using FlowerShop.Grpc.Service.Joint;
using FlowerShop.Grpc.Service.Joint.Event;
using FlowerShop.Grpc.Service.System;
using UnityEngine;
namespace Scripts
{
public class GrpcSetup : MonoBehaviour
{
private int _loopController = 1;
private Channel _grpcChannel;
private EventService.EventServiceClient _eventService;
private RegisterOnEvent _request;
private IAsyncStreamReader<Any> _responseStream;
private Any _receivedEvent;
private JointPositionChangedEvent _positionChangedEvent;
// this method will be called only one time in start method in another class
// .. I am initializing a channel for Grpc and some required properties for my ObserveEvents method
public void GrpcChannelInitialization()
{
_grpcChannel = new Channel("192.168.100.16", 50001, ChannelCredentials.Insecure);
_eventService = new EventService.EventServiceClient(_grpcChannel);
_request = new RegisterOnEvent();
}
// this method will be called in Update in another class
public async void GrpcUpdateMethod()
{
try
{
// to get the initial axesVales only one time
if (_loopController == 1)
{
await GetJointPositionOnDemand(_grpcChannel, "Flower_li35443");
_loopController = 0;
}
// receiving Events only when they are available
await ObserveEvents();
}
catch (RpcException e)
{
Debug.LogError("Connection Error: " + e);
}
}
// this method will be called every frame, the good thing about it is that, I will only receive events,
// .. when there are some available.
private async Task ObserveEvents()
{
using (var call = _eventService.OnEvent(_request))
{
_responseStream = call.ResponseStream;
if (await _responseStream.MoveNext())
{
Debug.Log("New Event is available.");
_receivedEvent = call.ResponseStream.Current;
if (_receivedEvent.TypeUrl.EndsWith(JointPositionChangedEvent.Descriptor.FullName))
{
_positionChangedEvent = _receivedEvent.Unpack<JointPositionChangedEvent>();
_positionChangedEvent.Positions.ToList().ForEach(i =>
Instance.Ref.AxesValues[i.Index - 1] = (float) Math.Round(i.Value * Mathf.Rad2Deg, 2));
}
}
}
}
// if I want to get Joint values whenever I like, regardless of ObserveEvents method architecture
// .. at this moment, I am calling it, one time in Update method
private async Task GetJointPositionOnDemand(Channel channel, string flowerName)
{
var client = new JointPositionService.JointPositionServiceClient(channel);
var requestLocal = new GetJointPositionRequest {FlowerName= flowerName};
var response = await client.GetJointPositionAsync(requestLocal);
foreach (var i in response.Positions)
{
var value = i.Value * Mathf.Rad2Deg;
Instance.Ref.AxesValues[i.Index - 1] = (float) Math.Round(value, 2);
}
}
// this will be called in Unity reserved method: OnApplicationQuit
// .. so we are trying to get rid of our opened channel
public async Task ChannelShutDown()
{
await _grpcChannel.ShutdownAsync();
}
}
}
and my AxeValuesConnectionInterface.cs goes like this:
using System.Threading.Tasks;
using UnityEngine;
namespace Scripts
{
[RequireComponent(typeof(GrpcSetup))]
public class AxeValuesConnectionInterface : MonoBehaviour
{
private GrpcSetup _grpcSetup;
private float _timeElapsed;
private int _loopController = 2;
private int _loopController1 = 1;
private int _loopController2 = 1;
private int _counterLoop;
private void Start()
{
_grpcSetup = GetComponent<GrpcSetup>();
}
private void Update()
{
GrpcInitialization();
GrpcUpdateMethods();
}
private void OnApplicationQuit()
{
Task.Run(_grpcSetup.ChannelShutDown);
}
private void GrpcInitialization()
{
if (_loopController2 != 1) return;
if (Instance.Ref.ConnectionInterface != Instance.Ci.Grpc) return;
_grpcSetup.GrpcChannelInitialization();
_loopController2 = 0;
}
private void GrpcUpdateMethods()
{
if (Instance.Ref.ConnectionInterface != Instance.Ci.Grpc || !Instance.Ref.FlowerIsPresent) return;
Task.Run(() => _grpcSetup.GrpcUpdateMethod());
}
}
}

How to connect to instrument through USB using c#

I am trying to use the Ivi.Visa.Interop .dll to communicate to a Voltech PM1000+ power meter using USB. I'm relatively new to C# and do not know really where to start. I am using Visual Studio 2015 Community. I have already talked to a different instrument using GPIB and here is the code for that:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Ivi.Visa.Interop;
namespace commOverIP
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitiateIOBtn_Click(object sender, EventArgs e)
{
///testing out excel
InitiateIOBtn.Text = "Initializing";
try
{
// resource manager and message-based session manager
Ivi.Visa.Interop.ResourceManager mngr = new Ivi.Visa.Interop.ResourceManager();
// GPIB address
string srcAddress = "GPIB::27::INSTR"; // GPIB address of data acquisition
//setting up communication
Ivi.Visa.Interop.FormattedIO488 instrument = new Ivi.Visa.Interop.FormattedIO488();
Ivi.Visa.Interop.IMessage Imsg = (mngr.Open(srcAddress, Ivi.Visa.Interop.AccessMode.NO_LOCK, 1000, "") as IMessage);
instrument.IO = Imsg;
instrument.IO.Clear();//clear io buffer
instrument.WriteString("*RST", true);//send RST? command to instrument
instrument.WriteString("*IDN?", true);//send IDN? command to instrument
returnOfCommand.Text = instrument.ReadString();//read IDN? result
//close communication
instrument.IO.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(instrument);
System.Runtime.InteropServices.Marshal.ReleaseComObject(mngr);
InitiateIOBtn.Text = "Initialize I/O";
//*/
}
catch(Exception exp)
{
MessageBox.Show(exp.Message);
}
InitiateIOBtn.Text = "Initialize I/O";
}
}
}
This works fine but USB seems to be a different beast. The only real lead I found was in the .dll with the:
IUsb.Init(string, Ivi.Visa.Interop.AccessMode, int, string)
I tried implementing this but I don't really know where to start.
If anyone could give me an example of how to query a "*IDN?" command that would be great. Or, even if there is a better way of doing this than through the Ivi.Visa.Interop dll.
Thanks in advance
Restart your device once. Clearing the IO also helps. Afterwards following code should work fine:
string resourceString= "USB0::xxx::xxx::xxx::0::INSTR";
ResourceManager manager = new ResourceManager();
FormattedIO488 connection = new FormattedIO488();
connection.IO = (IMessage)manager.Open(resourceString, AccessMode.NO_LOCK, 0, "");
connection.IO.Clear();
connection.WriteString("*IDN?", true);
string result = connection.ReadString();
I do what you are asking all of the time and I completely understand how frustrating it can be. I remember doing Google searches to come up with this code. The code actually came from some Keysight documentation when I bought the Agilent 82357B USB/GPIB Controller.
This can be adapted for any GPIB instrument, the only difference being the strings that you send to the instrument. These can be obtained by getting the programming manual for the instrument in which you're interested.
I installed the Keysight (formerly Agilent) I/O Library Suites that is used with the Agilent 82357B. One thing that is not obvious is that you should disable the 'Auto Discovery' option, as this feature will occasionally put your device in Local mode.
using System.Threading;
using System.Runtime.InteropServices;
// Add reference for VISA-COM 5.9 Type Library
using Ivi.Visa.Interop;
namespace USBCommunications
{
class Program
{
static void Main(string[] args)
{
Gpib.Write(address: 5, command: "*IDN?");
bool success = Gpib.Read(address: 5, valueRead: out string valueRead);
System.Console.WriteLine($"The ID is {valueRead}");
System.Console.ReadLine();
}
}
public class Gpib
{
static ResourceManager resourceManager;
static FormattedIO488 ioObject;
public static bool Write(byte address, string command)
{
resourceManager = new ResourceManager();
ioObject = new FormattedIO488();
string addr = $"GPIB::{address.ToString()}::INSTR";
try
{
ioObject.IO = (IMessage)resourceManager.Open(addr, AccessMode.NO_LOCK, 0, "");
Thread.Sleep(20);
ioObject.WriteString(data: command, flushAndEND: true);
return true;
}
catch
{
return false;
}
finally
{
try { ioObject.IO.Close(); }
catch { }
try { Marshal.ReleaseComObject(ioObject); }
catch { }
try { Marshal.ReleaseComObject(resourceManager); }
catch { }
}
}
public static bool Read(byte address, out string valueRead)
{
resourceManager = new ResourceManager();
ioObject = new FormattedIO488();
string addr = $"GPIB::{address.ToString()}::INSTR";
try
{
ioObject.IO = (IMessage)resourceManager.Open(addr, AccessMode.NO_LOCK, 0, "");
Thread.Sleep(20);
valueRead = ioObject.ReadString();
return true;
}
catch
{
valueRead = "";
return false;
}
finally
{
try { ioObject.IO.Close(); }
catch { }
try { Marshal.ReleaseComObject(ioObject); }
catch { }
try { Marshal.ReleaseComObject(resourceManager); }
catch { }
}
}
}
}
Happy programming!!

When Connecting R with RdotNet It says Set Environment Variable 'PATH'

When I Run the Warning
RDotNet.NativeLibrary.UnmanagedDll.SetDllDirectory(string) is obsolete Set environment variable 'PATH' instead.
I don't know how to do that. how to set variable path please help quickly please.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using RDotNet;
namespace WindowsFormsApplication7
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
string dlldir = #"C:\Program Files\R\R-3.1.1\bin\x64";
REngine.SetDllDirectory(dlldir);
REngine.CreateInstance("RDotNet");
}
private void button1_Click(object sender, EventArgs e)
{
REngine engine = REngine.GetInstanceFromID("RDotNet");
try
{
// import csv file
engine.Evaluate("dataset<-read.table(file.choose(), header=TRUE, sep = ',')");
// retrieve the data frame
DataFrame dataset = engine.Evaluate("dataset").AsDataFrame();
for (int i = 0; i < dataset.ColumnCount; ++i)
{
dataGridView1.ColumnCount++;
dataGridView1.Columns[i].Name = dataset.ColumnNames[i];
}
for (int i = 0; i < dataset.RowCount; ++i)
{
dataGridView1.RowCount++;
dataGridView1.Rows[i].HeaderCell.Value = dataset.RowNames[i];
for (int k = 0; k < dataset.ColumnCount; ++k)
{
dataGridView1[k, i].Value = dataset[i,k];
}
}
}
catch
{
MessageBox.Show(#"Equation error.");
}
}
}
}
If you want to know how can you set or edit environment variables in windows, you can use this link:
Set Environment Variable
PATH includes several addresses, and maybe you should add your dlldir address to this variable.
You are using an outdated version of R.NET; see the online documentation for instruction and sample code on how to install and use the current API (version 1.5.15 or above)

Unable to set images to open with my program, Why not?

Ok so i wrote my own photo viewer to open jpg,gif,png files on my computer. However for some reason whenever i set the file association in windows, using the normal properties menu and then selecting my exe it fails to open the program when i click a picture.
I tried debugging by adding message boxes, but sofar it gives no output.
I see the current window loose focus, but nothing appears.
And task manager does not show my process ever opening.
I think windows might be preventing my application from running in some way, iv attempted to disable my antivirus and running it thinking it was that, but no dice.
Program.cs
namespace PictureViewer
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (args == null || args.Length == 0)
{
//Console.WriteLine("args is null"); // Check for null array
Application.Run(new Form1());
}
else
{
for (int i = 0; i < args.Length; i++) // Loop through array
{
string argument = args[i];
Application.Run(new Form1(argument));
}
}
}
}
}
Inside Form1 is 2 constructors, 1 with and one without a pram of string then i just do a
Picturebox1.Image = Image.fromFile(pram);
Im quite sure this issent a c# thing, its more of a windows being dumb thing.
Windows 8.1 for refrence.
edit: heres form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PictureViewer
{
public partial class Form1 : Form
{
string curentdirectory = "";
List<string> imageindir;
int curentindex;
public Form1()
{
InitializeComponent();
imageindir = new List<string>();
}
public Form1(string initfile)
{
InitializeComponent();
curentdirectory = initfile.Substring(0, initfile.LastIndexOf("/"));
imageindir = new List<string>();
try
{
this.Text = initfile;
img.Image = Image.FromFile(initfile);
}
catch(Exception ex)
{
MessageBox.Show("ERR");
}
}
private void btnleft_Click(object sender, EventArgs e)
{
try
{
if (--curentindex < 0)
{
curentindex = imageindir.Count - 1;
}
img.Image = Image.FromFile(imageindir[curentindex]);
}
catch (Exception ex)
{
MessageBox.Show("ERR");
}
}
private void btnright_Click(object sender, EventArgs e)
{
try
{
if (++curentindex > imageindir.Count - 1)
{
curentindex = 0;
}
img.Image = Image.FromFile(imageindir[curentindex]);
}
catch (Exception ex)
{
MessageBox.Show("ERR");
}
}
private void getDirFromFileName(string dir)
{
DirectoryInfo di;
di = new DirectoryInfo(curentdirectory);
var directories = di.GetFiles("*", SearchOption.TopDirectoryOnly);
foreach (FileInfo d in directories)
{
if(dir == d.Name)
{
curentindex = imageindir.Count;
}
if(validExtension(d.Name))
{
imageindir.Add(d.Name);
}
}
}
private bool validExtension(string val)
{
val = val.ToLower();
if (val.Contains(".jpg") || val.Contains(".jpeg") || val.Contains(".gif") || val.Contains(".png") || val.Contains(".bmp"))
return true;
return false;
}
}
}
There is an error in curentdirectory = initfile.Substring(0, initfile.LastIndexOf("/")); line. the / should be \\. May be the problem is here.
I have tested your code and it works fine. i have uploaded test project here
Editional Details:
Project has created in Visual Studio 2005.

C# how to stop the program after a certain time?

I have a big problem, but probably it's only big for me :). "terminal.Bind(client);" this line causes my program to hang if IP is bad. I want to stop this program after 5s working because if IP is wrong after 10s all program is hang.. :(
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Rebex.TerminalEmulation;
using Rebex.Security;
using Rebex.Net;
namespace Routers_info_v._1
{
class Program
{
static void Main(string[] args)
{
Telnet client = new Telnet("192.168.1.1");
VirtualTerminal terminal = new VirtualTerminal(80, 25);
terminal.Bind(client);
terminal.SendToServer("pass\r");
terminal.SendToServer("sys ver\r");
TerminalState state;
do
{
state = terminal.Process(2000);
} while (state == TerminalState.DataReceived);
terminal.Save("terminal.txt", TerminalCaptureFormat.Text, TerminalCaptureOptions.DoNotHideCursor);
terminal.Unbind();
terminal.Dispose();
}
}
}
Try to wrap the call in a try catch (assuming some exception is thrown):
try
{
terminal.Bind(client);
}
catch(Exception ex)
{
return;
}
You could kick off the Bind in a thread, and start a timer, if the thread takes X seconds too long to complete, you could kill the thread, or your application, whichever you choose.
You can use Task.Wait. Here is little simulation for an operation which will take 10 sec and you are waiting it for 5 sec to finish :)
using System;
using System.Linq;
using System.Data.Linq;
using System.Data;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
class VirtualTerminal
{
public VirtualTerminal(int a, int b) { }
public bool Bind() { System.Threading.Thread.Sleep(10000); return true; }
}
class Program
{
static void Main(string[] args)
{
VirtualTerminal terminal = new VirtualTerminal(80, 25);
Func<bool> func = () => terminal.Bind() ;
Task<bool> task = new Task<bool>(func);
task.Start();
if (task.Wait(5*1000))
{
// you got connected
}
else
{
//failed to connect
}
Console.ReadLine();
}
}
}
I would suggest to put the network stuff into a second thread, which then may be aborted by the main thread.
class Program {
static void Main(string[] args) {
Thread thread = new Thread(threadFunc);
thread.Start();
Stopwatch watch = new Stopwatch();
watch.Start();
while (watch.ElapsedMilliseconds < 5000 && thread.IsAlive)
;
if (!thread.IsAlive) {
thread.Abort();
Console.WriteLine("Unable to connect");
}
}
private static void threadFunc() {
Telnet client = new Telnet("192.168.1.1");
VirtualTerminal terminal = new VirtualTerminal(80, 25);
terminal.Bind(client);
// ...
terminal.Dispose();
}
}

Categories

Resources