Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Sometimes you need to write an extension to Terminal Services (if you have used it). So why don't you write it in your favorite programming language (for me, it's C#)?

Background

Why am I playing with TS? How it started? Well ...

In our firm, almost everyone (about 120+) work on TS. We have a third party ERP system installed on our servers and there is an export function using Excel automation. Now is the question: how to use this functionality without spending money on two licenses for Excel for every user (that's right, in this case, all users should have license on both the server and the workstation)? The answer is: write an application which can provide the same (or at least what they are using) automation as Excel, and can build Excel files and send them to the client. The first version I wrote is based on exporting source-code from the ERP's producent (good to have friends there), and it was sending files via SMTP to the client, and it was written in ATL/WTL. But I knew the export source-code so I knew which object from Excel they (ERP's producent) were using.

After a few months, I found a way to send files over RDP. The application worked for more than a year. ... and then they changed the export code. After reading a great article about Building COM Servers in .NET, I decided to move this application to .NET. I found a way to get to know what functions/methods and objects they were using (IExpando from .NET and COM's IDispatchEx) and how to implement them. I thought, why not move the whole project to .NET, not just the server side. Well, I tried.

So back to the topic ... this article will not be about how to write Excel-like automation stuff. But how to write server/client side add-ins in C#.

What Should You Know?

What exactly will this sample application do?

The server side:

The client side:

You'll find it useful when:

Using the Code

Server Side

Well, it will be easy... all we have to do is import a few functions from wtsapi32.dll and use them in our application.

//The WTSVirtualChannelOpen function opens a handle
//to the server end of a specified virtual channel
HANDLE WTSVirtualChannelOpen(HANDLE hServer, DWORD SessionId,
                             LPSTR pVirtualName);

//The WTSVirtualChannelWrite function writes data
//to the server end of a virtual channel
BOOL WTSVirtualChannelWrite(HANDLE hChannelHandle, PCHAR Buffer,
                            ULONG Length, PULONG pBytesWritten);

//The WTSVirtualChannelClose function closes an open virtual channel handle.
BOOL WTSVirtualChannelClose(HANDLE hChannelHandle);

In C#, they look like this:

using System;
using System.Runtime.InteropServices;

class WtsApi32
{
    [DllImport("Wtsapi32.dll")]
    public static extern IntPtr WTSVirtualChannelOpen(IntPtr server,
        int sessionId, [MarshalAs(UnmanagedType.LPStr)] string virtualName);

    [DllImport("Wtsapi32.dll", SetLastError = true)]
    public static extern bool WTSVirtualChannelWrite(IntPtr channelHandle,
           byte[] buffer, int length, ref int bytesWritten);

    [DllImport("Wtsapi32.dll", SetLastError = true)]
    public static extern bool WTSVirtualChannelRead(IntPtr channelHandle,
           byte[] buffer, int length, ref int bytesReaded);

    [DllImport("Wtsapi32.dll")]
    public static extern bool WTSVirtualChannelClose(IntPtr channelHandle);
}

Now sending the data:

byte[] data = PseudoClass.GetSomeData();
//remember that VirtualName should have 7 or less signs

IntPtr mHandle = WtsApi32.WTSVirtualChannelOpen(IntPtr.Zero, -1, "TSCS");
int written = 0;
bool ret = WtsApi32.WTSVirtualChannelWrite(mHandle, data,
           data.Length, ref written);
if (!ret || written == gziped.Length)
    MessageBox.Show("Sent!", "OK", MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
else
    MessageBox.Show("Bumm! Somethings gone wrong!", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
ret = WtsApi32.WTSVirtualChannelClose(mHandle);

Off we go ... data sent (well, only when we correct the setup client-side stuff).

Client Side

What sits on the client side? Well, the client side is a DLL which exports a function in the first place in the export table.

BOOL VCAPITYPE VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints);

We will also need a definition for this:

typedef struct tagCHANNEL_ENTRY_POINTS {
  DWORD cbSize;
  DWORD protocolVersion;
  PVIRTUALCHANNELINIT pVirtualChannelInit;
  PVIRTUALCHANNELOPEN pVirtualChannelOpen;
  PVIRTUALCHANNELCLOSE pVirtualChannelClose;
  PVIRTUALCHANNELWRITE pVirtualChannelWrite;
} CHANNEL_ENTRY_POINTS, *PCHANNEL_ENTRY_POINTS;

typedef UINT VCAPITYPE VIRTUALCHANNELINIT(
  LPVOID FAR * ppInitHandle,
  PCHANNEL_DEF pChannel,
  INT channelCount,
  ULONG versionRequested,
  PCHANNEL_INIT_EVENT_FN pChannelInitEventProc
);

UINT VCAPITYPE VirtualChannelOpen(
  LPVOID pInitHandle,
  LPDWORD pOpenHandle,
  PCHAR pChannelName,
  PCHANNEL_OPEN_EVENT_FN pChannelOpenEventProc
);

UINT VCAPITYPE VirtualChannelClose(
  DWORD openHandle
);

UINT VCAPITYPE VirtualChannelWrite(
  DWORD openHandle,
  LPVOID pData,
  ULONG dataLength,
  LPVOID pUserData
);

OMG!!! pointer to functions.. it doesn't look good. Well, maybe, but delegates from .NET are actually functions pointers, so ... we will try to wrap all this stuff into some class.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Text;
using System.Threading;

namespace Win32
{
    namespace WtsApi32
    {
        public delegate ChannelReturnCodes
            VirtualChannelInitDelegate(ref IntPtr initHandle,
            ChannelDef[] channels, int channelCount, int versionRequested,
            [MarshalAs(UnmanagedType.FunctionPtr)]
            ChannelInitEventDelegate channelInitEventProc);
        public delegate ChannelReturnCodes
            VirtualChannelOpenDelegate(IntPtr initHandle, ref int openHandle,
            [MarshalAs(UnmanagedType.LPStr)] string channelName,
            [MarshalAs(UnmanagedType.FunctionPtr)]
            ChannelOpenEventDelegate channelOpenEventProc);
        public delegate ChannelReturnCodes
            VirtualChannelCloseDelegate(int openHandle);
        public delegate ChannelReturnCodes
            VirtualChannelWriteDelegate(int openHandle, byte[] data,
            uint dataLength, IntPtr userData);

        public delegate void ChannelInitEventDelegate(IntPtr initHandle,
            ChannelEvents Event,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]
            byte[] data, int dataLength);
        public delegate void ChannelOpenEventDelegate(int openHandle,
            ChannelEvents Event,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] byte[] data,
            int dataLength, uint totalLength, ChannelFlags dataFlags);

        [StructLayout(LayoutKind.Sequential)]
        public struct ChannelEntryPoints
        {
            public int Size;
            public int ProtocolVersion;
            [MarshalAs(UnmanagedType.FunctionPtr)]
            public VirtualChannelInitDelegate VirtualChannelInit;
            [MarshalAs(UnmanagedType.FunctionPtr)]
            public VirtualChannelOpenDelegate VirtualChannelOpen;
            [MarshalAs(UnmanagedType.FunctionPtr)]
            public VirtualChannelCloseDelegate VirtualChannelClose;
            [MarshalAs(UnmanagedType.FunctionPtr)]
            public VirtualChannelWriteDelegate VirtualChannelWrite;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct ChannelDef
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
            public string name;
            public ChannelOptions options;
        }

        public enum ChannelEvents
        {
            Initialized = 0,
            Connected = 1,
            V1Connected = 2,
            Disconnected = 3,
            Terminated = 4,
            DataRecived = 10,
            WriteComplete = 11,
            WriteCanceled = 12
        }

        [Flags]
        public enum ChannelFlags
        {
            First = 0x01,
            Last = 0x02,
            Only = First | Last,
            Middle = 0,
            Fail = 0x100,
            ShowProtocol = 0x10,
            Suspend = 0x20,
            Resume = 0x40
        }

        [Flags]
        public enum ChannelOptions : uint
        {
            Initialized = 0x80000000,
            EncryptRDP = 0x40000000,
            EncryptSC = 0x20000000,
            EncryptCS = 0x10000000,
            PriorityHigh = 0x08000000,
            PriorityMedium = 0x04000000,
            PriorityLow = 0x02000000,
            CompressRDP = 0x00800000,
            Compress = 0x00400000,
            ShowProtocol = 0x00200000
        }

        public enum ChannelReturnCodes
        {
            Ok = 0,
            AlreadyInitialized = 1,
            NotInitialized = 2,
            AlreadyConnected = 3,
            NotConnected = 4,
            TooManyChanels = 5,
            BadChannel = 6,
            BadChannelHandle = 7,
            NoBuffer = 8,
            BadInitHandle = 9,
            NotOpen = 10,
            BadProc = 11,
            NoMemory = 12,
            UnknownChannelName = 13,
            AlreadyOpen = 14,
            NotInVirtualchannelEntry = 15,
            NullData = 16,
            ZeroLength = 17
        }
    }
}

OK. I wasn't so bad. Now, how simple does the RDP client side DLL look in C/C++:

LPHANDLE              gphChannel;
DWORD                 gdwOpenChannel;
PCHANNEL_ENTRY_POINTS gpEntryPoints;

void WINAPI VirtualChannelOpenEvent(DWORD openHandle,
     UINT event, LPVOID pdata,
     UINT32 dataLength, UINT32 totalLength,
     UINT32 dataFlags)
{
    switch(event)
    {
        case CHANNEL_EVENT_DATA_RECEIVED:
        //reading data... and reading ... and reading

        break;
    }
}

VOID VCAPITYPE VirtualChannelInitEventProc(LPVOID pInitHandle, UINT event,
                                           LPVOID pData, UINT dataLength)
{
    UINT  ui;
    switch(event)
    {
        case CHANNEL_EVENT_INITIALIZED:
            break;
        case CHANNEL_EVENT_CONNECTED:
            ui = gpEntryPoints->pVirtualChannelOpen(
                 gphChannel,&gdwOpenChannel,
                "SAMPLE", (PCHANNEL_OPEN_EVENT_FN)VirtualChannelOpenEvent);
            if (ui != CHANNEL_RC_OK)
                MessageBox(NULL,TEXT("Open of RDP virtual "
                          "channel failed"),TEXT("Sample App"),MB_OK);
            break;
        case CHANNEL_EVENT_V1_CONNECTED:
            MessageBox(NULL,TEXT("Connecting to a non Windows 2000 "
                      "Terminal Server"), TEXT("Sample App"),MB_OK);
            break;
        case CHANNEL_EVENT_DISCONNECTED:
            break;
        case CHANNEL_EVENT_TERMINATED:
            LocalFree((HLOCAL)gpEntryPoints);
            break;
        default:
            break;
    }
}

BOOL VCAPITYPE VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints)
{
    CHANNEL_DEF cd;
    UINT        uRet;
    gpEntryPoints = (PCHANNEL_ENTRY_POINTS) LocalAlloc(LPTR,
                                    pEntryPoints->cbSize);
    CopyMemory(gpEntryPoints, pEntryPoints, pEntryPoints->cbSize);
    ZeroMemory(&cd, sizeof(CHANNEL_DEF));
    CopyMemory(cd.name, "SAMPLE", 6);
    uRet = gpEntryPoints->pVirtualChannelInit((LPVOID *)&gphChannel,
       (PCHANNEL_DEF)&cd,
        1, VIRTUAL_CHANNEL_VERSION_WIN2000,
        (PCHANNEL_INIT_EVENT_FN)VirtualChannelInitEventProc);
    if (uRet != CHANNEL_RC_OK)
    {
        MessageBox(NULL,TEXT("RDP Virtual channel Init Failed"),
                        TEXT("Sample App"),MB_OK);
        return FALSE;
    }
    if (cd.options != CHANNEL_OPTION_INITIALIZED)
        return FALSE;
    return TRUE;
}

After translation to C#:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Win32.WtsApi32;

namespace TSAddinInCS
{
    public class TSAddinInCS
    {
        static IntPtr Channel;
        static ChannelEntryPoints entryPoints;
        static int OpenChannel = 0;
        //ATTENTION: name should have 7 or less signs
        const string ChannelName = "TSCS";
        static Unpacker unpacker = null;
        static ChannelInitEventDelegate channelInitEventDelegate =
            new ChannelInitEventDelegate(VirtualChannelInitEventProc);
        static ChannelOpenEventDelegate channelOpenEventDelegate =
            new ChannelOpenEventDelegate(VirtualChannelOpenEvent);

        [ExportDllAttribute.ExportDll("VirtualChannelEntry",
            System.Runtime.InteropServices.CallingConvention.StdCall)]
        public static bool VirtualChannelEntry(ref ChannelEntryPoints entry)
        {
            ChannelDef[] cd = new ChannelDef[1];
            cd[0] = new ChannelDef();
            entryPoints = entry;
            cd[0].name = ChannelName;
            ChannelReturnCodes ret = entryPoints.VirtualChannelInit(
                ref Channel, cd, 1, 1, channelInitEventDelegate);
            if (ret != ChannelReturnCodes.Ok)
                MessageBox.Show("TSAddinInCS: RDP Virtual channel"
                                " Init Failed.\n" + ret.ToString(),
                                "Error", MessageBoxButtons.OK,
                                 MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        public static void VirtualChannelInitEventProc(IntPtr initHandle,
            ChannelEvents Event, byte[] data, int dataLength)
        {
            switch (Event)
            {
                case ChannelEvents.Initialized:
                    break;
                case ChannelEvents.Connected:
                    ChannelReturnCodes ret = entryPoints.VirtualChannelOpen(
                        initHandle, ref OpenChannel,
                        ChannelName, channelOpenEventDelegate);
                    if (ret != ChannelReturnCodes.Ok)
                        MessageBox.Show("TSAddinInCS: Open of RDP " +
                            "virtual channel failed.\n" + ret.ToString(),
                            "Error", MessageBoxButtons.OK,
                             MessageBoxIcon.Error);
                    else
                    {
                        string servername = System.Text.Encoding.Unicode.GetString(data);
                        servername = servername.Substring(0, servername.IndexOf('\0'));
                        //do something with server name
                    }
                    break;
                case ChannelEvents.V1Connected:
                    MessageBox.Show("TSAddinInCS: Connecting" +
                        " to a non Windows 2000 Terminal Server.",
                        "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    break;
                case ChannelEvents.Disconnected:
                    break;
                case ChannelEvents.Terminated:
                    GC.KeepAlive(channelInitEventDelegate);
                    GC.KeepAlive(channelOpenEventDelegate);
                    GC.KeepAlive(entryPoints);
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    break;
            }
        }

        public static void VirtualChannelOpenEvent(int openHandle,
            ChannelEvents Event, byte[] data,
            int dataLength, uint totalLength, ChannelFlags dataFlags)
        {
            switch (Event)
            {
                case ChannelEvents.DataRecived:
                    switch (dataFlags & ChannelFlags.Only)
                    {
                        case ChannelFlags.Only:
                            //we are here because first data is last too
                            //(totalLength == dataLength)
                            //...do something with data
                            break;
                        case ChannelFlags.First:
                            //first part of data arrived
                            //...do something with data
                            //(store 'coz it isn't all)
                            break;
                        case ChannelFlags.Middle:
                            //...and still arriving
                            //...do something with data
                            //(store 'coz it isn't all)
                            break;
                        case ChannelFlags.Last:
                            //...and still arriving
                            //well maybe not still this is the last part
                            //...do something with data (store)
                            //now we have full data in store
                            //we will do with it something more than storing
                            break;
                    }
                    break;
            }
        }
    }
}

Is That All?

(Isn't that a name of a U2 song? Actually - No.. We didn't export the function ... or did we?

[ExportDllAttribute.ExportDll("VirtualChannelEntry",
       System.Runtime.InteropServices.CallingConvention.StdCall)]

What does it mean? Well, I have a tool which can help with exporting functions to unmanaged code. You can read about it here. This tool is also included in the source (as a binary) and "added to post-build actions".

How To Get It All To Work

OK, now we have all pieces. The sample solution has two projects, the server and the client side and the ExportDLL tool, But it won't work, hrhrhrhr. We need to set it up first. What exactly do we need to run this:

We also need the following files:

Setup

Setting up the client (the server doesn't need setup) is easy:

Or just make a *.reg file with:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Terminal Server
  Client\Default\AddIns\TSAddinInCS]
"Name"="C:\\Documents and Settings\\Selvin\\Pulpit\\TSAddinInCS
        \\Client\\bin\\Debug\\TSAddinInCS.dll"

This changes C:\\Documents and Settings\\Selvin\\Pulpit\\TSAddinInCS\\Client\\bin\\Debug\\ to the proper path.

Running

Finally, we have to test it. Run mstsc.exe, connect to the server, run TSAddinInCSServer.exe on the server, hit the button, choose the file to send, and watch as this file opens on the local computer.

References

To Do List

Tips & Tricks/Notes

Many people ask why mstsc.exe crashes when they are sending data to the server.

The answer lies in very small read buffers on the server side.

From MSDN:
BufferSize
    Specifies the size, in bytes, of Buffer.
    If the client set the CHANNEL_OPTION_SHOW_PROTOCOL option,
    the value of this parameter should be at least CHANNEL_PDU_LENGTH.
    If the CHANNEL_OPTION_SHOW_PROTOCOL is not used, the value of this
    parameter should be at least CHANNEL_CHUNK_LENGTH.
...

CHANNEL_CHUNK_LENGTH is defined as 1600.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralRemote Desktop freeze
peroziu
12:44 15 Dec '09  
First of all, thanks for this great work.
I have problem when downloading files from server, remote desktop freeze while reciveing data, after receving, work fine.
Is there a way to start another tread to receve data?
sorry for bad english.
QuestionServer "OK Sent", client does nothing [modified]
pierlisi1
5:47 26 May '09  
Hello, great code!
1)
It is very interesting code, but we have a problem:
- Server says: OK - Sent
- Client does nothing!

We try to insert some MessageBox in client code, but none appears!

2)
What have we to do with ExportDll? I mean, have we to copy it somewhere?

3)
If we try to debug the client, it doesn't stop in our breakpoint!

4)
You wrote:
"Find HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns"
"Add a key TSAddinInCS "
Compiling code, there is a key automatically inserted in HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns: it is TSCS.
Is it an error? Maybe we have to add key "TSCS"?

Thanks a lot!
Pier

Pier Lisi

modified on Tuesday, May 26, 2009 10:53 AM

AnswerRe: Server "OK Sent", client does nothing
Selvin
23:12 26 May '09  
1) coz ExportDll not working coz:
2) u didnt set properly paths to ildasm and ilasm in ExportDll.exe.config
      <setting name="ildasmpath" serializeAs="String">
