четверг, октября 19, 2006

Remoting и Event'ы

Пишу одно приложение, использующее Remoting и возникла необходимсоть подписать клиена на соытия сервера. Сам по-себе принцип достаточно прост, причем на FW 1.1 все собиралось и работало замечательно, а вот на 2.0 возникли проблемы. Приблизительная реализация такая: создаем класс событий

Code C#
[Serializable]
public class ScreenEventArgs : EventArgs
{
   private byte[] _screenState;
   public byte [] ScreenState
   {
      get { return _screenState; }
      set { _screenState = value; }
   }
   public ScreenEventArgs(Bitmap ScreenState)
   {
      // констуктов
   }
}

Делегат
Code C#

public delegate void ScreenEventHandler(object sender, ScreenEventArgs args);

И непосредственно интерфейс
Code C#
public interface IScreenWatcher
{
   event ScreenEventHandler ScreenEvent;
   bool GetScreen(string Name, string Password, int Interval);
}

Уведомление клиентов выглядит так:
Code C#
private void NotifyClients(ScreenEventArgs args)
{
   if(ScreenEvent == null) { return; }
   Delegate[] invkList = ScreenEvent.GetInvocationList();
   IEnumerator iE = invkList.GetEnumerator();
   while(iE.MoveNext())
   {
      ScreenEventHandler handler = (ScreenEventHandler) iE.Current;
      try
      {
         IAsyncResult aResult = handler.BeginInvoke(this, args, null, null);
      }
      catch
      {
         ScreenEvent -= handler;
      }
   }
}

Ну, а подписывание клиента производится следующим образом:
Code C#
IScreenWatcher _iScreenWatcher = Activator.GetObject(typeof (IScreenWatcher), Client.ScreenViewerAddress) as IScreenWatcher;
// Подписываемся на событие
_iScreenWatcher.ScreenEvent += new ScreenEventHandler(OnScreenChange);

Где, OnScreenChange
Code C#
public void OnScreeChange(object sender, ScreenEventArgs args)
{
   // Реализация реакции на событие
}

Вот основная проблема заключалась в том, что в момент подписки на событие выкидывалось исключение, сообщаюшее, что данная операция невозможна с текущи уровнем безопасности. Оказалось, что при создании TcpChannel'a необходимо было понизить уровень безопасности, например, таким образом:
Code C#
for (int ii = 0; ii < _plugInfo.Count; ii++)
{
   IDictionary props = new Hashtable();
   props["name"] = _plugInfo[ii].Name; // Имя канала -- не должно быть одинаковых
   props["port"] = Server.StartPort + ii + 1; // Порт
   BinaryServerFormatterSinkProvider bsf = new BinaryServerFormatterSinkProvider();
   bsf.TypeFilterLevel = TypeFilterLevel.Full; // Вот как раз эта строчка и понижает уровень безопасности
   TcpChannel tcpCh = new TcpChannel(props, null, bsf);
   ChannelServices.RegisterChannel(tcpCh, true);
}

пятница, октября 13, 2006

NetConnector v 1.0

Наконец-то довел до ума программу, о которой писал ниже -- а именно управляющую сетевыми адаптерами. Может кому-то и будет полезна =)
В общем, NetConnector v 1.0 скачать можно отсюда.

пятница, октября 06, 2006

Перекрываем Undo/Redo в Windows.Forms

Недавно столкнулся с такой проблемой -- надо было перекрыть стандартный обработчик Undo/Redo в RichTextBox'е. Казалось бы, что тут сложного -- делаем компонент-наследник от искомого, перегружаем WndProc и при получении EM_UNDO мирно молчим, не показывая, что что-либо произошло =) Но, не тут-то было. Реализовав данный алгоритм я с удивлением обнаружил, что ничего не получилось... Тогда пришлось прихывать на помощь две абсолютно потрясающих утилиты: Hawkeye (о которой, кстати, узнал из блога Exception'a) и .Net Reflector (которым пользовался достаточно давно).
В итоге выяснилось, что при получении сообщения EM_UNDO, контрол получает в нагрузку еще цепочку из 5-ти сообщений, которые так же нельзя пропускать. В общем, получилась несколько ненормальная реализация перекрытия Undo/Redo:




Code C#

public partial class SyntaxHighLightArea : RichTextBox
{
 private int UndoQueueCount = 0;

