LINUX.ORG.RU

Взаимодействие perl и C


1

1

Некоторое ПО разрабатывается преимущественно на perl. Но есть необходимость некоторый код при этом написать на C (декодирование сжатых данных, работа с отдельными битами на perl не быстра). Как лучше организовать взаимодействие? Варианты:

1) perl модуль используя xs... столько я не скурю.

2) perl модуль через swig... несколько легче, но тоже...

3) ?

C-модуль может, например, получать массив бинарных данных и возвращать массив структур. Можно сделать отдельную программу на C, например, которая будет выдавать в stdout текстом, а perl парсить. Но как-то нехорошо это. Производительность тоже...


Ответ на: комментарий от AITap

Несложно? Да это без психоактивных вещество просто не понять...

Есть структура, например:

typedef struct { int x; int y; } point;

Есть функция которая возвращает массив:

int get_points(point **retval, arguments...);

Массив в retval (память выделяется), возвращаемое значение — длина (элементов). Как это проще превратить во что-то с чем сможет работать perl и сделать также, что perl не забыл сделать free(*retval) потом ?

fk0
() автор топика
Ответ на: комментарий от fk0

Вообще как в perl работать с такими типами данных. Я вижу два способа: либо получить массив хешей (но это ж и память и CPU отъедает ни на что), либо для каждого элемента структуры отдельный массив. Последнее кажется не самым дурацким решением.

fk0
() автор топика
Ответ на: комментарий от fk0

Может проще не морочить голову, а сделать (s)printf с перловым кодом на выходе (в качестве результата C функции) который потом пропустить через eval — чем не решение?

fk0
() автор топика

загрузка сишных библиотек в перл работает не так стабильно, как хотелось бы, гораздо надежнее написать вспомогательную комманд-лайн утилиту и вызывать ее через system()

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

загрузка сишных библиотек в перл работает не так стабильно, как хотелось бы

А поподробнее? Какие есть подводные камни. Возможно, скоро предстоит как раз скрещивать перл и си.

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от DELIRIUM

А поподробнее? Какие есть подводные камни. Возможно, скоро предстоит как раз скрещивать перл и си.

Был печальный опыт по скрещиванию си и перла - загружали Qt-библиотеки и на красной шапке стабильно текла память, периодически происходили сегфолты. После того как разделили это безобразие на разные процессы, удалось стабилизировать систему.

EugeneBas ★★
()
Ответ на: комментарий от EugeneBas

загрузка сишных библиотек в перл работает не так стабильно, как хотелось бы

В это нечень верится. Вот то, что там непросто в этом разобраться, - да.

загружали Qt-библиотеки

А qt4-perl?

А у меня как раз наоборот нужно было: использовать из C перл-скрипт.

gag ★★★★★
()
Ответ на: комментарий от EugeneBas

Был печальный опыт по скрещиванию си и перла - загружали Qt-библиотеки и на красной шапке стабильно текла память

Это не проблема перла, почитайте, что пишут разработчики из бугра:

http://search.cpan.org/perldoc?AnyEvent::Impl::Qt

http://search.cpan.org/perldoc?AnyEvent::Impl::Tk

http://search.cpan.org/perldoc?AnyEvent::Impl::Glib

Просто поставляется кривой код, который патчить никто не собирается. Сколько не использую сторонние сишные модули, такие как uuid, mongodb[::async], libev, dbd::oracle, dbd::pg и т.д. и т.п. проблем со стороны перла нет. Есть проблемы на стороне сишного кода, когда забывают чистить структуры, а также писать thread-safe код (или стремится к этому).

gh0stwizard ★★★★★
()
Ответ на: комментарий от gh0stwizard

Просто поставляется кривой код, который патчить никто не собирается. Сколько не использую сторонние сишные модули, такие как uuid, mongodb[::async], libev, dbd::oracle, dbd::pg и т.д. и т.п. проблем со стороны перла нет. Есть проблемы на стороне сишного кода, когда забывают чистить структуры, а также писать thread-safe код (или стремится к этому).

