1

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().

Misiu
  • 4,738
  • 21
  • 94
  • 198
  • You'll need to get a better diagnostic for the underlying SEH exception. Enable unmanaged debugging, ensure the Micrsoft Symbol server is enabled. Debug + Exceptions, tick the Thrown checkbox for Win32 Exceptions. Document the first chance exception notification you see in the Output window and the content of the Call Stack window. – Hans Passant Oct 27 '14 at 12:53
  • @HansPassant I added exception information that I got from VS – Misiu Oct 27 '14 at 13:03
  • You are lucky, such exceptions are normally pretty bad news. This is just a simple mistake, you are passing an invalid handle to WTSVirtualChannelClose(). Turned into an SEH because you are using the debugger, Microsoft is very worried about handle recycle attacks. The simplest explanation is you already having closed the handle. Wrapping the handle with a SafeHandle is generally highly advisable, less accidents that way. – Hans Passant Oct 27 '14 at 13:19
  • Best part is that I'm getting this exception only in VS. When running from Output directory everything works fine. I'm pretty sure I don't close that handle elsewhere. What do You mean by `SafeHandle`? Could You write bit more? – Misiu Oct 27 '14 at 13:44
  • Well, sure, it *looks* like it works when you run it without a debugger because you don't do any error checking on the return value of WTSVirtualChannelClose(). Common mistake and what worries Microsoft so much. It is nevertheless very unhealthy and you should *not* ignore it. Your snippets don't give me any way to repro the problem. Google ".net create safe handle class" to learn more about SafeHandle. – Hans Passant Oct 27 '14 at 13:59
  • @HansPassant I'll create simple project with just this functionality. Worst part is that You need terminal server access, because first part (DLL) is required to load into mstsc.exe and second part (exe) must be put on terminal server. Any chances You could look at that if I'll provide project? – Misiu Oct 27 '14 at 14:04
  • No problem :) I'll try to debug it better. I'll edit my question and add basic code that I use for message send and receive on server and of course close method. COuld You look at it. I'm starting all that RDP Connections stuff and native calls. I will be grateful for fast look. – Misiu Oct 27 '14 at 14:10
  • @HansPassant I added basic code in my question, could You please take a look? – Misiu Oct 27 '14 at 14:18
  • Well, my assumed reason for failure is certainly not disproved. You blindly call WTSVirtualChannelClose() without checking if it is connected. Obviously you want to only do this if isConnected is *true*. – Hans Passant Oct 27 '14 at 15:18
  • @HansPassant I am connected, because I'm able to send message from client and on server side I'm receiving messages. Same on server, I can send messages and on client I'm receiving them. I get this error when I try to disconnect on server (call Server.Disconnect()). – Misiu Oct 27 '14 at 17:55

0 Answers0