getting libstruct to work in matlab for dll pointer argument

风格不统一 提交于 2020-01-02 10:25:37

问题


I'm trying to call a dll function in matlab. I have a C++ struct as shown in sixense.h:

typedef struct _sixenseControllerData {
  float pos[3];
  float rot_mat[3][3];
  float joystick_x;
  float joystick_y;
  float trigger;
  ...
} sixenseControllerData;

and functions I could call:

SIXENSE_EXPORT int sixenseInit( void );
SIXENSE_EXPORT int sixenseGetAllNewestData( sixenseAllControllerData * );

I can easily get this to work with calllib('sixense','sixenseInit') since there is no input, but for the function sixenseGetAllNewestData I need to have a struct pointer. I realize that libstruct is what I need to use. However, I don't seem to be doing it right.

So I tried libstruct like so:

libstruct('sixenseControllerData')

and I get the error:

??? Error using ==> feval
Undefined function or variable 'lib.sixenseControllerData'.

Error in ==> libstruct at 15
    ptr=feval(['lib.' structtype]);

EDIT: here is my current unedited proto file: http://pastebin.com/PemmmMqF

the full header file is available here: https://github.com/rll/sixense/blob/master/include/sixense.h


回答1:


For C structures, loadlibrary generates types named: s_{NAME} where {NAME} is the name of the structure. In your case we create a pointer as:

s = libstruct('s_sixenseControllerData');

We can see this fact by instructing MATLAB to generate a prototype file:

>> loadlibrary('sixense', 'sixense.h', 'proto','sixense_proto')

A prototype file is a file of MATLAB commands which we can modify and use in place of a header file. In this case, the file will contain something like:

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');
....

Unfortunately, a limitation of loadlibrary is that it does not support nested structure very well, especially if a structure contains a pointer to another structure (or an array in this case):

Nested structures or structures containing a pointer to a structure are not supported. However, MATLAB can access an array of structures created in an external library.

So you will not be able to directly create the sixenseAllControllerData structure on the MATLAB side, which is defined in the C header file as:

typedef struct _sixenseAllControllerData {
    sixenseControllerData controllers[4];
} sixenseAllControllerData;

According to the following discussion, one workaround is to "unroll"/"flatten" the array into separate variables. You can either do this in a copy of the header file, or making the changes in the generated prototype file (which I think is the preferred way). You can do this without having to recompile the shared library.

In your case, change the nested structure in the generated sixense_proto.m file into:

structs.s_sixenseAllControllerData.members = struct(...
    'controllers1', 's_sixenseControllerData', ...
    'controllers2', 's_sixenseControllerData', ...
    'controllers3', 's_sixenseControllerData', ...
    'controllers4', 's_sixenseControllerData');

Now we can create a pointer to this structure, and call the C method:

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)

A completely different solution is to write a MEX-function to interface with the library. It is just like any other C/C++ code, only using mxArray and the MX-API to interface with MATLAB...


Example:

To test the above, I created a simple DLL with similar structures, and implemented the above solution. Here is the code if someone wants to test it:

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;
}

After compiling the above into a DLL, we generate the initial prototype file:

loadlibrary('./mylib.dll', './mylib.h', 'mfilename','mylib_proto')
unloadlibrary mylib

I edit the prototype file as described before:

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

Now we can finally invoke functions exposed by the 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


来源:https://stackoverflow.com/questions/18069858/getting-libstruct-to-work-in-matlab-for-dll-pointer-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!