<value>C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe</value>
</setting>
<setting name="ilasmpath" serializeAs="String">
<value>C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ilasm.exe</value>
</setting>

3) look 1
4) no. no.

...works fascinates me...i can stare it for hours...

GeneralRe: Server "OK Sent", client does nothing
Szindi
4:53 8 Oct '09  
Same symptomes for me...

Your tool works as expected, it modifies the calling convetion on that method, i checked with reflector. But when i press F5 in VS, mstsc starting to run, but tsaddinincs.dll doesnt get loaded into the mstsc process. When this assembly solud be loaded into mstsc? My client(!) side is Windows Server 2008 R2 X64.
GeneralDoes this work with Windows Server 2008 TS?
towser007
21:20 16 May '09  
Hi, does this code use all the latest DLLs to use the dynamic virtual channels in Windows Server 2008 Terminal Services?
Towser
GeneralRe: Does this work with Windows Server 2008 TS?
Hosenite
6:17 23 Sep '09  
I am using this with Server 2008 TS. Though you may want to consider the newer Dynamic VC tech for it.
GeneralRe: Does this work with Windows Server 2008 TS?
Selvin
5:19 25 Sep '09  
This version works only with:
client: RDP client 6.1 or higher
server: (Vista?)Server 2008 or higher
and it using DVC
http://esilo.pl/tsaddinincs.zip[^]