Как бы то ни было, но выделение всех сишных вызовов в отдельный процесс гарантированно избавляет от всех проблем с памятью, т.к. при завершении приложения вся память почистится, а при загрузки библиотеки так и будет течь

EugeneBas ★★
()
Ответ на: комментарий от fk0

Полдня чтения perlxstut, perlxs, perlapi и Google:

1) Создаём скелет модуля: h2xs -Afn My::Module
2) cd My-Module && $EDITOR Module.xs
3) #include<myfile.h>
И пишем обёртку для функции. Например, для «модельного» случая:

typedef struct { int x; int y; } point;
int get_points (point **buf, int cnt);

int get_points (point **buf,int cnt) {
        int i;
        (*buf)=(point *)malloc(sizeof(point)*cnt);
        for (i=0;i<cnt;i++) {
                (*buf)[i]=(point){7,7};
        }
        return cnt;
}
AV *
get_points(int num)
        CODE:  
                // int прозрачно превращается в типы C и обратно
                point* buf; // а массивы придётся превращать руками
                int cnt = get_points(&buf,num);
                // ---
                RETVAL = newAV(); // создаём массив
                sv_2mortal((SV*)RETVAL); // из-за бага в XS возвращаемые массивы нужно дополнительно делать "смертными"
                // ---
                int i;
                for (i=0;i<cnt;i++) {
                        AV* arr = newAV(); // создаём массив. его не нужно делать смертным
                        av_push(arr,newSViv((IV)buf[i].x)); // добавляем в него созданный из int Perl'овый скаляр
                        av_push(arr,newSViv((IV)buf[i].y));
                        SV* ref = newRV_noinc((SV*)arr); // получаем ссылку на этот массив
                        av_push(RETVAL,ref); // и запихиваем её в возвращаемое значение
                }
                free(buf); // не забываем
        OUTPUT:
                RETVAL // Perl возвращает ссылку на массив ссылок на массивы

4) Не забываем включить в Makefile.PL:

        # Un-comment this if you add C files to link with later:
    OBJECT            => '$(O_FILES)', # link all the C files too
5) perl Makefile.PL && make test

 perl -Iblib/arch -Ilib -MAITap::Module -MData::Dumper -e 'print Dumper AITap::Module::get_points(3)'
$VAR1 = [
          [
            7,
            7
          ],
          [
            7,
            7
          ],
          [
            7,
            7
          ]
        ];

// Заранее извиняюсь за мой французский C. Я разговариваю на нём со словарём.

AITap ★★★★★
()
Последнее исправление: AITap (всего исправлений: 1)
Ответ на: комментарий от EugeneBas

так и будет течь

Лолкс. У перла «оптимистичная» система выделения памяти под данные. Поэтому, перл практически никогда не отдает память обратно в систему. Все выглядит как будто перл жрет жрет память, для вас, это утечка. Но на деле - ничего не течет. Сишные либы могут освобождать память своих структур данных по необходимости и, естественно, они вернутся обратно в ОС. См. линки ниже и курите доки. Иначе бы перлом никто бы не пользовался.

http://perldoc.perl.org/perlfaq3.html#How-can-I-free-an-array-or-hash-so-my-p... http://perl.active-venture.com/pod/perldebguts-perlmemory.html http://perldoc.perl.org/perldebguts.html#Debugging-Perl-Memory-Usage

gh0stwizard ★★★★★
()
Ответ на: комментарий от EugeneBas

т.к. при завершении приложения вся память почистится, а при загрузки библиотеки так и будет течь

Загрузке? Или выгрузке? Так поэтому перл и не выгружает сишные библиотеки. Из-за того, что разработчики этих библиотек полагаются на ОС, не убирая за собою. И забывают, что их библиотеки не только разделяемые, но и динамически подгружаемые.

gag ★★★★★
()
Ответ на: комментарий от gag

