Показаны сообщения с ярлыком Программизм. Показать все сообщения
Показаны сообщения с ярлыком Программизм. Показать все сообщения

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

четверг, августа 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'я -- кто бы мне сказал, как привести код в человеческий вид =(

четверг, июля 06, 2006

Удовлетворение =)

Вот и произошло мое первое "боевое" знакомство с php, причем сразу не с самых простых вещей =) А заодно попрактиковался в написании плагинов к программам, которые не вполне хорошо продуманы в "модульном" плане.
Все дело в том, что я, в силу своих возрастных показателей, работаю в компьютерном клубе (надо сказать, что не так уж это и плохо) и пришла нам недавно идея отображать realtime статистику занятости клуба на сайте.
Ну раз решили, то надо делать, подумал я и сел читать документацию.
А в клубе у нас стоит ClubControl -- программа во всех отношениях классная и мастерски написанная... во всех, кроме поддержки плагинов... Ну блин, модуль, умеющий реагировать только на 4-5 событий -- это разве полнофункциональный плагин? Кроме того, мое личное мнение таково, что если ты предоставляешь сторонним разработчикам писать свои модули, то будь добр, выложи SDK, а не просто пример с куцими комментариями. Так или иначе, разобравшись с возможностями, которые предоставили мне разработчики, я засел за кодинг. Как это всегда бывает, после того, как плагин был готов и разлит по всем машинам, в голову пришла идея "немного проапгрейдить" его, дабы у админов была возможность смотреть полную статистику по клубу.
Недолго раздумывая, я дописал все, что было нужно и лег спать, в это время мой друг написал простенький скрипт на пхп, который отображал общую статистику. Проснувшись, я понял, что на меня напала жажда деятельности, а поскольку товарищ свалил домой, а возможностей его скрипта явно не хватало, то разбираться прийдется самому, в итоге я понял, что обучаться работе с библиотекой GD а также с возможностью вывода кириллических символов на изображении -- это круто для первого раза. Просидев часов 5 над всей этой гадостью я все же закончил оба скрипта (для общего обозрения и админский), осталось только залить все это дело на сайт....

четверг, июня 29, 2006

Хм.. что-то меня вслед за половиной рунета потянуло на подобного рода байтомарание, задумавшись о причине, которая сыграла основную роль в свершенном мной подвиге (а надо сказать, что я абсолютно не предрасположен к подобного рода забавам) я пришел к выводу, что все это из-за мелкомягких, а вернее из-за того, что M$ никогда не может ничего дописать до конца и нормально. Я понимаю, что такой огромный продукт как Windows невозможно протестировать от начала до конца, но тем не менее...
Началось все с того, что вчера вечером у меня в разгар очередных издевательств над XP-шкой слетела VMWare (я давно понял, что все свои бредовые идеи лучше реализовывать не под основной операционкой). Надо сказать, что меня это больше удивило чем расстроило, т.к. я привык, что виртуалка всегда работает на ура. "Ничего", -- подумал я. -- "переставить недолго".
Не откладывая дело в долгий ящик я переставил свою "машину", поставил на исполнение только что написанную программу, которая должна была проверить все мои файлы на профпригодность (специально ради этого вся гостевая операционнка была захламлена по самое нехочу), а именно прозаичная идея поиска файлов по маске, реализованная на новом для меня C#. От осознания того, что первая моя программа заработала, причем на первый взгляд довольно удачно, я выпил пива и лег спать.
Не могу сказать, что та картина, которую мне "посчастливилось" лицезреть в момент пробуждения меня порадовала -- на мониторе красовался BSoD... Я было подумал,что опять засбоила виртуалка, но каково было мое негодование, когда я обнаружил, что это моя родная система двинула коней. С тревогой нажав на ресет я задумался, что же привело к таким плачевным результатам -- недоработка ли в моей программе, вызвавшая "замыкание мозгов VMWare", которая в свою очередь опрокинула Винду (в голове у меня навязчиво крутилось название для недавно написанной программы -- "Убийственная рекурсия v. 0.0.0.1"), либо Винда не поладила с виртуалкой. Короче моим размышлениям не дано было закончиться с утра -- Винда слетела в 0 -- не помогло ничего....
Обиженный на всех и вся я поплелся на работу, где меня весь день неотступно преследовали кошмары о моих неработающих программах, недовольных заказчиках и глючном Framework'e... Весь день мне хотелось спать и ни в какую не удавалось сосредоточиться на работе. В итоге я забил на все, залез на блог знакомого, почитал, проникся идеей и попал сюда. Свершилось чудо, как тут же выдал мне товарищ и пригласил отметить это дело партейкой в Quake, чем я сейчас и намерен заняться, благо рабочий день на исходе и скоро я окажусь дома в обществе неработающего компа и Троелсена...
Да здравствует .Net!!!