[HKEY_CURRENT_USER\Software\Microsoft\Terminal Server
Client\Default\AddIns\TSAddinInCS]
"Name"="{guid of main class}"


now its implementing IWTSPlugin and it doesn't need ExportDll tool

...works fascinates me...i can stare it for hours...

modified on Friday, September 25, 2009 10:32 AM

GeneralTo register server side
Hosenite
4:54 29 Apr '09  
To be able to receive data from Virtual Channels on the server side you have to put a new key in the registry as well.

At:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TerminalServer\Addins\addin_key

Where "addin_key" is the name of the your add in. I set it as "TSCS" for this example.

The new Key requires 2 values.

It must have a REG_SZ value that contains the symbolic name of the driver or program. ApplicationName is the name of the executable. In this case it is "TSAddinInCSServer"
Name = ApplicationName

It must also have a REG_DWORD value that indicates the type of server application. For this case it is "0". Which stands for "User-mode application, session space."
Type = ApplicationType

Once this is done, your RDP session can receive files sent from client.

This information was found at: http://technet.microsoft.com/en-us/library/cc751287.aspx[^]
GeneralHow do I make remove the addin from my systray?
Mark Harmon
17:46 18 Mar '09  
I built and ran the sample and the code was helpful in understanding some issues I was facing. Now how do I remove this thing?
GeneralRe: How do I make remove the addin from my systray?
Selvin
23:09 18 Mar '09  
delete [HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default\AddIns\TSAddinInCS]

