Заставить libstruct работать в matlab для аргумента указателя dll
Я пытаюсь вызвать функцию DLL в Matlab. У меня есть структура C++, как показано в sixense.h:
typedef struct _sixenseControllerData {
float pos[3];
float rot_mat[3][3];
float joystick_x;
float joystick_y;
float trigger;
...
} sixenseControllerData;
и функции, которые я мог бы назвать:
SIXENSE_EXPORT int sixenseInit( void );
SIXENSE_EXPORT int sixenseGetAllNewestData( sixenseAllControllerData * );
Я могу легко заставить это работать calllib('sixense','sixenseInit')
так как нет ввода, но для функции sixenseGetAllNewestData мне нужно иметь указатель структуры. Я понимаю, что libstruct - это то, что мне нужно использовать. Тем не менее, я, кажется, не делаю это правильно.
Итак, я попробовал libstruct так:
libstruct('sixenseControllerData')
и я получаю ошибку:
??? Error using ==> feval
Undefined function or variable 'lib.sixenseControllerData'.
Error in ==> libstruct at 15
ptr=feval(['lib.' structtype]);
РЕДАКТИРОВАТЬ: вот мой текущий неотредактированный прото-файл: http://pastebin.com/PemmmMqF
полный файл заголовка доступен здесь: https://github.com/rll/sixense/blob/master/include/sixense.h
1 ответ
Для структур C, loadlibrary
генерирует типы с именем: s_{NAME}
где {NAME}
это имя структуры. В вашем случае мы создаем указатель как:
s = libstruct('s_sixenseControllerData');
Мы можем увидеть этот факт, указав MATLAB создать файл прототипа:
>> loadlibrary('sixense', 'sixense.h', 'proto','sixense_proto')
Файл прототипа - это файл команд MATLAB, который мы можем изменить и использовать вместо файла заголовка. В этом случае файл будет содержать что-то вроде:
sixense_proto.m
...
structs.s_sixenseControllerData.members = struct('pos', 'single#3', 'rot_mat', 'single#9', 'joystick_x', 'single', 'joystick_y', 'single', 'trigger', 'single', 'buttons', 'uint32', 'sequence_number', 'uint8', 'rot_quat', 'single#4', 'firmware_revision', 'uint16', 'hardware_revision', 'uint16', 'packet_type', 'uint16', 'magnetic_frequency', 'uint16', 'enabled', 'int32', 'controller_index', 'int32', 'is_docked', 'uint8', 'which_hand', 'uint8', 'hemi_tracking_enabled', 'uint8');
structs.s_sixenseAllControllerData.members = struct('controllers', 's_sixenseControllerData#4');
....
К сожалению, ограничение loadlibrary
является то, что он не очень хорошо поддерживает вложенную структуру, особенно если структура содержит указатель на другую структуру (или массив в этом случае):
Вложенные структуры или структуры, содержащие указатель на структуру, не поддерживаются. Однако MATLAB может получить доступ к массиву структур, созданных во внешней библиотеке.
Таким образом, вы не сможете напрямую создать sixenseAllControllerData
структура на стороне MATLAB, которая определена в заголовочном файле C как:
typedef struct _sixenseAllControllerData {
sixenseControllerData controllers[4];
} sixenseAllControllerData;
Согласно последующему обсуждению, один из обходных путей - "развернуть"/"сгладить" массив в отдельные переменные. Вы можете сделать это в копии файла заголовка или внести изменения в сгенерированный файл прототипа (который я считаю предпочтительным способом). Вы можете сделать это без перекомпиляции общей библиотеки.
В вашем случае измените вложенную структуру в сгенерированном sixense_proto.m
файл в:
structs.s_sixenseAllControllerData.members = struct(...
'controllers1', 's_sixenseControllerData', ...
'controllers2', 's_sixenseControllerData', ...
'controllers3', 's_sixenseControllerData', ...
'controllers4', 's_sixenseControllerData');
Теперь мы можем создать указатель на эту структуру и вызвать метод C:
s = libstruct('s_sixenseAllControllerData');
s.controllers1 = libstruct('s_sixenseControllerData');
s.controllers2 = libstruct('s_sixenseControllerData');
s.controllers3 = libstruct('s_sixenseControllerData');
s.controllers4 = libstruct('s_sixenseControllerData');
out = calllib('sixense', 'sixenseGetAllNewestData', s);
get(s)
Совершенно другое решение - написать MEX-функцию для взаимодействия с библиотекой. Это так же, как любой другой код C/C++, только с использованием mxArray
и MX-API для взаимодействия с MATLAB...
Пример:
Чтобы проверить вышеизложенное, я создал простую DLL с похожей структурой и реализовал вышеуказанное решение. Вот код, если кто-то хочет его протестировать:
helper.h
#ifndef HELPER_H
#define HELPER_H
#ifdef _WIN32
#ifdef EXPORT_FCNS
#define EXPORTED_FUNCTION __declspec(dllexport)
#else
#define EXPORTED_FUNCTION __declspec(dllimport)
#endif
#else
#define EXPORTED_FUNCTION
#endif
#endif
mylib.h
#ifndef MYLIB_H
#define MYLIB_H
#include "helper.h"
typedef struct _mystruct {
int pos[3];
double value;
} mystruct;
typedef struct _mystruct2 {
mystruct arr[2];
int num;
} mystruct2;
EXPORTED_FUNCTION void myfunc(mystruct *);
EXPORTED_FUNCTION void myfunc2(mystruct2 *);
#endif
mylib.c
#define EXPORT_FCNS
#include "helper.h"
#include "mylib.h"
void myfunc(mystruct *s)
{
s->pos[0] = 10;
s->pos[1] = 20;
s->pos[2] = 30;
s->value = 4.0;
}
void myfunc2(mystruct2 *s)
{
int i;
for(i=0; i<2; i++) {
myfunc(&(s->arr[i]));
}
s->num = 99;
}
После компиляции вышеупомянутого в DLL, мы генерируем исходный файл прототипа:
loadlibrary('./mylib.dll', './mylib.h', 'mfilename','mylib_proto')
unloadlibrary mylib
Я редактирую файл прототипа, как описано выше:
function [methodinfo,structs,enuminfo,ThunkLibName] = mylib_proto()
MfilePath = fileparts(mfilename('fullpath'));
ThunkLibName = fullfile(MfilePath,'mylib_thunk_pcwin64');
enuminfo = [];
structs = [];
structs.s_mystruct.members = struct('pos','int32#3', 'value','double');
structs.s_mystruct2.members = struct('arr1','s_mystruct', ...
'arr2','s_mystruct', 'num','int32');
ival = {cell(1,0)};
methodinfo = struct('name',ival, 'calltype',ival, 'LHS',ival, ...
'RHS',ival, 'alias',ival, 'thunkname',ival);
methodinfo.thunkname{1} = 'voidvoidPtrThunk';
methodinfo.name{1} = 'myfunc';
methodinfo.calltype{1} = 'Thunk';
methodinfo.LHS{1} = [];
methodinfo.RHS{1} = {'s_mystructPtr'};
methodinfo.thunkname{2} = 'voidvoidPtrThunk';
methodinfo.name{2} = 'myfunc2';
methodinfo.calltype{2} = 'Thunk';
methodinfo.LHS{2} = [];
methodinfo.RHS{2} = {'s_mystruct2Ptr'};
end
Теперь мы можем наконец вызвать функции, предоставляемые DLL:
%// load library using proto file
loadlibrary('./mylib.dll', @mylib_proto)
%// call first function with pointer to struct
s = struct('pos',[0,0,0], 'value',0);
ss = libstruct('s_mystruct',s);
calllib('mylib', 'myfunc', ss)
get(ss)
%// call second function with pointer to struct containing array of struct
xx = libstruct('s_mystruct2');
xx.arr1 = libstruct('s_mystruct');
xx.arr2 = libstruct('s_mystruct');
calllib('mylib', 'myfunc2', xx)
get(xx)
%// clear references and unload library
clear ss xx
unloadlibrary mylib