Настав час виконати свою обіцянку і поговорити про використання “некерованиго” коду з .NET середовища. Хочу сказати зразу що для роботи з некерованим кодом існує зразу 3 методи – це procedure invoke (p/invoke), com interop і unsafe, але радіти тут не потрібно, тому, що в кожного з них є свої переваги і недоліки, сьогодні я попробую розглянути основний найчастіше використовуваний P/Invoke, інформацю по інших я думаю ви без проблем знайдете в інтернеті.
Чому працювати з unmanaged кодом з C# так важко?
Все діло в тому що з самого початку створення керованого(.NET) коду, на нього були покладені певні обовязки такі як управління памяттю та вивільненням ресурсів, відсутність прямої адресації, потенційно небезпечних вказівників і тд. Це все зробило його набагто легшим в використанні, але й добавило певні недоліки(менша швидкодія, необхідність встановленого .NET Framework…), тому для нормальної роботи некерованого коду його необхідно подавати таким чином щоб він відповідав вимогам керованого. При використанні некерованого коду є певні недоліки – це те, що іноді для використання некерованого коду необхідно прописати доступність операції яку він буде виконувати в маніфесті програми, і якщо в вашому коді є обмежання по роботі з певними ресурсами, використовуючи функцію з некерованого коду ці обмежання можна легко обійти.
Трохи про маршалинг.
Кожному типу змінної в некерованому коді відповідає певна змінна з керованого коду, але прямо вони не є сумісними, тому коли ми передаємо в метод стрічку типу string компілятор автоматично перетворює її(маршалить) на LPTSTR (TCHAR*), але це можна змінити. Дуже хороша стаття по темі маршалінгу і роботи з некерованим кодом є тут а я розгляну лише те що вам найчастіше може знадобитись.
Метод перший P/Invoke.
P/Invoke це спеціальний механізм для виклику функцій з dll. Для цього нам необхідно створити статичний аналог даної функції з тим же набором параметрів і помітити його атрибутом [DLLImport(“Назвафайлу.dll”)] .
using System.Runtime.InteropServices;public class Win32{[DllImport("User32.Dll")]public static extern void SetWindowText(IntPtr hwnd, String lpString);}
В даному випадку ми викликаємо метод SetWindowText який приймає змінну hwnd типу IntPtr, та стрічку lpString. Тип IntPtr завжди відповідає змінним типу HWND і HMENU, він містить посилання на обєкт для якого ми хочемо виконати дану функцію, тобто якщо ми хочемо викликати цю функцію для поточного вікна то ми повинні в якості параметра передати this.Handle, якщо інший обєкт то відповідно ссилку на його “хендл”. Атрибут [DLLImport(“Назвафайлу.dll”)] служить для того щоб компілятор знав в якому саме файлі шукати необхідну нам функцію.
Наступним кроком розглянемо виклик функції яка повертає значення типу string – GetWindowText, в неї є вихідний параметр типу char* і позамовчуванню він нам буде маршалитись на System.String. Та тут виникає одна проблема System.String в нас є незмінним і після його ініціалізації дані в ньому змінити не можна, тому для того щоб мати змогу приймати повернений з функції текст нам необхідно використати класс StringBuilder.
using System.Text; // для StringBuilder[DllImport("user32.dll")]public static extern int GetWindowText(IntPtr hwnd,StringBuilder buf, int nMaxCount);
Тип маршалингу для StringBuilder теж LPTSTR тому ми без проблем зможемо працювати з ним.
StringBuilder sTitleBar = new StringBuilder(255);GetWindowText(this.Handle, sTitleBar, sTitleBar.Capacity);MessageBox.Show(sTitleBar.ToString());
В вище наведеному коді ми створюємо новий обєкт StringBuilder – sTitleBar з розмірністю 255. Викликаємо функцію GetWindowText та передаємо в неї параметри: посилання не поточне вікно, змінна для отримання результату виконання, та вказуємо розмір буферу, після чого виводимо результат на екран.
Зміна типу для маршалингу.
Іноді буває необхідно викликати метод передавши йому в якості параметра стрічку в вигляді ANSI а не Unicode який використовується по замовчуванню. Для цього нам необхідно задати відповідний атрибут, розглянемо детальніше на наступному прикладі:
[DllImport("user32.dll")]public static extern int GetClassName(IntPtr hwnd, [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, int nMaxCount);
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf – вказує компілятору що змінна буде передана в вигляді символів ANSI а не Unicode.
Виклик функцій з структурами в якості параметрів.
Для прикладу розглянемо фінкцію GetWindowRect, вона в якості одного з параметру примає структуру в яку записує координати вікна. Щоб передати їй цю структуру ми повинні позначити її атрибутом StructLayout.
[StructLayout(LayoutKind.Sequential)]public struct RECT{public int left ;public int top;public int right;public int bottom;}[DllImport("user32.dll")]
public static extern int GetWindowRect(IntPtr hwnd, ref RECT rc);
Структура в даному випадку передана по ссилці для того щоб функція модифікувала саме ваш обєкт а не його копію. Для використання вище наведеного коду достатньо зробити наступне:
RECT rc = new RECT();
GetWindowRect(this.Handle, ref rc);
Після його виконання наша структура буде заповнена відповідними значеннями.
Висновок, працювати з некерованим кодом важко і незвично але цілком підсилу любому, головне трохи бажання і часу для ознайомлення з основними правилами. Надалі я попробую розширити дану тему, та доповнити новими постами, та через навантеженість на роботі думаю що це буде ще не скоро.
Всім удачі!
"існує зразу 3 методи" - і які то?
ReplyDeleteСьогодні обовязково це виправлю
ReplyDeleteПС. Шкода що не зможу прийти на твій мітінг по ДДД в нас в тімі накладочка получається.