...works fascinates me...i can stare it for hours...

GeneralRe: How do I make remove the addin from my systray?
Mark Harmon
6:22 19 Mar '09  
Awesome, thanks!

I found that the key ended in TSCS, not TSAddinInCS. But still still thanks. Smile
GeneralTS Client done in C# doesn't works in a 64 bits operating system.
manuel.alvarez
20:27 5 Mar '09  
Hello Selvin,

I am checking a TS Client done in C# as the article and it seems that is not working when the machine which is executed has a 64 bits operating System.

I thought that changing option of ExportDll, for assembling to 64 bits was enought but I was wrong.

Do you know what can be the cause, and how to solve it?

Best Regards
GeneralRe: TS Client done in C# doesn't works in a 64 bits operating system.
Raney Eden
7:54 9 Mar '09  
I am having a similar issue. All 32 bit clients work fine. But when running under a 64-bit OS, the mstsc.exe (terminal services client) process tries to load the terminal server addin (which is compiled for 32-bit) and it fails for this reason. I am not sure how we can compile things on our 32-bit machine to have the same terminal server addin work on a 64-bit machine. Any suggestions????

Will we have to build on a 64-bit machine since we run ExportDLL.exe at the end...will ExportDLL.exe have to be compiled in 64-bit mode also?

Thanks in advance!!!!!!!
GeneralRe: TS Client done in C# doesn't works in a 64 bits operating system.
Selvin
14:11 9 Mar '09  
add this(bolded part of this line)(or at least /PE64 i really dont know :P)
arguments = string.Format("/PE64 /X64 /nologo /quiet /out:{0}.dll {0}.il /DLL{1} {2}", filename, res, debug ? "/debug" : "/optimize");
in ExportDll source


