[.NET, C#, Разработка под Linux, Разработка под Windows] Путешествие в unmanaged code: туда и обратно
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Высокоуровневые языки программирования популярны, но существуют области, в которых придется использовать неуправляемые реализации библиотек. Это могут быть вызов специфических функций ОС, низкоуровневый доступ к устройствам, необходимость быстродействия в алгоритмах и другие. Под катом я расскажу, с чем можно столкнуться во время путешествия в unmanaged code и что стоит взять с собой.
Вы стоите на пороге своей уютной IDE, и вас совсем не тянет отправиться в мир исходного кода, где темно и ничего не понятно. Для успеха предприятия прежде всего необходимо разжиться картой – сойдет описание заголовков библиотеки, а лучше иметь полноценную документацию. Обычно она выглядит так:
...
#include <linux/netfilter_ipv4/ip_tables.h>
#include <libiptc/xtcshared.h>
#ifdef __cplusplus
extern "C" {
#endif
#define iptc_handle xtc_handle
#define ipt_chainlabel xt_chainlabel
#define IPTC_LABEL_ACCEPT "ACCEPT"
#define IPTC_LABEL_DROP "DROP"
#define IPTC_LABEL_QUEUE "QUEUE"
#define IPTC_LABEL_RETURN "RETURN"
/* Does this chain exist? */
int iptc_is_chain(const char *chain, struct xtc_handle *const handle);
/* Take a snapshot of the rules. Returns NULL on error. */
struct xtc_handle *iptc_init(const char *tablename);
/* Cleanup after iptc_init(). */
void iptc_free(struct xtc_handle *h);
...
Представим, что вам повезло, и документацияесть. Здесь описываются сигнатуры функций, используемые структуры, псевдонимы, а также указаны ссылки на другие используемые заголовки. Первый квест – найти библиотеку в ОС. Её название может отличаться от ожидаемого:
~$ find /usr/lib/x86_64-linux-gnu/ -maxdepth 1 -name 'libip*'
/usr/lib/x86_64-linux-gnu/libip6tc.so.0.1.0
/usr/lib/x86_64-linux-gnu/libip4tc.so
/usr/lib/x86_64-linux-gnu/libiptc.so.0
/usr/lib/x86_64-linux-gnu/libip4tc.so.0.1.0
/usr/lib/x86_64-linux-gnu/libip6tc.so.0
/usr/lib/x86_64-linux-gnu/libiptc.so.0.0.0
/usr/lib/x86_64-linux-gnu/libip4tc.so.0
/usr/lib/x86_64-linux-gnu/libiptc.so
/usr/lib/x86_64-linux-gnu/libip6tc.so
Цифровой суффикс означает разные версии библиотек. В общем случае нам требуется оригинал libip4tc.so. Можно заглянуть внутрь одним глазком и убедиться, что дело стоящее:
~$ nm -D /usr/lib/x86_64-linux-gnu/libip4tc.so
...
0000000000206230 D _edata
0000000000206240 B _end
U __errno_location
U fcntl
000000000000464c T _fini
U __fprintf_chk
U free
U getsockopt
w __gmon_start__
0000000000001440 T _init
0000000000003c80 T iptc_append_entry
0000000000003700 T iptc_builtin
0000000000004640 T iptc_check_entry
0000000000003100 T iptc_commit
0000000000002ff0 T iptc_create_chain
00000000000043f0 T iptc_delete_chain
...
Кажется, библиотека содержит то, что нам надо, и теперь самое время ткнуть в нее палкой. Для этого создадим манускрипт вызова неуправляемых функций:
public static class Libiptc4
{
/* Prototype: iptc_handle_t iptc_init(const char *tablename) */
[DllImport("libip4tc.so")]
public static extern IntPtr iptc_init(string tablename);
}
К этому моменту вы должны прокачать навык маршалинга: он поможет вам преобразовать данные на входе и выходе неуправляемых функций. Все значимые типы имеют преобразование по умолчанию в неуправляемые типы. В то же время можно обойтись только ссылками IntPtr. Например, указанную выше функцию можно вызвать иначе:
/* Prototype: iptc_handle_t iptc_init(const char *tablename) */
[DllImport("libip4tc.so")]
public static extern IntPtr iptc_init(IntPtr tblPtr);
...
var tblPtr = Marshal.StringToHGlobalAnsi("filter");
var _handle = Libiptc4.iptc_init_ptr(tblPtr);
Marshal.FreeHGlobal(tblPtr);
Разница заключается в первую очередь в необходимости самостоятельно очищать память. В то же время, по моим наблюдениям, строки, структуры и классы ведут себя более предсказуемо, если работать с ними через ссылки.Представим теперь, что путешествие привело нас к пещере горного тролля: нужно соорудить на вход неуправляемой функции сложный объект динамического размера. Прототип может выглядеть следующим образом:
struct ipt_entry {
struct ipt_ip ip;
/* Mark with fields that we care about. */
unsigned int nfcache;
/* Size of ipt_entry + matches */
__u16 target_offset;
/* Size of ipt_entry + matches + target */
__u16 next_offset;
/* Back pointer */
unsigned int comefrom;
/* Packet and byte counters. */
struct xt_counters counters;
/* The matches (if any), then the target. */
unsigned char elems[0];
};
Обратите внимание на поле unsigned char elems[0] прототипа. Если я не ошибаюсь, это указатель на байтовый массив переменной длины, и его не нужно явно указывать в реализации. В упрощенном виде наш объект устроен следующим образом:
*******************************************
* ip_entry *
* 112 bytes *
*******************************************
* matches *
* target_offset - 112 bytes *
*******************************************
* target *
* next_offset - target_offset - 112 bytes *
*******************************************
Динамическая часть объекта (matches и target) пристыковывается к заголовку ip_entry. Создание такого объекта разбивается на два этапа:
- Выделение памяти требуемого размера.
- Последовательная запись элементов в нужные участки памяти.
Чтобы вычислить размер объекта, необходимо сложить все составные части, имеющие фиксированную структуру. Реализация прототипа заголовка ipt_entry выглядит следующим образом:
[StructLayout(LayoutKind.Sequential)]
public struct IptEntry
{
public IptIp ip;
public uint nfcache;
public ushort target_offset;
public ushort next_offset;
public uint comefrom;
public IptCounters counters;
};
Размер реализации вычисляется как Marshal.SizeOf<IptEntry>()и равен 112 байт. Затем вычисляются размеры всех составных объектовmatches и target ( которые тоже могут быть динамическими). Нюанс: при работе с библиотекойlibiptc я столкнулся с требованием округлять размеры объектов в большую сторону по модулю 8 ( размер long), так что часть байт в хвосте объектов будет не востребована. Видимо, такой подход ускоряет чтение объектов. Функция выравнивания может выглядеть следующим образом:
static readonly int _WORDLEN = Marshal.SizeOf<long>();
public static int Align(int size)
{
return ((size + (_WORDLEN - 1)) & ~(_WORDLEN - 1));
}
После того как размер объекта вычислен, необходимо определить смещения в памяти entry.target_offset и entry.next_offset, выделить память и записать объекты:
IntPtr entryPtr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr<IptEntry>(entryPtr, entry, false);
Marshal.StructureToPtr<Match>(entryPtr + 112, match, false);
Чтение объекта происходит в обратном порядке: читаем заголовок, вычисляем смещение, читаем динамическую часть:
var entry = Marshal.PtrToStructure<IptEntry>(point);
var match = Marshal.PtrToStructure<Match>(point + 112)
Помимо структур, на вашем пути может встретиться такой зверь как union:
struct xt_entry_match {
union {
struct {
__u16 match_size;
/* Used by userspace */
char name[XT_EXTENSION_MAXNAMELEN];
__u8 revision;
} user;
struct {
__u16 match_size;
/* Used inside the kernel */
struct xt_match *match;
} kernel;
/* Total length */
__u16 match_size;
} u;
unsigned char data[0];
};
Union - это полиморфизм на уровне памяти: один и тот же участок может быть интерпретирован как разные типы. Для него нет прямого аналога в языке c#. Необходимо отдельно описывать реализацию для каждого прототипа в объединении. Прототипы могут иметь разную длину, однако память будет выделяться по верхней границе (как будто для самого большого размера). Подробнее о реализации объединений читайте в примере.В описании прототипа можно встретить псевдонимы для дефолтных значений:
#define XT_EXTENSION_MAXNAMELEN 29
...
char name [XT_EXTENSION_MAXNAMELEN]
Как вытащить дефолтное значение в управляемый код для меня осталось загадкой. Поэтому приходится искать значения на просторах header файлов и устанавливать вручную.Постарайтесь не злить магов, иначе получите проклятье в спину. Ваши ushort, uint и long будут хранить совсем не то, что ожидаете. Все дело в порядке байт. Привычным является прямой порядок: слева старший байт, справа меньший. Тем не менее при работе с сетевыми адресами и номерами портов может понадобиться обратный порядок байт. Для знаковых типов есть готовый метод. Для беззнаковых типов снимать проклятье придется самим:
byte [] convArray = BitConverter.GetBytes(value);
Array.Reverse(convArray);
ushort reverseEndian = BitConverter.ToUInt16(convArray,0);
ushort reverseEndian = (ushort)((value << 8) | (value >> 8));
В конце нашего путешествия пришло время поговорить о перехвате ошибок. При работе с unmanaged code он работает не совсем так как мы привыкли. Функции могут возвращать флаг успех/неудача, а номер ошибки будет содержать переменная errno. В явном виде ее нигде нет. Поэтому берем дополнительный квест, и добавляем к атрибуту настройку:
[DllImport("libip4tc.so", SetLastError = true)]
Теперь, если нас постигнет неудача, можно вызвать:
int errno = Marshal.GetLastWin32Error();
var errPtr = Libiptc4.iptc_strerror(errno);
string errStr = Marshal.PtrToStringAnsi(errPtr);
И это сработает даже в Linux c net.core (видимо, не успели переименовать/забили). Также необходимо обращать внимание на сборку библиотек: могут быть как кросс-платформенные, так и отдельно 32/64 битные версии, для многих библиотек есть готовые порты в Windows. Поэтому ошибки времени запуска чаще всего решаются выбором подходящей версии библиотеки.На этом наше путешествие подходит к концу. Добавлю, что использование низкоуровневых библиотек, выглядит сложным и непредсказуемым в плане успеха подходом. Отсутствие документации и сложность взаимодействия может отпугнуть. Однако иногда это – единственный способ достичь желаемого результата.
===========
Источник:
habr.com
===========
Похожие новости:
- [Open source, C++, Алгоритмы] Зачем PVS-Studio использует анализ потока данных: по мотивам интересной ошибки в Open Asset Import Library
- [Open source, C++, Алгоритмы] Why PVS-Studio Uses Data Flow Analysis: Based on Gripping Error in Open Asset Import Library
- [Open source, Виртуализация, Разработка под Linux, Openshift] 14 лучших практик разработки приложений на OpenShift
- [.NET, C#] Делаем фильтры «как в экселе» на ASP.NET Core
- [Информационная безопасность, Программирование, .NET, C#, Разработка под Windows] Как следить (наблюдать) за компьютером. Часть 1 — делаем скриншоты пользователей
- [Развитие стартапа, Управление персоналом, Карьера в IT-индустрии, Научно-популярное] Как узнать, насколько ты хороший лидер (перевод)
- [Программирование, .NET, Разработка под MacOS, Разработка под Windows] От WPF к Авалонии
- [Open source, Системное программирование, Разработка под Linux, Компьютерное железо, Процессоры] Опубликована ранняя версия неофициального эмулятора архитектуры Эльбрус 2000
- [Программирование, .NET, ASP, C#] Что из себя представляет класс Startup и Program.cs в ASP.NET Core (перевод)
- [Проектирование и рефакторинг, C#, Разработка под Windows] Проектирование на C# глазами первокурсника -> NotePad++ №6
Теги для поиска: #_.net, #_c#, #_razrabotka_pod_linux (Разработка под Linux), #_razrabotka_pod_windows (Разработка под Windows), #_unmanaged_code, #_library, #_dllimport, #_.net, #_c#, #_razrabotka_pod_linux (
Разработка под Linux
), #_razrabotka_pod_windows (
Разработка под Windows
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 09:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Высокоуровневые языки программирования популярны, но существуют области, в которых придется использовать неуправляемые реализации библиотек. Это могут быть вызов специфических функций ОС, низкоуровневый доступ к устройствам, необходимость быстродействия в алгоритмах и другие. Под катом я расскажу, с чем можно столкнуться во время путешествия в unmanaged code и что стоит взять с собой. Вы стоите на пороге своей уютной IDE, и вас совсем не тянет отправиться в мир исходного кода, где темно и ничего не понятно. Для успеха предприятия прежде всего необходимо разжиться картой – сойдет описание заголовков библиотеки, а лучше иметь полноценную документацию. Обычно она выглядит так: ...
#include <linux/netfilter_ipv4/ip_tables.h> #include <libiptc/xtcshared.h> #ifdef __cplusplus extern "C" { #endif #define iptc_handle xtc_handle #define ipt_chainlabel xt_chainlabel #define IPTC_LABEL_ACCEPT "ACCEPT" #define IPTC_LABEL_DROP "DROP" #define IPTC_LABEL_QUEUE "QUEUE" #define IPTC_LABEL_RETURN "RETURN" /* Does this chain exist? */ int iptc_is_chain(const char *chain, struct xtc_handle *const handle); /* Take a snapshot of the rules. Returns NULL on error. */ struct xtc_handle *iptc_init(const char *tablename); /* Cleanup after iptc_init(). */ void iptc_free(struct xtc_handle *h); ... ~$ find /usr/lib/x86_64-linux-gnu/ -maxdepth 1 -name 'libip*'
/usr/lib/x86_64-linux-gnu/libip6tc.so.0.1.0 /usr/lib/x86_64-linux-gnu/libip4tc.so /usr/lib/x86_64-linux-gnu/libiptc.so.0 /usr/lib/x86_64-linux-gnu/libip4tc.so.0.1.0 /usr/lib/x86_64-linux-gnu/libip6tc.so.0 /usr/lib/x86_64-linux-gnu/libiptc.so.0.0.0 /usr/lib/x86_64-linux-gnu/libip4tc.so.0 /usr/lib/x86_64-linux-gnu/libiptc.so /usr/lib/x86_64-linux-gnu/libip6tc.so ~$ nm -D /usr/lib/x86_64-linux-gnu/libip4tc.so
... 0000000000206230 D _edata 0000000000206240 B _end U __errno_location U fcntl 000000000000464c T _fini U __fprintf_chk U free U getsockopt w __gmon_start__ 0000000000001440 T _init 0000000000003c80 T iptc_append_entry 0000000000003700 T iptc_builtin 0000000000004640 T iptc_check_entry 0000000000003100 T iptc_commit 0000000000002ff0 T iptc_create_chain 00000000000043f0 T iptc_delete_chain ... public static class Libiptc4
{ /* Prototype: iptc_handle_t iptc_init(const char *tablename) */ [DllImport("libip4tc.so")] public static extern IntPtr iptc_init(string tablename); } /* Prototype: iptc_handle_t iptc_init(const char *tablename) */
[DllImport("libip4tc.so")] public static extern IntPtr iptc_init(IntPtr tblPtr); ... var tblPtr = Marshal.StringToHGlobalAnsi("filter"); var _handle = Libiptc4.iptc_init_ptr(tblPtr); Marshal.FreeHGlobal(tblPtr); struct ipt_entry {
struct ipt_ip ip; /* Mark with fields that we care about. */ unsigned int nfcache; /* Size of ipt_entry + matches */ __u16 target_offset; /* Size of ipt_entry + matches + target */ __u16 next_offset; /* Back pointer */ unsigned int comefrom; /* Packet and byte counters. */ struct xt_counters counters; /* The matches (if any), then the target. */ unsigned char elems[0]; }; *******************************************
* ip_entry * * 112 bytes * ******************************************* * matches * * target_offset - 112 bytes * ******************************************* * target * * next_offset - target_offset - 112 bytes * *******************************************
[StructLayout(LayoutKind.Sequential)]
public struct IptEntry { public IptIp ip; public uint nfcache; public ushort target_offset; public ushort next_offset; public uint comefrom; public IptCounters counters; }; static readonly int _WORDLEN = Marshal.SizeOf<long>();
public static int Align(int size) { return ((size + (_WORDLEN - 1)) & ~(_WORDLEN - 1)); } IntPtr entryPtr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr<IptEntry>(entryPtr, entry, false); Marshal.StructureToPtr<Match>(entryPtr + 112, match, false); var entry = Marshal.PtrToStructure<IptEntry>(point);
var match = Marshal.PtrToStructure<Match>(point + 112) struct xt_entry_match {
union { struct { __u16 match_size; /* Used by userspace */ char name[XT_EXTENSION_MAXNAMELEN]; __u8 revision; } user; struct { __u16 match_size; /* Used inside the kernel */ struct xt_match *match; } kernel; /* Total length */ __u16 match_size; } u; unsigned char data[0]; }; #define XT_EXTENSION_MAXNAMELEN 29
... char name [XT_EXTENSION_MAXNAMELEN] byte [] convArray = BitConverter.GetBytes(value);
Array.Reverse(convArray); ushort reverseEndian = BitConverter.ToUInt16(convArray,0); ushort reverseEndian = (ushort)((value << 8) | (value >> 8));
[DllImport("libip4tc.so", SetLastError = true)]
int errno = Marshal.GetLastWin32Error();
var errPtr = Libiptc4.iptc_strerror(errno); string errStr = Marshal.PtrToStringAnsi(errPtr); =========== Источник: habr.com =========== Похожие новости:
Разработка под Linux ), #_razrabotka_pod_windows ( Разработка под Windows ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 09:20
Часовой пояс: UTC + 5