 [System.Security.Permissions.PermissionSet( System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
 protected override void WndProc(ref Message msg)
 {
  if (msg.Msg == Convert.ToInt32(PAL.Native.Msg.EM_UNDO))
  {
   UndoQueueCount = WinPAL.RedoBackCountMessages;
   return;
  }
  if (UndoQueueCount > 0)
  {
   if(UndoQueueCount == 1)
   {
    PAL.Native.SendMessage(Parent.Handle, PAL.Native.Msg.EM_UNDO, IntPtr.Zero, IntPtr.Zero);
   }
   UndoQueueCount--;
   return;
  }
  base.WndProc(ref msg);
 }

 private void SyntaxHighLightArea_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
 {
  if ((Control.ModifierKeys == Keys.Control) && (e.KeyCode == Keys.Z))
  {
   PAL.Native.SendMessage(this.Handle, PAL.Native.Msg.EM_UNDO, IntPtr.Zero, IntPtr.Zero);
  }
 }
.......skip.......
}

среда, октября 04, 2006

Сеть и WMI

Я достаточно долго искал способ, который бы позволил мне изменять параметры сетевого адаптера. Дело в том, что у меня приличное количество мест, где я подрубаюсь к сети и в итоге начались проблемы с запоминанием всех параметров. Одно время спасал обычный текстовик со списком, но все равно вручную менять все это было крайне напряжно. В итоге, мне подсказали способ, как можно это автоматизировать, используя netsh, но все равно, когда количество батников перевалило за 20 меня это начало раздражать...
И вот, пустился я в поиски по MSDN'у (вернее, сначала, я пытался откопать что-то в нете, но то ли плохо искал, то ли...). Вначале, я, естественно, наткнулся на функцию AddIPAddress, в IP Helper, но, во-1, она не всегда корректно работала, во-2, возможности изменять шлюзы и DNS адреса я не нашел... В итоге, я вспомнил про Windows Management Instrumentation(WMI), покопавшись, я нашел класс Win32_NetworkAdapterConfiguration, который как раз позволял мне реализовать мою затею =)
В общем, смысл реализации всего задуманного был таков: описываем класс опций адаптера



Code C#

public class AdapterOptions
{
   string _adapterName; // Имя адаптера
   public string AdapterName
   {
      get { return _adapterName; }
      set { _adapterName = value; }
   }
.......skip.......
   string _macAddress; // MAC адрес
   public string MACAddress
   {
      get { return _macAddress; }
      set { _macAddress = value; }
   }
   string [] _ipAdress; // IP адрес
   public string [] IpAdress
   {
      get { return _ipAdress;}
      set { _ipAdress = value;}
   }
.......skip.......
}


Тогда получение его опций можно произвести следующим образом:



Code C#

public static AdapterOptions GetAdapterOptions(string AdapterName)
{
 AdapterOptions adapterOptions = new AdapterOptions();
 ManagementClass netAdapters = new ManagementClass("Win32_NetworkAdapterConfiguration");
 ManagementObjectCollection netAdaptersCollection = netAdapters.GetInstances();
 foreach (ManagementBaseObject managementBaseObject in netAdaptersCollection)
 {
  if (!(bool)managementBaseObject["ipEnabled"])
   continue;
  if ((string)managementBaseObject["Caption"] == AdapterName)
  {
   adapterOptions.AdapterName = AdapterName;
   adapterOptions.ServiceName = (string) managementBaseObject["ServiceName"];
   adapterOptions.MACAddress = (string) managementBaseObject["MACAddress"];
   adapterOptions.IpAdress = (string[]) managementBaseObject["IPAddress"];
   adapterOptions.SubnetMask = (string[]) managementBaseObject["IPSubnet"];
   adapterOptions.Gateways = (string[]) managementBaseObject["DefaultIPGateway"];
   adapterOptions.DNSAddresses = (string[]) managementBaseObject["DNSServerSearchOrder"];
   return adapterOptions;
  }
 }
 return null;
}

И, соответственно, установка:



Code C#

public static void SetAdapterOptions(AdapterOptions adapterOptions)
{
 ManagementClass netAdapters = new ManagementClass("Win32_NetworkAdapterConfiguration");
 ManagementObjectCollection netAdaptersCollection = netAdapters.GetInstances();
 foreach (ManagementObject managementBaseObject in netAdaptersCollection)
 {
  if (!(bool)managementBaseObject["ipEnabled"])
   continue;
  if ((string)managementBaseObject["Caption"] == adapterOptions.AdapterName)
  {
   try
   {
    // Устанавливаем IP адреса и маски подсети
    // Получаем параметры метода
    ManagementBaseObject ipAddr = managementBaseObject.GetMethodParameters("EnableStatic");
    // Устанавливаем их
    ipAddr["IPAddress"] = adapterOptions.IpAdress;
    ipAddr["SubnetMask"] = adapterOptions.SubnetMask;
    // Вызываем метод с указанными параметрами
    managementBaseObject.InvokeMethod("EnableStatic", ipAddr, null);
.......skip.......
   }
   catch (Exception)
   {
    MessageBox.Show("Got error while trying to change adapter parameters", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    return;
   }
   MessageBox.Show("Setting new options successfully complete", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
   return;
  }
 }
}

Надо сказать, что работа с WMI на VB.Net и C# одно удовольствие, а вот если вы попробуете сделать это на С, то ваша жизнь превратится в кошмар =)