maybe u should also modify this lines in diassebled ".il" files
.vtfixup [1] int32 fromunmanaged at VT_01
.data VT_01 = int32[1]
from int32 to int64
i have no time to check if this working :P

...works fascinates me...i can stare it for hours...

QuestionRe: TS Client done in C# doesn't works in a 64 bits operating system.
Raney Eden
7:44 12 Mar '09  
I will try this ... I have also modified the ExportDLL program so it can reflect and look at all the members in a 64 bit assembly versus a 32 bit assembly. This is a nice enhancement. If you are interested, I can email this to you.

I will try these different combinations today. This is mission critical for us. I would love to donate something to you for this great work if we can get this woring. My email is "raney eden AT my-evo.com" ... simply remove the spaces to get the valid email address.

Would my original assembly have to be compiled for a x64 target before running ExportDLL.exe, or should AnyCpu work?

Thanks again!!!!!!!Cool
QuestionRe: TS Client done in C# doesn't works in a 64 bits operating system. [modified]
Sergio Oliveira
5:00 27 Mar '09  
Hi
I did all the steps above and my program is still not working. I'm just receiving a message from RDP that tells me "This computer can't connect to the remote computer". If I remove the Addin from register I can connect.

Can you give me any suggestion ?

Thanks

modified on Friday, March 27, 2009 2:44 PM