Загрузке? Или выгрузке?

неточно выразился, при загрузке и невыгрузке сразу после вызова нужной функции

EugeneBas ★★
()
Ответ на: комментарий от rand

ZeroMQ и иже с ними? Зато универсально...

+1, когда читал вопрос ТС-а сам невольно думал про ZeroMQ :)

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от AITap

Заработало!!! Почти

AV *
decode(indata)
	SV *indata;
	INIT:
		STRLEN size;
		int rval;
		unsigned n;
		const char *data;
                point *buf;
		AV *result;
        CODE:  
		data=SvPVbyte(indata, size);
                rval=decode(&buf, data, size);
		if (rval <= 0) XSRETURN_UNDEF;
		result=newAV();
		av_extend(result, rval);
                for (n=0; n<rval; n++) {
			SV *ref;
                        AV *point=newAV();
                        av_push(point, newSViv(buf[n].motion));
                        av_push(point, newSViv((IV)buf[n].type));
			av_push(point, newSViv((IV)buf[n].time));
			av_push(point, newSViv((IV)buf[n].lat));
			av_push(point, newSViv((IV)buf[n].lng));
			av_push(point, newSViv((IV)buf[n].dir));
			av_push(point, newSViv((IV)buf[n].speed));
			av_push(point, newSViv((IV)buf[n].mcc));
			av_push(point, newSViv((IV)buf[n].mnc));
			av_push(point, newSViv((IV)buf[n].lac));
			av_push(point, newSViv((IV)buf[n].cell));
                        ref=newRV_noinc((SV*)point);
                        av_store(result, n, ref);
                }
                free(buf);
                RETVAL = result;
		sv_2mortal((SV *)RETVAL);

        OUTPUT:
		RETVAL

Как-то так. Возвращает примерно то что надо:

$VAR1 = [
          [
            1,
            1,
            1336215223,
            5536352,
            3792400,
            0,
            0,
            -1,
            -1,
            0,
            0
          ],
          [
            1,
            1,
            1336215271,
            5536336,
            3792384,
            0,
            16,
            -1,
            -1,
            0,
            0
          ],
         ...
]

Но я вот, допустим, вот такую штуку хочу:

use Class::Struct Point => [motion=>'$', type=>'$', time=>'$', lat=>'$', lng=>'$',
        dir=>'$', speed=>'$', mcc=>'$', mnc=>'$', lac=>'$', cell=>'$'];

use Data::Dumper;

my $p=new Point(motion=>1, type=>2, time=>3, lat=>4, lng=>5, dir=>6, speed=>6, mcc=>8, mnc=>8, lac=>10, cell=>11);

print Dumper $p;

