channels/drdynvc: refactor and start exporting interface

This commit is contained in:
Marc-André Moreau 2013-05-13 16:07:42 -04:00
parent 8e151409be
commit e72f898956
11 changed files with 285 additions and 95 deletions

View File

@ -487,6 +487,8 @@ int freerdp_channels_post_connect(rdpChannels* channels, freerdp* instance)
pChannelClientData->pChannelInitEventProc(pChannelClientData->pInitHandle, CHANNEL_EVENT_CONNECTED, hostname, hostnameLength);
}
channels->drdynvc = (DrdynvcClientContext*) freerdp_channels_get_static_channel_interface(channels, "drdynvc");
return 0;
}

View File

@ -32,6 +32,7 @@
#include <freerdp/utils/event.h>
#include <freerdp/utils/debug.h>
#include <freerdp/client/channels.h>
#include <freerdp/client/drdynvc.h>
#include <freerdp/channels/channels.h>
#ifdef HAVE_CONFIG_H
@ -103,6 +104,8 @@ struct rdp_channels
freerdp* instance;
wMessagePipe* MsgPipe;
DrdynvcClientContext* drdynvc;
};
#ifdef WITH_DEBUG_CHANNELS

View File

@ -234,7 +234,7 @@ static UINT32 drdynvc_read_variable_uint(wStream* stream, int cbLen)
static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cbChId, wStream* s)
{
int pos;
int error;
int status;
UINT32 ChannelId;
wStream* data_out;
@ -242,14 +242,14 @@ static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cb
pos = Stream_GetPosition(s);
DEBUG_DVC("ChannelId=%d ChannelName=%s", ChannelId, Stream_Pointer(s));
error = dvcman_create_channel(drdynvc->channel_mgr, ChannelId, (char*) Stream_Pointer(s));
status = dvcman_create_channel(drdynvc->channel_mgr, ChannelId, (char*) Stream_Pointer(s));
data_out = Stream_New(NULL, pos + 4);
Stream_Write_UINT8(data_out, 0x10 | cbChId);
Stream_SetPosition(s, 1);
Stream_Copy(data_out, s, pos - 1);
if (error == 0)
if (status == 0)
{
DEBUG_DVC("channel created");
Stream_Write_UINT32(data_out, 0);
@ -260,11 +260,11 @@ static int drdynvc_process_create_request(drdynvcPlugin* drdynvc, int Sp, int cb
Stream_Write_UINT32(data_out, (UINT32)(-1));
}
error = svc_plugin_send((rdpSvcPlugin*) drdynvc, data_out);
status = svc_plugin_send((rdpSvcPlugin*) drdynvc, data_out);
if (error != CHANNEL_RC_OK)
if (status != CHANNEL_RC_OK)
{
DEBUG_WARN("VirtualChannelWrite failed %d", error);
DEBUG_WARN("VirtualChannelWrite failed %d", status);
return 1;
}
@ -332,18 +332,23 @@ static void drdynvc_process_receive(rdpSvcPlugin* plugin, wStream* s)
case CAPABILITY_REQUEST_PDU:
drdynvc_process_capability_request(drdynvc, Sp, cbChId, s);
break;
case CREATE_REQUEST_PDU:
drdynvc_process_create_request(drdynvc, Sp, cbChId, s);
break;
case DATA_FIRST_PDU:
drdynvc_process_data_first(drdynvc, Sp, cbChId, s);
break;
case DATA_PDU:
drdynvc_process_data(drdynvc, Sp, cbChId, s);
break;
case CLOSE_REQUEST_PDU:
drdynvc_process_close_request(drdynvc, Sp, cbChId, s);
break;
default:
DEBUG_WARN("unknown drdynvc cmd 0x%x", Cmd);
break;
@ -386,18 +391,30 @@ static void drdynvc_process_terminate(rdpSvcPlugin* plugin)
DEBUG_DVC("terminating");
if (drdynvc->channel_mgr != NULL)
if (drdynvc->channel_mgr)
dvcman_free(drdynvc->channel_mgr);
free(drdynvc);
}
/**
* Channel Client Interface
*/
int drdynvc_get_version(DrdynvcClientContext* context)
{
drdynvcPlugin* drdynvc = (drdynvcPlugin*) context->handle;
return drdynvc->version;
}
/* drdynvc is always built-in */
#define VirtualChannelEntry drdynvc_VirtualChannelEntry
int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints)
{
drdynvcPlugin* _p;
DrdynvcClientContext* context;
CHANNEL_ENTRY_POINTS_EX* pEntryPointsEx;
_p = (drdynvcPlugin*) malloc(sizeof(drdynvcPlugin));
ZeroMemory(_p, sizeof(drdynvcPlugin));
@ -414,6 +431,19 @@ int VirtualChannelEntry(PCHANNEL_ENTRY_POINTS pEntryPoints)
_p->plugin.event_callback = drdynvc_process_event;
_p->plugin.terminate_callback = drdynvc_process_terminate;
pEntryPointsEx = (CHANNEL_ENTRY_POINTS_EX*) pEntryPoints;
if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_EX)) &&
(pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
{
context = (DrdynvcClientContext*) malloc(sizeof(DrdynvcClientContext));
context->handle = (void*) _p;
context->GetVersion = drdynvc_get_version;
*(pEntryPointsEx->ppInterface) = (void*) context;
}
svc_plugin_init((rdpSvcPlugin*) _p, pEntryPoints);
return 1;

View File

@ -21,6 +21,7 @@
#define __DRDYNVC_MAIN_H
#include <freerdp/types.h>
#include <freerdp/client/drdynvc.h>
typedef struct drdynvc_plugin drdynvcPlugin;

View File

@ -27,70 +27,14 @@
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/stream.h>
#include <freerdp/addin.h>
#include <winpr/stream.h>
#include <freerdp/utils/list.h>
#include "drdynvc_types.h"
#include "dvcman.h"
#define MAX_PLUGINS 10
typedef struct _DVCMAN DVCMAN;
struct _DVCMAN
{
IWTSVirtualChannelManager iface;
drdynvcPlugin* drdynvc;
const char* plugin_names[MAX_PLUGINS];
IWTSPlugin* plugins[MAX_PLUGINS];
int num_plugins;
IWTSListener* listeners[MAX_PLUGINS];
int num_listeners;
LIST* channels;
};
typedef struct _DVCMAN_LISTENER DVCMAN_LISTENER;
struct _DVCMAN_LISTENER
{
IWTSListener iface;
DVCMAN* dvcman;
char* channel_name;
UINT32 flags;
IWTSListenerCallback* listener_callback;
};
typedef struct _DVCMAN_ENTRY_POINTS DVCMAN_ENTRY_POINTS;
struct _DVCMAN_ENTRY_POINTS
{
IDRDYNVC_ENTRY_POINTS iface;
DVCMAN* dvcman;
ADDIN_ARGV* args;
};
typedef struct _DVCMAN_CHANNEL DVCMAN_CHANNEL;
struct _DVCMAN_CHANNEL
{
IWTSVirtualChannel iface;
DVCMAN* dvcman;
DVCMAN_CHANNEL* next;
UINT32 channel_id;
IWTSVirtualChannelCallback* channel_callback;
wStream* dvc_data;
HANDLE dvc_chan_mutex;
};
static int dvcman_get_configuration(IWTSListener* pListener, void** ppPropertyBag)
{
*ppPropertyBag = NULL;
@ -112,15 +56,17 @@ static int dvcman_create_listener(IWTSVirtualChannelManager* pChannelMgr,
ZeroMemory(listener, sizeof(DVCMAN_LISTENER));
listener->iface.GetConfiguration = dvcman_get_configuration;
listener->iface.pInterface = NULL;
listener->dvcman = dvcman;
listener->channel_name = _strdup(pszChannelName);
listener->flags = ulFlags;
listener->listener_callback = pListenerCallback;
if (ppListener)
*ppListener = (IWTSListener*)listener;
*ppListener = (IWTSListener*) listener;
dvcman->listeners[dvcman->num_listeners++] = (IWTSListener*)listener;
dvcman->listeners[dvcman->num_listeners++] = (IWTSListener*) listener;
return 0;
}
@ -197,18 +143,53 @@ UINT32 dvcman_get_channel_id(IWTSVirtualChannel * channel)
IWTSVirtualChannel* dvcman_find_channel_by_id(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId)
{
LIST_ITEM* curr;
int index;
BOOL found = FALSE;
DVCMAN_CHANNEL* channel;
DVCMAN* dvcman = (DVCMAN*) pChannelMgr;
for (curr = dvcman->channels->head; curr; curr = curr->next)
ArrayList_Lock(dvcman->channels);
index = 0;
channel = (DVCMAN_CHANNEL*) ArrayList_GetItem(dvcman->channels, index++);
while (channel)
{
if (((DVCMAN_CHANNEL*) curr->data)->channel_id == ChannelId)
if (channel->channel_id == ChannelId)
{
return (IWTSVirtualChannel*) curr->data;
found = TRUE;
break;
}
channel = (DVCMAN_CHANNEL*) ArrayList_GetItem(dvcman->channels, index++);
}
ArrayList_Unlock(dvcman->channels);
return (found) ? ((IWTSVirtualChannel*) channel) : NULL;
}
void* dvcman_get_channel_interface_by_name(IWTSVirtualChannelManager* pChannelMgr, const char* ChannelName)
{
int i;
BOOL found = FALSE;
void* pInterface = NULL;
DVCMAN_LISTENER* listener;
DVCMAN* dvcman = (DVCMAN*) pChannelMgr;
for (i = 0; i < dvcman->num_listeners; i++)
{
listener = (DVCMAN_LISTENER*) dvcman->listeners[i];
if (strcmp(listener->channel_name, ChannelName) == 0)
{
pInterface = listener->iface.pInterface;
found = TRUE;
break;
}
}
return NULL;
return (found) ? pInterface : NULL;
}
IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin)
@ -223,7 +204,7 @@ IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin)
dvcman->iface.FindChannelById = dvcman_find_channel_by_id;
dvcman->iface.GetChannelId = dvcman_get_channel_id;
dvcman->drdynvc = plugin;
dvcman->channels = list_new();
dvcman->channels = ArrayList_New(TRUE);
return (IWTSVirtualChannelManager*) dvcman;
}
@ -238,13 +219,14 @@ int dvcman_load_addin(IWTSVirtualChannelManager* pChannelMgr, ADDIN_ARGV* args)
pDVCPluginEntry = (PDVC_PLUGIN_ENTRY) freerdp_load_channel_addin_entry(args->argv[0],
NULL, NULL, FREERDP_ADDIN_CHANNEL_DYNAMIC);
if (pDVCPluginEntry != NULL)
if (pDVCPluginEntry)
{
entryPoints.iface.RegisterPlugin = dvcman_register_plugin;
entryPoints.iface.GetPlugin = dvcman_get_plugin;
entryPoints.iface.GetPluginData = dvcman_get_plugin_data;
entryPoints.dvcman = (DVCMAN*) pChannelMgr;
entryPoints.args = args;
pDVCPluginEntry((IDRDYNVC_ENTRY_POINTS*) &entryPoints);
}
@ -262,15 +244,25 @@ static void dvcman_channel_free(DVCMAN_CHANNEL* channel)
void dvcman_free(IWTSVirtualChannelManager* pChannelMgr)
{
int i;
int count;
IWTSPlugin* pPlugin;
DVCMAN_LISTENER* listener;
DVCMAN_CHANNEL* channel;
DVCMAN* dvcman = (DVCMAN*) pChannelMgr;
while ((channel = (DVCMAN_CHANNEL*) list_dequeue(dvcman->channels)) != NULL)
dvcman_channel_free(channel);
ArrayList_Lock(dvcman->channels);
list_free(dvcman->channels);
count = ArrayList_Count(dvcman->channels);
for (i = 0; i < count; i++)
{
channel = (DVCMAN_CHANNEL*) ArrayList_GetItem(dvcman->channels, i);
dvcman_channel_free(channel);
}
ArrayList_Unlock(dvcman->channels);
ArrayList_Free(dvcman->channels);
for (i = 0; i < dvcman->num_listeners; i++)
{
@ -326,8 +318,7 @@ static int dvcman_close_channel_iface(IWTSVirtualChannel* pChannel)
DEBUG_DVC("id=%d", channel->channel_id);
if (list_remove(dvcman->channels, channel) == NULL)
DEBUG_WARN("channel not found");
ArrayList_Remove(dvcman->channels, channel);
dvcman_channel_free(channel);
@ -366,8 +357,9 @@ int dvcman_create_channel(IWTSVirtualChannelManager* pChannelMgr, UINT32 Channel
{
DEBUG_DVC("listener %s created new channel %d",
listener->channel_name, channel->channel_id);
channel->channel_callback = pCallback;
list_add(dvcman->channels, channel);
ArrayList_Add(dvcman->channels, channel);
return 0;
}
@ -390,7 +382,7 @@ int dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelI
channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId);
if (channel == NULL)
if (!channel)
{
DEBUG_WARN("ChannelId %d not found!", ChannelId);
return 1;
@ -415,7 +407,7 @@ int dvcman_receive_channel_data_first(IWTSVirtualChannelManager* pChannelMgr, UI
channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId);
if (channel == NULL)
if (!channel)
{
DEBUG_WARN("ChannelId %d not found!", ChannelId);
return 1;
@ -436,7 +428,7 @@ int dvcman_receive_channel_data(IWTSVirtualChannelManager* pChannelMgr, UINT32 C
channel = (DVCMAN_CHANNEL*) dvcman_find_channel_by_id(pChannelMgr, ChannelId);
if (channel == NULL)
if (!channel)
{
DEBUG_WARN("ChannelId %d not found!", ChannelId);
return 1;

View File

@ -23,8 +23,62 @@
#include <freerdp/dvc.h>
#include <freerdp/addin.h>
#include <winpr/collections.h>
#include "drdynvc_main.h"
#define MAX_PLUGINS 32
struct _DVCMAN
{
IWTSVirtualChannelManager iface;
drdynvcPlugin* drdynvc;
const char* plugin_names[MAX_PLUGINS];
IWTSPlugin* plugins[MAX_PLUGINS];
int num_plugins;
IWTSListener* listeners[MAX_PLUGINS];
int num_listeners;
wArrayList* channels;
};
typedef struct _DVCMAN DVCMAN;
struct _DVCMAN_LISTENER
{
IWTSListener iface;
DVCMAN* dvcman;
char* channel_name;
UINT32 flags;
IWTSListenerCallback* listener_callback;
};
typedef struct _DVCMAN_LISTENER DVCMAN_LISTENER;
struct _DVCMAN_ENTRY_POINTS
{
IDRDYNVC_ENTRY_POINTS iface;
DVCMAN* dvcman;
ADDIN_ARGV* args;
};
typedef struct _DVCMAN_ENTRY_POINTS DVCMAN_ENTRY_POINTS;
struct _DVCMAN_CHANNEL
{
IWTSVirtualChannel iface;
DVCMAN* dvcman;
UINT32 channel_id;
IWTSVirtualChannelCallback* channel_callback;
wStream* dvc_data;
HANDLE dvc_chan_mutex;
};
typedef struct _DVCMAN_CHANNEL DVCMAN_CHANNEL;
IWTSVirtualChannelManager* dvcman_new(drdynvcPlugin* plugin);
int dvcman_load_addin(IWTSVirtualChannelManager* pChannelMgr, ADDIN_ARGV* args);
void dvcman_free(IWTSVirtualChannelManager* pChannelMgr);
@ -34,5 +88,7 @@ int dvcman_close_channel(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelI
int dvcman_receive_channel_data_first(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId, UINT32 length);
int dvcman_receive_channel_data(IWTSVirtualChannelManager* pChannelMgr, UINT32 ChannelId, BYTE* data, UINT32 data_size);
void* dvcman_get_channel_interface_by_name(IWTSVirtualChannelManager* pChannelMgr, const char* ChannelName);
#endif

View File

@ -35,7 +35,6 @@
#include "rdpei_main.h"
typedef struct _RDPEI_LISTENER_CALLBACK RDPEI_LISTENER_CALLBACK;
struct _RDPEI_LISTENER_CALLBACK
{
IWTSListenerCallback iface;
@ -43,8 +42,8 @@ struct _RDPEI_LISTENER_CALLBACK
IWTSPlugin* plugin;
IWTSVirtualChannelManager* channel_mgr;
};
typedef struct _RDPEI_LISTENER_CALLBACK RDPEI_LISTENER_CALLBACK;
typedef struct _RDPEI_CHANNEL_CALLBACK RDPEI_CHANNEL_CALLBACK;
struct _RDPEI_CHANNEL_CALLBACK
{
IWTSVirtualChannelCallback iface;
@ -53,14 +52,16 @@ struct _RDPEI_CHANNEL_CALLBACK
IWTSVirtualChannelManager* channel_mgr;
IWTSVirtualChannel* channel;
};
typedef struct _RDPEI_CHANNEL_CALLBACK RDPEI_CHANNEL_CALLBACK;
typedef struct _RDPEI_PLUGIN RDPEI_PLUGIN;
struct _RDPEI_PLUGIN
{
IWTSPlugin iface;
IWTSListener* listener;
RDPEI_LISTENER_CALLBACK* listener_callback;
};
typedef struct _RDPEI_PLUGIN RDPEI_PLUGIN;
const char* RDPEI_EVENTID_STRINGS[] =
{
@ -305,32 +306,47 @@ static int rdpei_on_new_channel_connection(IWTSListenerCallback* pListenerCallba
static int rdpei_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
{
RDPEI_PLUGIN* echo = (RDPEI_PLUGIN*) pPlugin;
int status;
RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) pPlugin;
DEBUG_DVC("");
echo->listener_callback = (RDPEI_LISTENER_CALLBACK*) malloc(sizeof(RDPEI_LISTENER_CALLBACK));
ZeroMemory(echo->listener_callback, sizeof(RDPEI_LISTENER_CALLBACK));
rdpei->listener_callback = (RDPEI_LISTENER_CALLBACK*) malloc(sizeof(RDPEI_LISTENER_CALLBACK));
ZeroMemory(rdpei->listener_callback, sizeof(RDPEI_LISTENER_CALLBACK));
echo->listener_callback->iface.OnNewChannelConnection = rdpei_on_new_channel_connection;
echo->listener_callback->plugin = pPlugin;
echo->listener_callback->channel_mgr = pChannelMgr;
rdpei->listener_callback->iface.OnNewChannelConnection = rdpei_on_new_channel_connection;
rdpei->listener_callback->plugin = pPlugin;
rdpei->listener_callback->channel_mgr = pChannelMgr;
return pChannelMgr->CreateListener(pChannelMgr, "Microsoft::Windows::RDS::Input", 0,
(IWTSListenerCallback*) echo->listener_callback, NULL);
status = pChannelMgr->CreateListener(pChannelMgr, "Microsoft::Windows::RDS::Input", 0,
(IWTSListenerCallback*) rdpei->listener_callback, &(rdpei->listener));
rdpei->listener->pInterface = rdpei->iface.pInterface;
return status;
}
static int rdpei_plugin_terminated(IWTSPlugin* pPlugin)
{
RDPEI_PLUGIN* echo = (RDPEI_PLUGIN*) pPlugin;
RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) pPlugin;
DEBUG_DVC("");
free(echo);
free(rdpei);
return 0;
}
/**
* Channel Client Interface
*/
int rdpei_get_version(RdpeiClientContext* context)
{
//RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*) context->handle;
return 1;
}
#ifdef STATIC_CHANNELS
#define DVCPluginEntry rdpei_DVCPluginEntry
#endif
@ -339,6 +355,7 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
{
int error = 0;
RDPEI_PLUGIN* rdpei;
RdpeiClientContext* context;
rdpei = (RDPEI_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "rdpei");
@ -352,6 +369,13 @@ int DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
rdpei->iface.Disconnected = NULL;
rdpei->iface.Terminated = rdpei_plugin_terminated;
context = (RdpeiClientContext*) malloc(sizeof(RdpeiClientContext));
context->handle = (void*) rdpei;
context->GetVersion = rdpei_get_version;
rdpei->iface.pInterface = (void*) context;
error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpei", (IWTSPlugin*) rdpei);
}

View File

@ -29,6 +29,8 @@
#include <freerdp/addin.h>
#include <freerdp/utils/debug.h>
#include <freerdp/client/rdpei.h>
#define RDPINPUT_HEADER_LENGTH 6
#define CONTACT_DATA_CONTACTRECT_PRESENT 0x0001

View File

@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Dynamic Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIENT_DRDYNVC_H
#define FREERDP_CHANNEL_CLIENT_DRDYNVC_H
/**
* Client Interface
*/
typedef struct _drdynvc_client_context DrdynvcClientContext;
typedef int (*pcDrdynvcGetVersion)(DrdynvcClientContext* context);
struct _drdynvc_client_context
{
void* handle;
pcDrdynvcGetVersion GetVersion;
};
#endif /* FREERDP_CHANNEL_CLIENT_DRDYNVC_H */

View File

@ -0,0 +1,37 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Dynamic Virtual Channel Extension
*
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FREERDP_CHANNEL_CLIENT_RDPEI_H
#define FREERDP_CHANNEL_CLIENT_RDPEI_H
/**
* Client Interface
*/
typedef struct _rdpei_client_context RdpeiClientContext;
typedef int (*pcRdpeiGetVersion)(RdpeiClientContext* context);
struct _rdpei_client_context
{
void* handle;
pcRdpeiGetVersion GetVersion;
};
#endif /* FREERDP_CHANNEL_CLIENT_RDPEI_H */

View File

@ -67,6 +67,8 @@ struct _IWTSListener
/* Retrieves the listener-specific configuration. */
int (*GetConfiguration) (IWTSListener* pListener,
void** ppPropertyBag);
void* pInterface;
};
struct _IWTSVirtualChannel
@ -115,6 +117,10 @@ struct _IWTSPlugin
/* Notifies the plug-in that the Remote Desktop Connection (RDC) client
has terminated. */
int (*Terminated) (IWTSPlugin* pPlugin);
/* Extended */
void* pInterface;
};
struct _IWTSListenerCallback