QuestionRe: TS Client done in C# doesn't works in a 64 bits operating system.
shanewho
9:21 8 Oct '09  
Did you ever get this to work? We are having the same problem.
AnswerRe: TS Client done in C# doesn't works in a 64 bits operating system.
Selvin
0:35 9 Oct '09  
yes ... its working at least on 2003 x64 ... the problem is in ExportDll ... and its easy to fix ...
-remove adding ".corflags 0x00000002"
-remove adding ".data VT_01 ..."
-remove adding ".vtentry ..." only .export [x] is needed
-add "/PE64 /X64" in assembler options

if you're connecting to 2008 server try
this[^]
version.

...works fascinates me...i can stare it for hours...

GeneralNo point to GC.KeepAlive
normanr
7:43 23 Jan '09  
GC.KeepAlive: References the specified object, which makes it ineligible for garbage collection from the start of the current routine to the point where this method is called.

So the way it's being used serves no purpose. The objects it references are instance objects, so they will always be ineligible for garbage collection.
GeneralRegSvr32 support (DllRegisterServer and DllUnregisterServer )
normanr
5:50 23 Jan '09  
What about adding the DllRegisterServer and DllUnregisterServer methods (and exporting them), so that you can just run:
regsvr32 TSAddinInCS.dll
on the client? (it would remove the requirement to hand-edit the registry)
GeneralVirtualWrite
adam mcneilly
9:14 5 Oct '08  
Does VirtualChannelWrite() really transport the file bytes to the client? or does it simulate a data transfer? if it does transmit the data to the server then why not use Socket?
GeneralRe: VirtualWrite
Selvin
23:14 5 Oct '08  
"Virtual channels are software extensions that can be used to add functional enhancements to a Terminal Services application
[^]"
... and this is what im showing in this article
tell me why it better to use Sockets then using TS API WHEN YOU'RE WRITTING TS ADDIN

