Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.85/13: Рейтинг темы: голосов - 13, средняя оценка - 4.85
0 / 0 / 0
Регистрация: 06.11.2019
Сообщений: 4
1

Возврат массива неизвестного размера из DLL (C++) в C#

06.11.2019, 08:08. Показов 2699. Ответов 14

Author24 — интернет-сервис помощи студентам
Доброго дня. Есть DLL, в которой функция от аппаратуры получает набор данных заранее неизвестного размера, size определяется внутри функции:
C++
1
2
3
4
5
6
7
8
9
10
11
__declspec(dllexport) int get_bat_info(LPCTSTR comportidx, float* voltage, byte* power, byte* current, unsigned short &size)
{
....
for (int i = 0; i < size; i++)
    {
        voltage[i] = (float)uart_buffer[i * 3] / 10;
        power[i] = uart_buffer[i * 3 + 1];
        current[i] = uart_buffer[i * 3 + 2];
    }
...
}
И есть код верхнего уровня на C#, который должен получить эти данные уже в удобном к использованию виде:
C#
1
2
3
4
5
6
7
8
9
10
11
12
[DllImport("dll_name.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
        private static extern int get_bat_info(string COMname, [In, Out] float[] voltage, [In, Out] byte[] power, [In, Out] byte[] current, out ushort count);
...
private void button6_Click(object sender, EventArgs e)
{
    ushort count;
    float[] voltage= new float[65535];
    byte[] power= new byte[65535];
    byte[] current= new byte[65535];
    int state = get_bat_info(comboBox1.Text, voltage, power, current, out count);
    ...
}
В таком виде все работает, но мне не нравится то, что я создаю заранее буфер максимально возможного размера. Можно ли как-то выделять память внутри DLL, а в ПО верхнего уровня передавать указатель и размер?
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
06.11.2019, 08:08
Ответы с готовыми решениями:

Получить изображение неизвестного размера (байтовый массив) из dll C++
Здравствуйте уважаемые Гуру! Подскажите пожалуйста новичку как правильно осуществить сабж? DLL...

Как пробежаться по элементам двумерного массива неизвестного размера по типу "по строкам-по столбцам"
собственно: int arr; for(int i = 0, i &lt; ...., i++) { for(int j = 0, j &lt; ...., j++) {...

Массив неизвестного размера
Хочу написать функцию для нахождения предела последовательности. А и eps вводятся с клавиатуры....

Чтение файла неизвестного размера
Необходимо прочитать файл неизвестного размера, динамически растягивая массив, загнать файл в...

14
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
06.11.2019, 11:10 2
qwerty-off, не создавайте, вообще в функцию передается не указатель на массив, а указатель на начало памяти которая может быть выделена как под один элемент так и под несколько, Используйте в методе импорт IntPtr а далее в цикле делайте получение нужно значения через маршалинг байт.

Добавлено через 4 минуты
Цитата Сообщение от qwerty-off Посмотреть сообщение
Можно ли как-то выделять память внутри DLL, а в ПО верхнего уровня передавать указатель и размер?
Выделять можно, но тогда длл обязана очищать память после обработки.
1
278 / 186 / 75
Регистрация: 12.04.2017
Сообщений: 1,088
Записей в блоге: 2
06.11.2019, 11:14 3
Цитата Сообщение от Andreyip Посмотреть сообщение
Используйте в методе импорт IntPtr а далее в цикле делайте получение нужно значения через маршалинг байт.
зачем ?
Цитата Сообщение от qwerty-off Посмотреть сообщение
В таком виде все работает,
Andreyip, нужно все сломать?
0
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
06.11.2019, 11:38 4
RunningMan, qwerty-off, советую почитать как устроены массивы в C#, массив в C# конечно имеет последовательность значений элементов но не с указателя

выдержка:
Single array
Такие массивы часто называются также SZ-массивами (single-dimensional, zero-based) или векторами. Создадим обычный int[]-массив (каждый элемент занимает 4 байта) длинной 5 элементов, который заполним числами от 0 до 4:

int[] a = new int[5];
for (int i = 0; i < 5; i++)
a[i] = i;
В памяти он будет представлен следующим образом:

0x03022424 0 // SyncBlockIndex
0x03022428 0x61B9C448 // *MethodTable
0x0302242C 5 // a.Length
0x03022430 0 // a[0]
0x03022434 1 // a[1]
0x03022438 2 // a[2]
0x0302243C 3 // a[3]
0x03022440 4 // a[4]
Воспользуемся расширением отладчика SOS и через Immediate Window посмотрим чуть больше информации о нашем массиве:

.load sos.dll
!DumpArray 0x03022428
Name: System.Int32[]
MethodTable: 61b9c448
EEClass: 6180c0d0
Size: 32(0x20) bytes
Array: Rank 1, Number of elements 5, Type Int32
Element Methodtable: 61b9c480
[0] 03022430
[1] 03022434
[2] 03022438
[3] 0302243c
[4] 03022440
Тут всё достаточно просто. Переменная a указывает на адрес 0x03022428, по которому хранится указатель на таблицу методов соответствующего типа (в данном случае System.Int32[]), которая занимает 4 байта (для x64 — 8 байт). Перед ней находится SyncBlockIndex (отсчитывается от 1, 0 означает пустое значение; размер под x86 — 4 байта, под x64 — 8 байт). После *MethodTable идёт сначала размер массива, а затем по порядку все его элементы.
0
0 / 0 / 0
Регистрация: 06.11.2019
Сообщений: 4
06.11.2019, 13:10  [ТС] 5
Цитата Сообщение от Andreyip Посмотреть сообщение
Выделять можно, но тогда DLL обязана очищать память после обработки.
Насколько я понял C# сам чистит память сборщиком мусора, на данный случай это не распространяется? Выглядит довольно бредово создание еще одной функции в DLL ради того, чтобы грохнуть буфер.
Цитата Сообщение от RunningMan Посмотреть сообщение
нужно все сломать?
Возможно мне, как "железячнику", хочется сэкономить мегабайт памяти там, где его можно и не тратить. Все-таки считаю это правильным подходом.
Цитата Сообщение от Andreyip Посмотреть сообщение
массив в C# конечно имеет последовательность значений элементов но не с указателя
Это значит что приведенный мной код не должен работать, так как DLL испортит служебные поля массива? Но ведь работает же.

Так и не понял, в какую сторону в итоге мне копать? Получать из DLL IntPtr?
0
278 / 186 / 75
Регистрация: 12.04.2017
Сообщений: 1,088
Записей в блоге: 2
06.11.2019, 13:14 6
Цитата Сообщение от qwerty-off Посмотреть сообщение
Все-таки считаю это правильным подходом.
qwerty-off, вопрос был не вам.
вопрос был к Andreyip , зачем использовать в методе импорт IntPtr а далее в цикле делать получение значения через маршалинг байт.
Но что-то он не ответил.
0
0 / 0 / 0
Регистрация: 06.11.2019
Сообщений: 4
06.11.2019, 14:12  [ТС] 7
Продвинулся до такого подхода:
C++
1
2
3
4
5
6
7
8
9
10
11
12
__declspec(dllexport) int get_bat_info(LPCTSTR comportidx, float*& voltage, byte*& power, byte*& current, unsigned short &size)
{
    voltage = new float[size];
    power = new byte[size];
    current = new byte[size];
    for (int i = 0; i < size; i++)
    {
        voltage[i] = (float)info[i * 3] / 10;
        power[i] = info[i * 3 + 1];
        current[i] = info[i * 3 + 2];
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static extern int get_bat_info(string COMname, out IntPtr voltage, out IntPtr power, out IntPtr current, out ushort count);
 
private void button6_Click(object sender, EventArgs e)
{
    ushort count;
    IntPtr ptr_voltage;
    IntPtr ptr_power;
    IntPtr ptr_current;
 
    int state = get_bat_info(comboBox1.Text, out ptr_voltage, out ptr_power, out ptr_current, out count);
 
    float[] voltage = new float[count];
    byte[] power = new byte[count];
    byte[] current = new byte[count];
    Marshal.Copy(ptr_voltage, voltage, 0, count);
}
Работает. Вопрос, надо ли что-то делать с памятью, выделенной внутри моей библиотеки? Нужно вызывать FreeHGlobal или что-то еще?
0
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
06.11.2019, 14:23 8
qwerty-off, Да после вызова метода нужно удалить память дернув какой то метод из библиотеки, либо перед следующим вызовом данного метода и после выгрузки DLL потому что память вы можете выделять разными способами отсюда и освобождать ее нужно так как выделили.
1
0 / 0 / 0
Регистрация: 06.11.2019
Сообщений: 4
06.11.2019, 15:00  [ТС] 9
Andreyip, понял, спасибо. Добавил еще одну функцию в DLL. Вот почему-то только не покидает ощущение, что все это решение еще более уродливное, чем изначальное выделение массивов на 65к отсчетов.
0
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
06.11.2019, 15:15 10
qwerty-off, это решение более правильное, если вам нужно что то передавать что то одинакового размера то делайте структуру описывающиее данные, создавайте структуру в exe и передавайте в DLL для заполнения.
Если данные не одинакового размера и размер определяется в DLL то приложение изначально не знает какой буфер выделить, тут 2 варианта использовать функцию Calback которую вызовет библиотека по переданному адресу, либо сначала дергать метод который возвращает размер, далее создаем объект в памяти и после вызываем функцию с адресом этого выделенного кусочка, ну либо как тут, вызвали получили, при следующем вызове очистили.
1
51 / 149 / 33
Регистрация: 29.06.2019
Сообщений: 1,428
29.02.2020, 09:05 11
Цитата Сообщение от Andreyip Посмотреть сообщение
Выделять можно, но тогда длл обязана очищать память после обработки.
набросайте примером кода please - ЧТО она тогда вернёт??.. в ту память, которая была выделена приложением, но уже мала для возвращения в неё результата из dll...

Добавлено через 4 минуты
из #1 от ТС
Цитата Сообщение от qwerty-off Посмотреть сообщение
В таком виде все работает, но мне не нравится то, что я создаю заранее буфер максимально возможного размера. Можно ли как-то выделять память внутри DLL,
Я вот тоже не знаю иного способа, кроме как выделить память изначально с запасом... но смущает такой вариант - ведь каким бы ни был запас всё равно может сложиться ситуация, когда даже его не хватит...

Добавлено через 4 минуты
Цитата Сообщение от qwerty-off Посмотреть сообщение
Andreyip, понял, спасибо. Добавил еще одну функцию в DLL
жаль не показали в теме ??
======================================
Добавлено через 21 минуту
вот для COMdll всё как-то продумано у MS - BSTR
Суть:
BSTR это указатель! Причем "BSTR * " - это так называемый указатель первого уровня, а сам BSTR - вложенный. Они управляются разной логикой. Так, "BSTR * " должна разместить вызывающая сторона, а сам BSTR занимается вызываемой функцией (для "out"-параметра). "in"-параметры вообще не должны изменяться внутри функции (вернее с их представлением в стеке можно делать что угодно, но с памятью, на которую они могут указывать - ни-ни).
И способ использования: CComBSTR и его метод Append
Альтернативный вариант:
можно вызывать COM-API-шный метод SysAllocStringLen (выделяющий память под BSTR). При этом память из строк нужно копировать как в случае с обыкновенными строками, но нельзя забывать, что BSTR имеет (обычно) размер символа 2 байта (sizeof(OLECHAR)). В принципе, через API можно написать более быстрый вариант, но коду будет больше, и он будет более опасным, а значит и возможность наделать ошибок будет выше...
... но тоже не понятно как реализовать этот вариант для увеличения размера буфера вызывающей стороны ? ... (для помещения туда результата, бОльшего по объёму)

Добавлено через 35 минут
БЕЗ ОБЁРТКИ CComBSTR
BSTR это обычный OLECHAR*. Значит и функции преобразования будут работать, как с OLECHAR Определение длины строки несколько опасно. Вообще то это строка которая должна оканчиваться нулем. Но внутри нее могут быть нули и потому для определения длины лучше использовать специальные функции.
- как здесь...
- далее, вероятно, SysReAllocString в помощь (там же по линку)... но непонятно работает ли в сторону увеличения строки (in, out) ?? надо тестить
0
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
02.03.2020, 21:20 12
JeyCi, вернет указатель на память, и вторым параметром вернет размер. Для такой работы есть разные способы есть. Один из способов обратный вызов, второй способ вызов с выделением памяти и очистку через другой вызов.
1
51 / 149 / 33
Регистрация: 29.06.2019
Сообщений: 1,428
03.03.2020, 07:19 13
Цитата Сообщение от Andreyip Посмотреть сообщение
Один из способов обратный вызов,
второй способ вызов с выделением памяти и очистку через другой вызов.
ок - беру на заметку... по реализации подумаю потом
0
544 / 352 / 119
Регистрация: 17.08.2014
Сообщений: 1,335
03.03.2020, 08:02 14
JeyCi, Чуть позже скину вам примерчики. Честно я не особо за выделение памяти в библиотеке, лучше уж обратные вызовы.
0
51 / 149 / 33
Регистрация: 29.06.2019
Сообщений: 1,428
04.03.2020, 04:43 15
Цитата Сообщение от Andreyip Посмотреть сообщение
Честно я не особо за выделение памяти в библиотеке
я тоже
p.s. примерчик буду ждать
0
04.03.2020, 04:43
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
04.03.2020, 04:43
Помогаю со студенческими работами здесь

Указатель на строку неизвестного размера
Доброго времени суток. Возник вопрос при работе с символьными массивами. Если имеется указатель на...

Массив неизвестного размера+функция
Алоха, имею такой вопрос вот у меня ниже код, который считает среднее значение всех елиментов...

Указатель на строку неизвестного размера
Доброго времени суток. Возник вопрос при работе с символьными массивами. Если имеется указатель на...

Массив заранее неизвестного размера
Скажите как создать массив за рание не известной размерности. Искал по форумам приимеры описания,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
15
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru