لإجراء بحث حول تشغيل البرامج ونظام التشغيل ، هناك الكثير من الأدوات المختلفة. الأجهزة الافتراضية ، IDEs ، دفاتر الملاحظات الذكية ، IDA ، الرادار ، المحررين السداسي ، المحررين pe ، وحتى أكثر من مائة من خدمات Sysinternals - كل هذا يتم لتسهيل العديد من العمليات الروتينية. ولكن في بعض الأحيان تأتي لحظة عندما تدرك أنه من بين كل هذا التنوع ، فإنك تفتقد إلى أداة صغيرة من شأنها أن تقوم ببساطة بعمل عادي وبسيط. يمكنك كتابة نصوص بايثون أو باورشيل على ركبتك ، ولكن في كثير من الأحيان لا يمكنك النظر إلى هذه الحرف اليدوية دون دموع ومشاركتها مع زملائك.
في الآونة الأخيرة ، جاء لي هذا الوضع مرة أخرى. وقررت أن الوقت قد حان لأخذ وكتابة أداة مرتبة. سأخبرنا عن الأداة المساعدة في إحدى المقالات التالية ، لكنني سأخبرنا عن إحدى المشاكل أثناء التطوير الآن.
يتجلى الخطأ كما يلي: إذا قمت
بإدراج العديد من أسطر النص في عنصر التحكم TextBox القياسي ، فإن الاستدعاءات إلى وظيفة
GetLineText () بدءًا من فهرس معين ستعيد أسطرًا غير صحيحة.
الشيء الخاطئ هو أنه على الرغم من أن الأسطر ستكون من النص المثبت ، ولكنها تقع في مكان أبعد ، في الواقع فإن
GetLineText () سيتخطى ببساطة بعض الأسطر. يظهر الخطأ مع عدد كبير جدًا من الأسطر. لذا التقيت بها - حاولت عرض 25 ميغابايت من النص في TextBox. كشف العمل مع السطور الأخيرة عن تأثير غير متوقع.
تقترح Google
أن الخطأ موجود منذ عام 2011 وأن Microsoft ليست في عجلة من أمرها لإصلاح شيء ما.
مثال
لا توجد متطلبات معينة لإصدار .NET. نقوم بإنشاء مشروع WPF قياسي ونملأ الملفات مثل هذا:
MainWindow.xaml
<Window x:Class="wpf_textbox.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:wpf_textbox" mc:Ignorable="d" Title="WTF, WPF?" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="20"/> <RowDefinition Height="20"/> </Grid.RowDefinitions> <TextBox Grid.Row="0" Margin="5" Name="txt" AcceptsReturn="True" AcceptsTab="True" /> <Button Grid.Row="1" Content="Fire 1!" Click="btn_OnClick" /> <Button Grid.Row="2" Content="Fire 2!" Click="btn2_OnClick" /> </Grid> </Window>
MainWindow.cs (التخطي باستخدام ومساحة الاسم)
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void btn_OnClick(object sender, RoutedEventArgs e) { var sb = new StringBuilder(); for (int i = 0; i < 90009; i++) sb.AppendLine($"{i}"); txt.Text = sb.ToString(); } private void btn2_OnClick(object sender, RoutedEventArgs e) { var sb = new StringBuilder(); for (var i = 1; i < 7; i++) sb.AppendLine("req: " + 150 * i + ", get: " + txt.GetLineText(150 * i).Trim()); for (var i = 1; i < 7; i++) sb.AppendLine("req: " + 15000 * i + ", get: " + txt.GetLineText(15000 * i).Trim()); txt.Text = sb.ToString(); } }
يتكون التطبيق من TextBox وزرين. انقر أولاً على "Fire 1!" (املأ TextBox بالأرقام) ، ثم "Fire 2!" (ستطلب الأسطر بالأرقام وطباعتها).
النتيجة المتوقعة:
مسا: 150 ، الحصول على: 150
مسا: 300 ، احصل على: 300
مسا: 450 ، الحصول على: 450
مسا: 600 ، احصل على: 600
مسا: 750 ، الحصول على: 750
مسا: 900 ، الحصول على: 900
مسا: 15000 ، الحصول على: 15000
مسا: 30000 ، الحصول على: 30000
مسا: 45000 ، الحصول على: 45000
مسا: 60000 ، الحصول على: 60000
مسا: 75000 ، احصل على: 75000
مسا: 90000 ، الحصول على: 90000
الواقع:

يمكن ملاحظة أنه بالنسبة للمؤشرات الأقل من 1000 - كل شيء على ما يرام ، ولكن بالنسبة إلى 15000 كبيرة - بدأت التحولات. وكلما زاد.
اكتشف الخلل
نكتشف جزء من المُحلل المسؤول عن عرض كود مصدر .NET والفئة الخاصة "موسع الفرص والقيود المفرطة القائمة على التأمل".
محدد القدرة على الانعكاس ومحدد public static class ReflectionExtensions { public static T GetFieldValue<T>(this object obj, string name) { var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var field = obj.GetType().GetField(name, bindingFlags); if (field == null) field = obj.GetType().BaseType.GetField(name, bindingFlags); return (T)field?.GetValue(obj); } public static object InvokeMethod(this object obj, string methodName, params object[] methodParams) { var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { }; var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; MethodInfo method = null; var type = obj.GetType(); while (method == null && type != null) { method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null); var intfs = type.GetInterfaces(); if (method != null) break; foreach (var intf in intfs) { method = intf.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null); if (method != null) break; } type = type.BaseType; } return method?.Invoke(obj, methodParams); } }
تجريبيا ، نثبت أنه في مثال محدد ، تبدأ المشكلة في منطقة الخط 8510. إذا طلبت
txt.GetLineText (8510) ، فسيتم إرجاع "8510". لـ 8511-8511 و 8512- فجأة 8513.
نحن ننظر إلى تطبيق
GetLineText () على TextBox:
نتخطى الشيكات في الأسطر الأولى ونرى المكالمة
GetStartPositionOfLine () . يبدو أن المشكلة يجب أن تكون في هذه الوظيفة ، لأنه بالنسبة للخط الخاطئ يجب أن يعود الموضع الخاطئ لبداية السطر.
ندعو في رمزنا:
var o00 = txt.InvokeMethod("GetStartPositionOfLine", 8510); var o01 = txt.InvokeMethod("GetStartPositionOfLine", 8511); var o02 = txt.InvokeMethod("GetStartPositionOfLine", 8512);
والحقيقة - تتم الإشارة إلى إزاحة الكائن الأول (بداية السطر 8510th) على أنها 49950 حرفًا ، للعنصر الثاني - 49956 ، والثالث - 49968. بين أول حرفين 6 أحرف ، وبين 12 حرفًا تاليًا - هذا هو السطر المفقود.
انتقل إلى
GetStartPositionOfLine () :

مرة أخرى ، نتخطى اختبارات البداية وننظر في الإجراءات الحقيقية. أولاً ، يتم حساب النقطة ، والتي يجب أن تذهب إلى السطر برقم
lineIndex . يتم أخذ ارتفاع جميع الخطوط وإضافة نصف ارتفاع الخط للوصول إلى مركزه.
نحن لا ننظر إلى هذا.
عموديًا و
هذا و
أفقيًا - إنه أصفار.
نعتبر في الكود الخاص بنا:
var lineHeight = (double) txt.InvokeMethod("GetLineHeight", null); var y0 = lineHeight * (double)8510 + lineHeight / 2.0 - txt.VerticalOffset; var y1 = lineHeight * (double)8511 + lineHeight / 2.0 - txt.VerticalOffset; var y2 = lineHeight * (double)8512 + lineHeight / 2.0 - txt.VerticalOffset;
القيم معقولة ، مرتبطة بالمنطق ، كل شيء في محله. نذهب أبعد من ذلك على طول رمز
GetStartPositionOfLine () - نحن مهتمون بالسطر ذي المغزى التالي (الأول داخل الشرط) ، والذي يبدو وكأنه تمساح وينتهي بمكالمة
GetTextPositionFromPoint () .
نفتح التحديات ونخرجها من خلال التفكير. يرجى ملاحظة أن بعض الواجهات غير متاحة لنا بسبب قيود الرؤية ، لذلك علينا الرجوع إليها باستخدام نفس التأمل.
var renderScope = (txt.GetFieldValue<FrameworkElement>("_renderScope") as IServiceProvider);
تُظهر الكائنات الناتجة نفس الإزاحات - 49950 ، 49956 ، 49568. نتعمق في تطبيق
GetTextPositionFromPoint () داخل TextBoxView.

في ، يبدو
GetLineIndexFromPoint () واعدًا. ادخل الرمز الخاص بك.
var o20 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y0), true); var o21 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y1), true); var o22 = textView.InvokeMethod("GetLineIndexFromPoint", new Point(-txt.HorizontalOffset, y2), true);
نحصل على 8510 و 8511 و 8513 - البنغو! للتنفيذ:

حتى بالعين المجردة ، من الواضح أن هذا بحث ثنائي.
_lineMetrics - قائمة بخصائص السلسلة (البداية ، الطول ، عرض الحدود). أفرك فرحًا بأقلامي - ظننت أنه غالبًا ما ينسون لصق
1+ في مكان ما أو وضع
> بدلاً من
> = . انسخ الوظيفة إلى الكود وقم بتصحيحها. نظرًا للطبيعة المغلقة لأنواع
_lineMetrics ،
فإننا نخرجها من خلال الانعكاسات ،
_lineHeight التي حصلنا عليها سابقًا. المجموع:
var lm = textView.GetFieldValue<object>("_lineMetrics"); var c = (int)lm.InvokeMethod("get_Count"); var lineMetrics = new List<Tuple<int,int,int,double>>(); for (var i = 0; i < c; i++) { var arr_o = lm.InvokeMethod("get_Item", i); var contLength = arr_o.GetFieldValue<int>("_contentLength"); var length = arr_o.GetFieldValue<int>("_length"); var offset = arr_o.GetFieldValue<int>("_offset"); var width = arr_o.GetFieldValue<double>("_width"); lineMetrics.Add(new Tuple<int, int, int, double>(contLength, length, offset, width)); } var o30 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y0), true); var o31 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y1), true); var o32 = GetLineIndexFromPoint(lineMetrics, lineHeight, new Point(-txt.HorizontalOffset, y2), true); private int GetLineIndexFromPoint(List<Tuple<int, int, int, double>> lm, double _lineHeight, Point point, bool snapToText) { if (point.Y < 0.0) return !snapToText ? -1 : 0; if (point.Y >= _lineHeight * (double)lm.Count) { if (!snapToText) return -1; return lm.Count - 1; } int index = -1; int num1 = 0; int num2 = lm.Count; while (num1 < num2) { index = num1 + (num2 - num1) / 2; var lineMetric = lm[index]; double num3 = _lineHeight * (double)index; if (point.Y < num3) num2 = index; else if (point.Y >= num3 + _lineHeight) { num1 = index + 1; } else { if (!snapToText && (point.X < 0.0 || point.X >= lineMetric.Item4)) { index = -1; break; } break; } } if (num1 >= num2) return -1; return index; }
نحن لا نصل إلى التصحيح. o30 و o31 و o32 هي 8510 و 8511 و 8512 على التوالي. مثل ما ينبغي أن تكون! لكن o20 و o21 و o22 لا يتفقان معهم. كيف ذلك؟ لم نغير الرمز تقريبًا. تقريبا؟ وهنا تأتي البصيرة.
var lh = textView.GetFieldValue<double>("_lineHeight");

هذا هو السبب - الفرق
0.0009375 . علاوة على ذلك ، إذا قدرنا تراكم الأخطاء - نضرب في 8511 ، نحصل على 7.9790625. هذا هو فقط نصف خط الطول ، وبالتالي ، عند حساب الإحداثيات ، تطير النقطة خارج الخط المطلوب وتقع في اليوم التالي. تم حساب نفس المتغير (في المعنى) بطريقتين مختلفتين ، وفجأة ، لم يتطابق.
على هذا قررت التوقف. من الممكن أن نصل إلى حقيقة لماذا كان ارتفاع العمود مختلفًا ، لكني لا أرى الكثير من النقاط. من المشكوك فيه أن تقوم Microsoft بإصلاح ذلك ، لذلك ننظر إلى العكازات للعمل. Reflection-
crutch - قم بتعيين
_lineHeigh الصحيح
في مكان واحد أو آخر. يبدو غبيًا ، وربما يكون بطيئًا وعلى الأرجح غير موثوق به. أو يمكنك الحفاظ على مجموعة الخطوط الخاصة بك ، بالتوازي مع TextBox ، وأخذ الخطوط منه ، حيث إن الحصول على رقم السطر في موضع المؤشر يعمل بشكل صحيح.
الخلاصة
من المبرمجين المبتدئين يمكنك غالبًا سماع شيء عن الأخطاء في المترجم أو المكونات القياسية. في الواقع ، إنها ليست شائعة جدًا ، ولكن لا يزال أحد في مأمن منها. لا تخف من النظر داخل الأداة التي تحتاجها - إنها مثيرة ومثيرة للاهتمام.
اكتب كود جيد!
مقالات مدونة أخرى
→
التعلم الآلي في الأمن الهجوميلا توجد سيارة يمكن أن تحل محل لي. Muhaha ha. آمل ذلك.
←
مكان إدراج علامة الاقتباس في IPv6الرجال يعرفون أين وماذا يشقونه لجعله جيدًا. بعد هذه الكلمات ، سيستبدلونني بروبوت بالتأكيد. الأربعاء! UHF!