...works fascinates me...i can stare it for hours...

RantDefinition of "Pure C#" is incorrect
The Dogcow Farmer
10:30 23 Sep '08  
Pure C# doesn't use DllImports or unsafe code.

Chuck Norris has the greatest Poker-Face of all time. He won the 1983 World Series of Poker, despite holding only a Joker, a Get out of Jail Free Monopoloy card, a 2 of clubs, 7 of spades and a green #4 card from the game UNO.
In the movie "The Matrix", Chuck Norris is the Matrix. If you pay close attention in the green "falling code" scenes, you can make out the faint texture of his beard.
Chuck Norris actually owns IBM. It was an extremely hostile takeover.

JokeRe: Definition of "Pure C#" is incorrect
Selvin
0:34 24 Sep '08  
Well generally you're right but ...
in code u use C# and C# only ... so its pure C# Smile ... but ...
its compiled to not pure MSIL :P

...works fascinates me...i can stare it for hours...

GeneralRe: Definition of "Pure C#" is incorrect
The Dogcow Farmer
0:49 24 Sep '08  
Selvin wrote:
in code u use C# and C# only ... so its pure C#


That's a load of crap. Pure C# does not use DllImports because they effectively lock the code into Windows. (thus using Windows-specific C/C++ code) This means Mono users can't run the code. I also saw unsafe code is processor related stuff, which locks you into one CPU platform. Pure C# can be run on any framework on any processor.

Chuck Norris has the greatest Poker-Face of all time. He won the 1983 World Series of Poker, despite holding only a Joker, a Get out of Jail Free Monopoloy card, a 2 of clubs, 7 of spades and a green #4 card from the game UNO.
In the movie "The Matrix", Chuck Norris is the Matrix. If you pay close attention in the green "falling code" scenes, you can make out the faint texture of his beard.
Chuck Norris actually owns IBM. It was an extremely hostile takeover.


Last Updated 31 May 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010