Как то что оно мне возвращает сделать объектом-структурой вот такого типа? Я что-то не понимаю, даже не знаю что. :-(

fk0
() автор топика
Ответ на: Заработало!!! Почти от fk0

Видимо, вместо AV нужно делать Hash Value и при получении их reference делать для них bless:

HV *point=newHV();
hv_stores(point, «motion», newSViv(buf[n].motion));
hv_stores(point, «type», newSViv(buf[n].type));
hv_stores(point, «time», newSViv(buf[n].time));
hv_stores(point, «lat», newSViv(buf[n].lat));
hv_stores(point, «lng», newSViv(buf[n].lng));
hv_stores(point, «dir», newSViv(buf[n].dir));
hv_stores(point, «speed», newSViv(buf[n].speed));
hv_stores(point, «mcc», newSViv(buf[n].mcc));
hv_stores(point, «mnc», newSViv(buf[n].mnc));
hv_stores(point, «lac», newSViv(buf[n].lac));
hv_stores(point, «cell», newSViv(buf[n].cell));
ref=newRV_noinc((SV*)point);
sv_bless(ref,gv_stashsvs(«Point»,GV_ADD));
av_store(result, n, ref);

Аналогичный код на моей «модели модуля» даёт:
$ perl -Iblib/arch -Ilib -MAITap::Module -MData::Dumper -e 'print Dumper AITap::Module::get_hvpoints(2)'
$VAR1 = [
bless( {
'y' => 7,
'x' => 7
}, 'Point' ),
bless( {
'y' => 7,
'x' => 7
}, 'Point' )
];

AITap ★★★★★
()
Ответ на: комментарий от AITap

Сделал...

Примерно то, что я хотел:

AV *
decode(indata)
	SV *indata;
	INIT:
		STRLEN size;
		int rval;
		unsigned n;
		const char *data;
                point *buf;
		AV *result;
		HV *class;
        CODE:  
		data=SvPVbyte(indata, size);
                rval=decode(&buf, data, size);
		if (rval <= 0) XSRETURN_UNDEF;
		class=gv_stashpv("Point", 0);
		result=newAV();
		av_extend(result, rval);
                for (n=0; n<rval; n++) {
			SV *ref, *bless;
                        AV *point=newAV();
                        av_push(point, newSVuv(buf[n].motion));
                        av_push(point, newSVuv(buf[n].type));
			av_push(point, newSVuv(buf[n].time));
			av_push(point, newSViv(buf[n].lat));
			av_push(point, newSViv(buf[n].lng));
			av_push(point, newSViv(buf[n].dir));
			av_push(point, newSViv(buf[n].speed));
			av_push(point, newSViv(buf[n].mcc));
			av_push(point, newSViv(buf[n].mnc));
			av_push(point, newSVuv(buf[n].lac));
			av_push(point, newSVuv(buf[n].cell));
                        ref=newRV_noinc((SV*)point);
			bless=sv_bless(ref, class);
                        av_store(result, n, bless);
                }
                free(buf);
                RETVAL = result;
		sv_2mortal((SV *)RETVAL);

        OUTPUT:
		RETVAL

Возвращает ссылку на массив в котором bless'нутые в нужный класс массивы (не хеши — ибо расточительно под каждую точечку хеш), с другой стороны через Class::Struct можно работать.

Мне немножко непонятно, и это всегда вызывало недоумение — это почему нужно возвращать ссылку на массив вместо массива. В последнем случае он копируется и это плохо и медленно? Приходится @{$array} <-- вот такие штуки выписывать потом в коде на perl.

И ещё может подскажет кто. В итоге у меня perl-модуль как бы, и .so тоже. Он даже через make install куда-то ставится. Последнее не очень желательно. Хотелось бы просто в текущем каталоге вместе с другими *.pm в одну кучу свалить. Надо просто взять *.pm и *.so или как это делать правильно?

fk0
() автор топика
Ответ на: Сделал... от fk0

Мне немножко непонятно, и это всегда вызывало недоумение — это почему нужно возвращать ссылку на массив вместо массива.

Мне тоже: RETVAL имеет тип AV*, а не SV* (RV). Удалось найти:

AFAICT from the PerlXS doc, RETVAL has a 'limit' of one value returned,
so RETVAL won't work in my 'envisaged' function, but it seems possible
to return any number of vars via stack manipulation (subject to size)
using the ST(n) array.

См. http://www.nntp.perl.org/group/perl.xs/2011/06/msg2624.html

Хотелось бы просто в текущем каталоге вместе с другими *.pm в одну кучу свалить. Надо просто взять *.pm и *.so или как это делать правильно?

Модуль можно установить модуль в почти произвольное место при помощи local::lib.
Можно взять с собой директории lib и blib и запускать свой модуль при помощи -Iblib/arch -Ilib (примеры выше), или при помощи use lib:


[13:46:27][aitap@infinitea ~/temp/c2perl/AITap-Module]> perl -e 'use lib qw{lib blib/arch}; use AITap::Module; use Data::Dumper; print Dumper AITap::Module::get_hvpoints(1)'
$VAR1 = [
bless( {
'y' => 7,
'x' => 7
}, 'Point' )
];

AITap ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.