четверг, октября 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# одно удовольствие, а вот если вы попробуете сделать это на С, то ваша жизнь превратится в кошмар =)

суббота, сентября 30, 2006

Ужасы нашего городка

Казалось бы: Питер -- культурная столица, но иногда от творений скульпторов эпохи совка наворачиваются слезы безграничного счастья =) И мысли "не зря я поперся сегодня шататься по городу" приходят все чаще. Итак, Парк Победы:

Спотрсменки нежно обнимающие друг друга всегда немного удивляли меня.. это что же за спорт

















В Союзе секса не было, это знают все, но как вы объясните такую прелюдию? Странно, но творцов тянет на гомосексуализм уже 2-ой раз













Ну и на последок рушим мифы о том, что из-за железного занавеса в нашу страну не проникала никакая капиталистическая литература. Прошу любить и жаловать -- Горлум, собственной персоной (Абыр-абыр хе-хе =)

четверг, сентября 28, 2006

С корабля на бал

Стоило мне только вернуться из великолепного отдыха на Черном море, как сразу на работе начали происходить всякие раздражающие события. Сегодня с утра нужно было перелить винт с одного компа на другой. Казалось, операция достаточно простая и не требует напряжения... Но не тут-то было.
Поставил я копироваться и только отвернулся чтобы уходить, как услышал характерный писк динамика, сообщающий, что моей мечте посидеть попить кофе пришел конец. С опаской я взклянул на экран и увидел сообщение об ошибке номер какая-то, утверждающей, что "partition was improperly dismounted". Понадеявшись на случайный глюк партишина, я перегрузился и попроовал снова. Тот же результат. В общем, не буду описывать все методы, к которым я прибегал, пытаясь все же устранить эту ошибку, скажу только что перепробовал все известные мне утилиты для копирования и восстановления дисков...
В итоге, практически отчаявшись, я погуглил и выяснилось, что подобная хреномуть лечится обычным чекдиском.... Как я злился =(

суббота, сентября 02, 2006

Обидно

Блин, это же надо, уже пол-года, наверное, как пользуюсь Resharper'ом, и надо сказать, что горя не знал (кроме того, когда приходилось пользоваться студией без него ;). Умничка решарпер всегда все самое нудное делал сам, не надо запоминать тонны пространств имен -- сам найдет, сам подставит, сам оптимизирует... Вот как раз сегодня на подобной оптимизации я и обжегся. Все началось с того, что я скачал новую версию, поставил и, довыольный ускорением его работы, с радостью стал ковырятся в одном небольшом проекте. В общем, понадобилось мне сделать компонент, реализовал была примерно такая




Code C#
public partial class SHL : System.Windows.Forms.RichTextBox
{
   public SHL()
   {
      InitializeComponent();
   }

   private void SHL_TextChanged(object sender, System.EventArgs e)
   {
      //...
   }
}


Так вот, как раз при попытке оптимизировать по предложения Решарпера using'и, дабы избежать System.Windows.Forms.RichTextBox, у меня как раз и случилось огорчение: вначале упала студия ("Не беда",-- подумал я). Открыв вновь проект через ее главную страницу я с удивлением обнаружил, что в нем явно не хватает файлов ("Опять же, вроде ничего страшного -- бывает"). Но как же я был удивлен, когда обнаружил, что файлы не просто так исчезли из проекта -- гадкий Решарпир их попросту удалил физически =(
После удачного, на первый взгляд, восстановления, оказалось что в них просто бинарный мусор :'(
P.S. Все же, Решарпер -- очень мощная вещь, а JetBrains (компания, выпустившея его), выпускает довольно много дргих продуктов, среди которых Omea Reader -- единственный RSS-фидер, который меня полностью устраивает

Блог 2.0b

Хех.. зашел сегодня на блог и обнаружил, что можно перейти на бета-версию блога, которая поддерживает неплохой редактор шаблонов, категории и еще несколько мелочей... Беря во внимание то, что Blogger активно пропагандируют Google AdSense, то я абсолютно не удивился, что новый движок использует гугловский аккаунт..
Тем не менее, решил я перейти на новую версию и, в принципе, не жалею =)

пятница, августа 11, 2006

Промучавшись часа два и доведя своего друга, разбирающегося в Web-программировании, чуть ли не до истерики, я все же сумел внести кое-какие исправления в предыдущий пост -- теперь куски кода стали похожи хоть на что-то... Жизнь прекрасна.

четверг, августа 10, 2006

Что можно сделать от скуки

