Лучший отвечающий
WebBrowser AccessViolationException

Вопрос
-
Здравствуйте, подскажите как избавиться от исключения AccessViolationException в WebBrowser.
Возникает оно не периодически бывает через 100000 загрузок сайта, а бывает через 10, в общем не предсказуемо.
Задача стоит в том, что бы получать запрос сайта через асинхронный сокет, и в ответ посылать скрин сайта.
Это код для работы сокета ->
using System; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; using System.Drawing; using System.Windows.Forms; namespace WebShotServer.Classes { public class Listener { private static Parameters parameters; private Socket listener; private static ManualResetEvent allDone = new ManualResetEvent(false); private class StateObject { public Socket workSocket = null; public const int bufferSize = 1024; public byte[] buffer = new byte[bufferSize]; public StringBuilder sb = new StringBuilder(); } public Listener(Parameters param) { parameters = param; this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this.listener.Bind(new IPEndPoint(IPAddress.Any, parameters.port)); this.listener.Listen(1000); } public void START() { try { Console.WriteLine("\n--------------------------------------------\nОжидание соединения..."); while (true) { allDone.Reset(); listener.BeginAccept(new AsyncCallback(AcceptCallback), listener); allDone.WaitOne(); } } catch (SocketException error) { Console.WriteLine(error.ToString()); } } public static void AcceptCallback(IAsyncResult ar) { allDone.Set(); Socket listen = (Socket)ar.AsyncState; Socket handler = listen.EndAccept(ar); StateObject state = new StateObject(); state.workSocket = handler; handler.BeginReceive(state.buffer, 0, StateObject.bufferSize, 0, new AsyncCallback(ReadCallback), state); } public static void ReadCallback(IAsyncResult ar) { String messageURL = String.Empty; StateObject state = (StateObject)ar.AsyncState; Socket WorkSocket = state.workSocket; int bytesRead = WorkSocket.EndReceive(ar); if (bytesRead > 0) { messageURL = Encoding.Unicode.GetString(state.buffer, 0, bytesRead); Console.WriteLine("Запрос миниатюры по адресу: {0}", messageURL); Thumbnail thumbnail = new Thumbnail(parameters, messageURL); Thread thread = new Thread(thumbnail.GetThumbnail); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); Image webShot = thumbnail.resultThumbnail; ImageConverter imageConverter = new ImageConverter(); byte[] byteData = (byte[])imageConverter.ConvertTo(webShot, typeof(byte[])); int size = byteData.Length; byte[] datasize = new byte[4]; datasize = BitConverter.GetBytes(size); PicSize picSize = new PicSize(); picSize.WorkSocket = WorkSocket; picSize.datasize = datasize; picSize.pic = byteData; //уничтожаем webShot.Dispose(); byteData = null; imageConverter = null; thread.Abort(); thread = null; thumbnail = null; messageURL = null; WorkSocket.BeginSend(picSize.datasize, 0, datasize.Length, 0, new AsyncCallback(SendCallbackOne), picSize); } } public class PicSize { public Socket WorkSocket; public byte[] datasize; public byte[] pic; } private static void SendCallbackOne(IAsyncResult ar) { try { PicSize picsize = (PicSize)ar.AsyncState; Socket handler = picsize.WorkSocket; int bytesSent = handler.EndSend(ar); handler.BeginSend(picsize.pic, 0, picsize.pic.Length, 0, new AsyncCallback(SendCallbackTwo), handler); } catch (Exception error) { Console.WriteLine(error.ToString()); } } private static void SendCallbackTwo(IAsyncResult ar) { try { Socket handler = (Socket)ar.AsyncState; int bytesSent = handler.EndSend(ar); Console.WriteLine("- Миниатюра сайта отправлена клиенту."); handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception error) { Console.WriteLine(error.ToString()); } } } }
А это собственно класс для получения скриншота ->using System; using System.Net; using System.Drawing; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.Security.Permissions; using System.Threading; namespace WebShotServer.Classes { [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] public class Thumbnail { public string errorMessage; private HttpWebRequest webRequest; public Bitmap resultThumbnail; private Parameters parameters; private string url; private object loker = new object(); public Thumbnail(Parameters parameters, string url) { this.parameters = parameters; this.url = url; this.resultThumbnail = null; this.webRequest = null; this.errorMessage = null; } [STAThread] public void GetThumbnail() { lock (loker) { this.url = this.url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) ? this.url : this.url = "http://" + this.url; try { this.webRequest = (HttpWebRequest)WebRequest.Create(url); this.webRequest.AllowAutoRedirect = true; this.webRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; .NET CLR 3.5.21022; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"; this.webRequest.Referer = "http://www.ru"; this.webRequest.ContentType = "text/html"; this.webRequest.Accept = "*/*"; this.webRequest.KeepAlive = false; using (HttpWebResponse webResponse = (HttpWebResponse)this.webRequest.GetResponse()) { string x = webResponse.StatusDescription; } } catch (Exception error) { this.errorMessage = error.Message; this.resultThumbnail = ReturnErrorThumbnail(0); this.parameters = null; this.url = null; this.webRequest = null; return; } WebBrowser webBrowser = new WebBrowser(); webBrowser.Size = new Size(this.parameters.browserWidth, this.parameters.browserHeight); webBrowser.ScrollBarsEnabled = false; webBrowser.ScriptErrorsSuppressed = true; this.webRequest = (HttpWebRequest)WebRequest.Create(url); this.webRequest.AllowAutoRedirect = true; this.webRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; .NET CLR 3.5.21022; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"; this.webRequest.Referer = "http://www.ru"; this.webRequest.ContentType = "text/html"; this.webRequest.AllowWriteStreamBuffering = true; this.webRequest.AutomaticDecompression = DecompressionMethods.GZip; this.webRequest.Method = "GET"; this.webRequest.Proxy = null; this.webRequest.ReadWriteTimeout = 20; HttpStatusCode status; try { using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) { status = webResponse.StatusCode; } } catch (Exception error) { this.errorMessage = error.Message; this.resultThumbnail = ReturnErrorThumbnail(0); webBrowser.Dispose(); this.parameters = null; this.url = null; this.webRequest = null; return; } if (status == HttpStatusCode.OK || status == HttpStatusCode.Moved) { webBrowser.Navigate(url); try { System.Timers.Timer timer = new System.Timers.Timer(parameters.timeOut); timer.AutoReset = false; timer.Start(); while ((webBrowser.ReadyState.ToString() != "Complete")) { if (timer.Enabled == false) { this.resultThumbnail = ReturnErrorThumbnail(0); timer.Dispose(); webBrowser.Dispose(); this.parameters = null; this.url = null; this.webRequest = null; Console.WriteLine("\n---Время ожидания вышло.---\n"); return; } Application.DoEvents(); } } catch (AccessViolationException error) { Console.WriteLine(error.ToString()); this.resultThumbnail = ReturnErrorThumbnail(0); webBrowser.Dispose(); this.parameters = null; this.url = null; this.webRequest = null; return; } } else { this.resultThumbnail = ReturnErrorThumbnail(0); this.parameters = null; this.url = null; this.webRequest = null; webBrowser.Dispose(); return; } if (this.parameters.browserAbsolute) { webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width; webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height; } Bitmap bitmap = new Bitmap(this.parameters.browserWidth, this.parameters.browserHeight); Rectangle bitmapRect = new Rectangle(0, 0, this.parameters.browserWidth, this.parameters.browserHeight); webBrowser.DrawToBitmap(bitmap, bitmapRect); if (this.parameters.picHeight == this.parameters.browserHeight && this.parameters.picWidth == this.parameters.browserWidth) { this.resultThumbnail = (Bitmap)bitmap.Clone(); this.parameters = null; this.url = null; this.webRequest = null; webBrowser.Dispose(); bitmap.Dispose(); return; } else { Bitmap thumbnail = new Bitmap(this.parameters.picWidth, this.parameters.picHeight); using (Graphics graphics = Graphics.FromImage(thumbnail)) { graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(bitmap, new Rectangle(0, 0, this.parameters.picWidth, this.parameters.picHeight), bitmapRect, GraphicsUnit.Pixel); } this.parameters = null; this.url = null; this.webRequest = null; webBrowser.Dispose(); resultThumbnail = (Bitmap)thumbnail.Clone(); bitmap.Dispose(); thumbnail.Dispose(); } } } private Bitmap ReturnErrorThumbnail(int codeError) { //обработка кода ошибки return new Bitmap(this.parameters.picWidth, this.parameters.picHeight); } } }
А вот и сама ошибка ->
System.AccessViolationException не обработано
Message=Попытка чтения или записи в защищенную память. Это часто свидетельствует о том, что другая память повреждена.
Source=mscorlib
StackTrace:
в System.StubHelpers.InterfaceMarshaler.ConvertToManaged(IntPtr pUnk, IntPtr itfMT, IntPtr classMT, Int32 flags)
в System.Windows.Forms.UnsafeNativeMethods.IHTMLDocument2.GetLocation()
в System.Windows.Forms.WebBrowser.get_Document()
в System.Windows.Forms.WebBrowser.get_ReadyState()
в WebShotServer.Classes.Thumbnail.GetThumbnail() в D:\!Work\WEBSHOT\WebShot\WebShotServer\Classes\Thumbnail.cs:строка 117
в System.Threading.ThreadHelper.ThreadStart_Context(Object state)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
в System.Threading.ThreadHelper.ThreadStart()
InnerException:
15 августа 2011 г. 7:56
Ответы
-
> А сколько итераций вы ждали?
~300. видимо, повезло. ошибка появилась после перезагрузки системы.
значит код надо переделать так, чтобы в потоке был messageloop и вместо проверки ReadyState перехватывать DocumentCompleted:using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication9 { using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using System.Runtime.InteropServices; namespace WebBrowserInConsoleForMSDN { class Program { static void Main(string[] args) { int i = 0; while (true) { for (int j = 0; j < 5; j++) { Thread thread = new Thread(Start); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Console.WriteLine(i++); } Thread.Sleep(600); } } static void Start() { var ac = new ApplicationContext(); Application.Idle += delegate { Thumbnail thumbnail = new Thumbnail(ac); thumbnail.GetThumbnail(); }; Application.Run(ac); } } public class Thumbnail { [DllImport("user32.dll")] static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr hdc, uint drawingOptions); public Thumbnail(ApplicationContext ac) { _Context = ac; } private bool _Complete = false; private ApplicationContext _Context; private string url = "http://localhost"; public void GetThumbnail() { using (WebBrowser webBrowser = new WebBrowser()) { webBrowser.Size = new Size(400, 400); webBrowser.ScrollBarsEnabled = false; webBrowser.ScriptErrorsSuppressed = true; webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted); webBrowser.Navigate(url); while (!_Complete) Application.DoEvents(); _Context.ExitThread(); } } void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { var webBrowser = (WebBrowser)sender; using (Image img = new Bitmap(webBrowser.Width, webBrowser.Height)) { // получить изображение uint flags = 0x04 /*CLIENT*/ | 0x10 /*NONCLIENT*/ | 0x08 /*ERASEBKGND*/; using (Graphics g = Graphics.FromImage(img)) SendMessage(webBrowser.Handle, 0x317 /*WM_PRINT*/, g.GetHdc(), flags); // ... } Console.WriteLine("\tОК"); _Complete = true; } } } }
17 августа 2011 г. 8:18 -
в класс Thumbnail надо добавить свойство Result, событие ResultChanged и изменить метод webBrowser_DocumentCompleted:также в методе, в котором создается Thumbnail, надо подключить обработчик к событию Thumbnail.ResultChanged.
public event EventHandler ResultChanged = delegate { }; public Image Result { get; private set; } void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { var webBrowser = (WebBrowser)sender; // получить изображение Image img = new Bitmap(webBrowser.Width, webBrowser.Height); uint flags = 0x04 /*CLIENT*/ | 0x10 /*NONCLIENT*/ | 0x08 /*ERASEBKGND*/; using (Graphics g = Graphics.FromImage(img)) SendMessage(webBrowser.Handle, 0x317 /*WM_PRINT*/, g.GetHdc(), flags); // сохранить результат и уведомить подписчиков. this.Result = img; this.ResultChanged(this, EventArgs.Empty); Console.WriteLine("\tОК"); _Complete = true; }
если Thumbnail'ов много, то их хранить в коллекции и удалять из коллекции в момент события Thumbnail.ResultChanged.19 августа 2011 г. 14:40
Все ответы
-
вместо while ((webBrowser.ReadyState.ToString() != "Complete")) { ... Application.DoEvents(); }
попробуйте так:
while ((webBrowser.ReadyState != WebBrowserReadyState.Complete)) {
Application.DoEvents(); // WebBrowser выполнит все что ему требуется до своего Dispose, если timer остановлен
if (timer.Enabled == false) ...
}15 августа 2011 г. 8:52 -
вместо while ((webBrowser.ReadyState.ToString() != "Complete")) { ... Application.DoEvents(); }
попробуйте так:
while ((webBrowser.ReadyState != WebBrowserReadyState.Complete)) {
Application.DoEvents(); // WebBrowser выполнит все что ему требуется до своего Dispose, если timer остановлен
if (timer.Enabled == false) ...
}Не без разницы,я побывал все равно вылетает исключение.
Пробовал вообще без таймера, та же самая история.
Мне кажется что тут проблема с приватностью доступа и потоками, но что именно я не знаю.
Забыл уточнить ошибка возникает при чтении свойства ReadyState15 августа 2011 г. 9:07 -
какая версия .NET? у меня 4.0 работает нормально. ниже фрагмент на почти C# (просто VS далеко) ... думаю, будет понятно:
var ev = new Threading.ManualResetEvent(false)
var thumbnail = null
void run() { // метод должен выполняться в отдельном STA-потоке
var wb = new WebBrowser()
// подключаем обработчик; вызывается раньше чем завершится while;
wb.DocumentCompleted += {
if e.Url = wb.Url then // т.к. событие возникает для каждого iframe
// здесь создаем thumbnail
thumbnail = ...
ev.Set() // отпускаем вызвавший поток
}
// загружаем страницу
wb.Navigate(url)
// ждем
while wb.IsBusy || wb.ReadyState != WebBrowserReadyState.Complete {
Application.DoEvents() }
}
// ждем вызов ev.Set(); текущий поток блокируется
var res = ev.WaitOne(1000) // блокировка снимается, если ev.Set() не будет вызван в течение 1 сек.
if res = true {
// скриншот получен вовремя
thumbnail ...
}15 августа 2011 г. 15:25 -
какая версия .NET? у меня 4.0 работает нормально. ниже фрагмент на почти C# (просто VS далеко) ... думаю, будет понятно:
var ev = new Threading.ManualResetEvent(false)
var thumbnail = null
void run() { // метод должен выполняться в отдельном STA-потоке
var wb = new WebBrowser()
// подключаем обработчик; вызывается раньше чем завершится while;
wb.DocumentCompleted += {
if e.Url = wb.Url then // т.к. событие возникает для каждого iframe
// здесь создаем thumbnail
thumbnail = ...
ev.Set() // отпускаем вызвавший поток
}
// загружаем страницу
wb.Navigate(url)
// ждем
while wb.IsBusy || wb.ReadyState != WebBrowserReadyState.Complete {
Application.DoEvents() }
}
// ждем вызов ev.Set(); текущий поток блокируется
var res = ev.WaitOne(1000) // блокировка снимается, если ev.Set() не будет вызван в течение 1 сек.
if res = true {
// скриншот получен вовремя
thumbnail ...
}
15 августа 2011 г. 16:17 -
> это у вас mono?
это ленивый-C# :) просто набирал прямо здесь в редакторе.
15 августа 2011 г. 16:28 -
Не та же самая история.15 августа 2011 г. 16:40
-
попробуйте создать отдельный проект. обычное windows приложение. в котором будет только код загрузки webbrowser. без permissions.
а еще лучше вначале проверить работает ли загрузка в webbrowser, когда он находится на form.
15 августа 2011 г. 17:39 -
попробуйте создать отдельный проект. обычное windows приложение. в котором будет только код загрузки webbrowser. без permissions.
а еще лучше вначале проверить работает ли загрузка в webbrowser, когда он находится на form.
Ну собственно сделал простенькое консольное приложение для тестинга - запускается бесконечный цикл, в каждой итерации запускаться 5 потоков для загрузки webBrowser, в итоге ошибка осталась, на все том же ReadyState :(
Вот код ->
using System; using System.Drawing; using System.Threading; using System.Windows.Forms; namespace WebBrowserInConsoleForMSDN { class Program { static void Main(string[] args) { int i = 0; while (true) { for (int j = 0; j < 5; j++) { Thumbnail thumbnail = new Thumbnail(); Thread thread = new Thread(thumbnail.GetThumbnail); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Console.WriteLine(i++.ToString()); } Thread.Sleep(300); } } } public class Thumbnail { private string url = "http://localhost:3012/Default.aspx"; public void GetThumbnail() { WebBrowser webBrowser = new WebBrowser(); webBrowser.Size = new Size(400, 400); webBrowser.ScrollBarsEnabled = false; webBrowser.ScriptErrorsSuppressed = true; webBrowser.Navigate(url); while ((webBrowser.ReadyState != WebBrowserReadyState.Complete)) { Application.DoEvents(); } Bitmap bitmap = new Bitmap(400, 400); Rectangle bitmapRect = new Rectangle(0, 0, 400, 400); webBrowser.DrawToBitmap(bitmap, bitmapRect); Console.WriteLine("\tОК"); } } }
16 августа 2011 г. 9:48 -
только что проверил. работает без ошибок в Microsoft Visual Studio 2010 Version 10.0.40219.1 SP1Rel, Microsoft .NET Framework Version 4.0.30319 SP1Rel, все Hotfix установлены. браузер IE9 version 9.0.8112.16421
16 августа 2011 г. 12:54 -
только что проверил. работает без ошибок в Microsoft Visual Studio 2010 Version 10.0.40219.1 SP1Rel, Microsoft .NET Framework Version 4.0.30319 SP1Rel, все Hotfix установлены. браузер IE9 version 9.0.8112.16421
А сколько итераций вы ждали?
Я два раза запускал, первый раз на 273 вылетела, второй раз на 3564.
И виснет не только у меня, во первых пробывал сам на других машинах + у людей в интернете тоже такие проблемы.
У меня Win7 x64 sp1, студия, фраймворк и IE такие же.
16 августа 2011 г. 19:10 -
> А сколько итераций вы ждали?
~300. видимо, повезло. ошибка появилась после перезагрузки системы.
значит код надо переделать так, чтобы в потоке был messageloop и вместо проверки ReadyState перехватывать DocumentCompleted:using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication9 { using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using System.Runtime.InteropServices; namespace WebBrowserInConsoleForMSDN { class Program { static void Main(string[] args) { int i = 0; while (true) { for (int j = 0; j < 5; j++) { Thread thread = new Thread(Start); thread.SetApartmentState(ApartmentState.STA); thread.Start(); Console.WriteLine(i++); } Thread.Sleep(600); } } static void Start() { var ac = new ApplicationContext(); Application.Idle += delegate { Thumbnail thumbnail = new Thumbnail(ac); thumbnail.GetThumbnail(); }; Application.Run(ac); } } public class Thumbnail { [DllImport("user32.dll")] static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr hdc, uint drawingOptions); public Thumbnail(ApplicationContext ac) { _Context = ac; } private bool _Complete = false; private ApplicationContext _Context; private string url = "http://localhost"; public void GetThumbnail() { using (WebBrowser webBrowser = new WebBrowser()) { webBrowser.Size = new Size(400, 400); webBrowser.ScrollBarsEnabled = false; webBrowser.ScriptErrorsSuppressed = true; webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted); webBrowser.Navigate(url); while (!_Complete) Application.DoEvents(); _Context.ExitThread(); } } void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { var webBrowser = (WebBrowser)sender; using (Image img = new Bitmap(webBrowser.Width, webBrowser.Height)) { // получить изображение uint flags = 0x04 /*CLIENT*/ | 0x10 /*NONCLIENT*/ | 0x08 /*ERASEBKGND*/; using (Graphics g = Graphics.FromImage(img)) SendMessage(webBrowser.Handle, 0x317 /*WM_PRINT*/, g.GetHdc(), flags); // ... } Console.WriteLine("\tОК"); _Complete = true; } } } }
17 августа 2011 г. 8:18 -
Протестировал, все работает отлично :)
Спасибо вам огромное за помощь!
Надо почитать про messageLoop, можете что нибудь посоветовать, желательно на русском?
17 августа 2011 г. 19:57 -
> Надо почитать про messageLoop
http://www.gotdotnet.ru/blogs/msdn/6349/ - Простая и безопасная реализация многопоточности в Windows Forms. (есть схема Window message queue и потоков).
http://msdn.microsoft.com/ru-ru/magazine/cc163417.aspx - Обработка сообщения в консольных приложениях.17 августа 2011 г. 23:38 -
Извинить за тупой вопрос, но как тогда добраться до изображение, из последнего листинга, в главный метод Main???19 августа 2011 г. 10:58
-
в класс Thumbnail надо добавить свойство Result, событие ResultChanged и изменить метод webBrowser_DocumentCompleted:также в методе, в котором создается Thumbnail, надо подключить обработчик к событию Thumbnail.ResultChanged.
public event EventHandler ResultChanged = delegate { }; public Image Result { get; private set; } void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { var webBrowser = (WebBrowser)sender; // получить изображение Image img = new Bitmap(webBrowser.Width, webBrowser.Height); uint flags = 0x04 /*CLIENT*/ | 0x10 /*NONCLIENT*/ | 0x08 /*ERASEBKGND*/; using (Graphics g = Graphics.FromImage(img)) SendMessage(webBrowser.Handle, 0x317 /*WM_PRINT*/, g.GetHdc(), flags); // сохранить результат и уведомить подписчиков. this.Result = img; this.ResultChanged(this, EventArgs.Empty); Console.WriteLine("\tОК"); _Complete = true; }
если Thumbnail'ов много, то их хранить в коллекции и удалять из коллекции в момент события Thumbnail.ResultChanged.19 августа 2011 г. 14:40 -
Ага, все понял, еще раз спасибо :)19 августа 2011 г. 19:55