I'm building application that will communicate with server using Remote Desktop Service API.
I'm building my application using code found here: https://code.google.com/p/tstunnels/ and here: http://www.codeproject.com/Articles/16374/How-to-Write-a-Terminal-Services-Add-in-in-Pure-C
I build client library that is loaded when I do Remote Desktop Connection and I'm able to send and receive messages.
On server side I get SEHException when I'm trying to close my application.
I use this methods using DLLImport:
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern IntPtr WTSVirtualChannelOpen(IntPtr server, int sessionId, [MarshalAs(UnmanagedType.LPStr)] string virtualName);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelQuery(IntPtr channelHandle, WtsVirtualClass Class, out IntPtr data, out int bytesReturned);
[DllImport("Wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelWrite(IntPtr channelHandle, byte[] data, int length, out int bytesWritten);
[DllImport("Wtsapi32.dll")]
public static extern bool WTSVirtualChannelClose(IntPtr channelHandle);
public static Stream WTSVirtualChannelQuery_WTSVirtualFileHandle(IntPtr channelHandle)
{
int len;
IntPtr buffer;
var b = WTSVirtualChannelQuery(channelHandle, WtsVirtualClass.WTSVirtualFileHandle, out buffer, out len);
if (!b) throw new Win32Exception();
var fileHandle = new SafeFileHandle(Marshal.ReadIntPtr(buffer), true);
WTSFreeMemory(buffer);
return new FileStream(fileHandle, FileAccess.ReadWrite, 0x1000, true);
}
Then inside my application I'm doing this:
mHandle = Native.WTSVirtualChannelOpen(IntPtr.Zero, -1, ChannelName);
if (mHandle == IntPtr.Zero)
{
Log("RDP Virtual channel Open Failed: " + new Win32Exception().Message);
return;
}
try
{
var stream = Native.WTSVirtualChannelQuery_WTSVirtualFileHandle(mHandle);
reader = new BinaryReader(new BufferedStream(stream));
}
catch (Win32Exception ex)
{
Log("RDP Virtual channel Query Failed: " + ex.Message);
return;
}
After that I'm able to write and read thru Virtual Channel, but when I try to close using below code I get error.
if (reader != null) reader.Close();
var ret = Native.WTSVirtualChannelClose(mHandle);
Here is error:
System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception. at Server.Native.WTSVirtualChannelClose(IntPtr channelHandle) at Server.Server.Disconnect() in i:\Tomka\TS\v2\TSAddin\Server\Server.cs:line 73
I tried getting last Win32Error using Marshal.GetLastWin32Error()
but it is returning 1008
.
Looking in msdn I found that error description:
ERROR_NO_TOKEN 1008 (0x3F0) An attempt was made to reference a token that does not exist.
I found article on MSDN but code is in C++ so that didn't help, anyway here is the link: http://msdn.microsoft.com/en-us/library/aa383852(v=vs.85).aspx
EDIT 1 I did as @Hans wrote. Here is first exception I got:
First-chance exception at 0x00000000771105B7 (ntdll.dll) in Server.exe: 0xC0000008: An invalid handle was specified.
EDIT 2
Here are classes I'm using on server:
class Native
{
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern IntPtr WTSVirtualChannelOpen(IntPtr server, int sessionId, [MarshalAs(UnmanagedType.LPStr)] string virtualName);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelQuery(IntPtr channelHandle, WtsVirtualClass Class, out IntPtr data, out int bytesReturned);
[DllImport("Wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelWrite(IntPtr channelHandle, byte[] data, int length, out int bytesWritten);
[DllImport("Wtsapi32.dll", SetLastError = true)]
public static extern bool WTSVirtualChannelRead(IntPtr channelHandle, int timeOut, IntPtr data, int length, out int bytesRead);
[DllImport("Wtsapi32.dll")]
public static extern bool WTSVirtualChannelClose(IntPtr channelHandle);
public static Stream WTSVirtualChannelQuery_WTSVirtualFileHandle(IntPtr channelHandle)
{
int len;
IntPtr buffer;
var b = WTSVirtualChannelQuery(channelHandle, WtsVirtualClass.WTSVirtualFileHandle, out buffer, out len);
if (!b) throw new Win32Exception();
var fileHandle = new SafeFileHandle(Marshal.ReadIntPtr(buffer), true);
WTSFreeMemory(buffer);
return new FileStream(fileHandle, FileAccess.ReadWrite, 0x1000, true);
}
public enum WtsVirtualClass
{
WTSVirtualClient = 0,
WTSVirtualFileHandle = 1
}
}
and Server.cs:
public class Server
{
private IntPtr mHandle;
private BinaryReader reader;
public bool IsConnected { get; private set; }
private bool SeenHello;
public const string ChannelName = "TEST";
private delegate void Action();
public void Connect()
{
mHandle = Native.WTSVirtualChannelOpen(IntPtr.Zero, -1, ChannelName);
if (mHandle == IntPtr.Zero)
{
Log("RDP Virtual channel Open Failed: " + new Win32Exception().Message);
return;
}
try
{
var stream = Native.WTSVirtualChannelQuery_WTSVirtualFileHandle(mHandle);
reader = new BinaryReader(new BufferedStream(stream));
}
catch (Win32Exception ex)
{
Log("RDP Virtual channel Query Failed: " + ex.Message);
return;
}
IsConnected = true;
Log("Connected");
Action process = Process;
process.BeginInvoke(process.EndInvoke, null);
Action hello = () =>
{
while (!SeenHello && IsConnected)
{
WriteMessage("HELLO");
Log("Sending HELLO");
Thread.Sleep(200);
}
};
hello.BeginInvoke(hello.EndInvoke, null);
}
public void Disconnect()
{
IsConnected = false;
if (reader != null) reader.Close();
var ret = Native.WTSVirtualChannelClose(mHandle);
}
private void Process()
{
while (IsConnected)
{
try
{
var len = reader.ReadInt32();
Log(len.ToString());
byte[] buff = reader.ReadBytes(len);
Log(Encoding.UTF8.GetString(buff));
MessageReceived(Encoding.UTF8.GetString(buff));
}
catch (OperationCanceledException ex)
{
Log(ex);
return;
}
catch (Exception ex)
{
Log(ex);
}
}
}
public bool WriteMessage(string msg)
{
byte[] data = Encoding.UTF8.GetBytes(msg);
int written;
var ret = Native.WTSVirtualChannelWrite(mHandle, data, data.Length, out written);
if (ret) return true;
var ex = new Win32Exception();
if (!SeenHello && ex.NativeErrorCode == 1 /* Incorrect Function */) return false;
Log("RDP Virtual channel Write Failed: " + ex.Message);
return false;
}
public void MessageReceived(string msg)
{
Log(msg);
if (msg.ToUpper() == "HELLO:RESPONSE")
{
if (!SeenHello)
{
SeenHello = true;
OnConnected(msg);
}
}
OnMessage(msg);
}
public EventHandler<MessageEventArgs> Connected;
protected void OnConnected(string msg)
{
if (Connected != null)
Connected(this, new MessageEventArgs(msg));
}
public EventHandler<MessageEventArgs> Message;
protected void OnMessage(string msg)
{
if (Message != null)
Message(this, new MessageEventArgs(msg));
}
public void Log(object message)
{
OnMessageLogged(message.ToString());
}
public EventHandler<MessageEventArgs> MessageLogged;
protected void OnMessageLogged(string message)
{
if (MessageLogged != null)
MessageLogged(this, new MessageEventArgs(message));
}
}
and usage looks like this:
In main form constructor I'm creating server object, in Load I'm calling Server.Connect() and in FormClosed Server.Disconnect().