Что-то последнее время напало на меня какое-то расслабленное и добродушное настроение -- сижу на форуме и мне даже не лень отвечать более чем 3 словами, интересно, к чему бы это? =) Вероятно, просто скучно...
Но не суть, в общем недавно наткнулся на не вполне тривиальный вопрос -- как перехватить момент закрытия программы для того, чтобы иметь возможность сохранить какие-либо данные. Честно-говоря, вариантов сделать процесс "неубиваемым" (читай отловить момент закрытия), не прибегая к Native'ным функциям и их перехвату, я не знал. Сразу мне вспомнился Антивирус Касперского, авторы которого заявляли, что их детище полностью защищено от посягательств на свою "жизнь" любых вредоносных программ. Однако же, методы нашлись, при желании можно спокойно получить хендл процесса, а дальше -- делай с бедным "касперычем" что хочешь. Т.е. вариант с перехватами отпал, во-1, в силу описанных причин, во-2, потому что, насколько я понял, человеку, которому понадобилось реализовать подобное, нафиг не упали никакие стелс-элементы. В общем, решил я пойти по вполне мирному пути.
Идеи с постоянным сохранением данных в файл и поиском окна приложения для его дальнейшего запуска, я посчитал несколько несолидными и, кроме того, нифига не практичными. Поэтому решил я копать в направлении, подсказанном мне MSDN'ом. А прочитал я об очередности событий, проходящих в момент остановки процесса. Но нифига, как оказалось, мне несветило, хоть потоки и завершаются в первую очередь, но отловить этот момент в основном потоке не удалось =(
Вторая идея была навеяна безысходностью -- попытаться отловить момент, когда ExitCode процесса перестанет быть STILL_ACTIVE, ясное дело, что нифига не вышло =)
В общем, помучавшись некоторое время, я все же вышел на верный курс. Как всегда оказалось, что все самые простые идеи приходит в голову самыми последними. Поскольку, как уже было сказано, никаких нестандартных технологий я решил не использовать, то пришлось пожертвовать еще одним приложением, которое, в принципе, можно объявить как сервис. Либо же, если боротся за максимальную надежность, то все же применить технологии кернел-хака. В голову приходят сразу две идеи:
  1. инжектировать какой-нибудь критический системный процесс, однако это чревато тем, что анти-руткиты могут попытаться завершить его и комп просто уйдет в ребут.
  2. инжектировать winlogon -- хрен знает почему, но его никто не трогает =)
В общем, это уже детали реализации, а сам процесс отлавливания закрытия приложения прост до банального -- нам нужно стать для него отладчиком =) В простейшем случае это будет выглядеть следующим образом



Code C++
PROCESS_INFORMATION pi;
STARTUPINFO si;
::ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
::CreateProcess("c:\\windows\\notepad.exe", 0, 0, 0, 0, DEBUG_PROCESS DEBUG_ONLY_THIS_PROCESS, 0, 0, &si, &pi);
DEBUG_EVENT de;
while(WaitForDebugEvent(&de, INFINITE))
{
   if(de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
   {
      ::MessageBox(0, "Closing", "Ahtung!", 0);
      break;
   }
   ::ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
}
::DebugActiveProcessStop(pi.dwProcessId);

После этого мы со спокойной душой можем делать дамп памяти процесса и сохранять все нужные данные =)
Естественно, что для того, чтобы применять этот код в "бовых" условиях, нужно позаботиться о том, чтобы в случае исключения внутри отлаживаемого процесса, наш "дебаггер" возвращал DBG_NOT_HANDLED. Кроме того, чтобы получить возможность отлаживать процесс, не созданный в режиме DEBUG_PROCESS, нужно получить в системе права отладчика, для этого можно использовать следующий подход:




Code C++
void IAmaDebugger(bool debug)
{
   HANDLE hCp;
   if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hCp))
   {
      TOKEN_PRIVILEGES tp;
      tp.PrivilegeCount = 1;
      ::LookupPrivilegeValue(0, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
      tp.Privileges[0].Attributes = debug ? SE_PRIVILEGE_ENABLED : 0;
      ::AdjustTokenPrivileges(hCp, 0, &tp, sizeof(tp), 0, 0);
      ::CloseHandle(hCp);
   }
}

В общем, я остался морально и интеллектуально удовлетворенным, почаще бы такие вопросы возникали =)
ЗЫЖ Бр-р-р-р, как же все-таки непросто мне осознать основы HTML'я -- кто бы мне сказал, как привести код в человеческий вид =(