2
votes

I'm interfacing Matlab and C-code in order to be able to directly use some C-functions in Matlab. I know the prototype of these functions but the code inside could change. In order to interface it all, I use loadlibrary and calllib in Matlab and I don't want to use MEXFiles.

In the C-code, some structures are defined. Nevertheless, the one which defines the base component of the code could change : it contains some structure and variables, but some other structures can be added in this one, and I would like Matlab to be able to deal with all that.

But, as Mathworks says it :

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 I can't store directly a nested structure in Matlab. For example, the main component is a structure (a). This one contains an other structure (b). And (b) contains pointers to functions. If I directly store (a) in a variable using libstruct, when I call a C-method with (a) in argument, we can see that the pointers are lost. The worst is that the C-code knows what is the pointer to the structure (b), but he can't access to the pointed funcitons. Nevertheless, by creating this structure (b) before in Matlab, it works, but it's specific to (b) and I can't do the same for other nested structures.

That's why I was thinking that I must prevent Matlab from looking the type of the variable. I just must give it the pointer to the structure and lock all is in relation with this pointer in order to be able to pass this pointer in argument of a C-function with calllib.

So that's my question : do you know if I can lock a part of the memory which contains the structure (a) and all of what is in relation with? And do you think I can prevent Matlab from looking what is this pointer?

In fact, I just want to create a nested structure in a C-function and to re-use it in an other C-function, but to call these two C-functions with Matlab (without using MexFiles).

Thanks! :)

C-code

Structure (a)

    typedef struct {
    fmi2Real    *r;
    fmi2Integer *i;
    fmi2Boolean *b;
    fmi2String  *s;
    fmi2Boolean *isPositive;

    fmi2Real time;
    fmi2String instanceName;
    fmi2Type type;
    fmi2String GUID;
    const fmi2CallbackFunctions *functions;
    fmi2Boolean loggingOn;
    fmi2Boolean logCategories[NUMBER_OF_CATEGORIES];

    fmi2ComponentEnvironment componentEnvironment;
    ModelState state;
    fmi2EventInfo eventInfo;
    int isDirtyValues; // !0 is true
} ModelInstance;

Other structure can be added in this structure (a).

Structure (b)

    typedef struct {
   const fmi2CallbackLogger         logger;
   const fmi2CallbackAllocateMemory allocateMemory;
   const fmi2CallbackFreeMemory     freeMemory;
   const fmi2StepFinished           stepFinished;
   const fmi2ComponentEnvironment    componentEnvironment;
} fmi2CallbackFunctions;

typedef void      (*fmi2CallbackLogger)        (fmi2ComponentEnvironment, fmi2String, fmi2Status, fmi2String, fmi2String, ...);
typedef void*     (*fmi2CallbackAllocateMemory)(size_t, size_t);
typedef void      (*fmi2CallbackFreeMemory)    (void*);
typedef void      (*fmi2StepFinished)          (fmi2ComponentEnvironment, fmi2Status);

Prototype of one of the C-function (the first one which creates the main component)

fmi2Component fmi2Instantiate(fmi2String instanceName, fmi2Type fmuType, fmi2String fmuGUID,
                            fmi2String fmuResourceLocation, const fmi2CallbackFunctions *functions,
                            fmi2Boolean visible, fmi2Boolean loggingOn);

   typedef void* fmi2Component;

Matlab code

Call the function fmi2Instantiate and create the component.

fmu.component=calllib(model, 'fmi2Instantiate', libpointer('int8Ptr', fmu.instanceName), fmu.type, libpointer('int8Ptr', fmu.guid), libpointer('int8Ptr', resourceLocation), fmu.callbackFunctions, visible, loggingOn);

This component will be further pass in argument to an other C-function.

1
1) Some mock-code for struct a, struct b, and the c-functions and how you want to call these from matlab would help. 2) Why to absolutely avoid having a mex-file make the glue and handle the c-lib "as is" for you ?CitizenInsane
1) I added what you asked for- :) 2) I want to avoid MexFiles because the DLL I load has functions with a specific prototype I know, but what is inside, it could change, and I don't want to have to rebuild some MexFiles for every DLL, but just to load a DLL and that works! :)Kerial
2) Make more sense now .. even though as an alternative or backup solution you may pass the dll name as a parameter to mex function and let it load it for you with mexCallMATLAB ... humm... let's read for added details ;)CitizenInsane

1 Answers

0
votes

Reading from your comments that there can be different dlls but with exported functions having same prototypes, I would anyhow delegate the management of the library to a mex file.

So, please, let's consider building a FmiManager.mex that can be used from matlab like this:

[ok, msg] = FmiManager('SETUP', libraryName);
[hCallbacks] = FmiManager('CREATE_CALLBACKS', ... params for creating callbacks ...);
[hInstance] = FmiManager('CREATE_INSTANCE', hCallbacks, ... other params ...)
[...] = FmiManager('OTHER_COMMAND', ...);

It's quite simple, the first parameter is what you want to do with the library, and the other parameters are specific to the function you want to call in c-code.

  • SETUP is an extra command to dynamically change the library you're linked with.
  • CREATE_CALLBACKS calls into c-function to create callbacks and returns a handle to them (for instance, by simply casting c-pointer to 'struct b' as int)
  • CREATE_INSTANCE calls into c-function to instanciate main component and returns handle to created instance (again simple cast to int for 'struct a' for instance) and takes as input the handle to the callback (just need to cast back to 'struct b' within c-code)
  • OTHER_COMMAND you can extend process to other c-functions if required ...

Here is then some pseudo-code showing how to build this FmiManager.mex file:

[FmiManager.c]

#include <mex.h>
#include <windows.h>
#include "FmiManager.h" // The header for this mex file
#include "fmi.h" // The header for types in your 'fmi' library

// Pointer to correct dll
static HINSTANCE hDll = NULL;

// Pointer to the function that creates the callbacks
typedef fmi2CallbackFunctions* (FMI_CALLCONV createCallBacksPtr *)(...)
static createCallBacksPtr createCallBacks = NULL;

// Pointer to the function that performs instantiation
typedef fmi2Component* (FMI_CALLCONV instanciatePtr *)(...)
static instanciatePtr instanciate = NULL;

// Mex entry point
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    // Arguments parsing
    if (nrhs < 1) { mexErrMsgTxt("Not enough input arguments."); return; }
    if (!mxIsChar(prhs[0])) { mexErrMsgTxt("First parameter must be a string."); return; }

    // Command selection
    if (commandIs(prhs[0], "SETUP")) { processSetup(nlhs, plhs, nrhs, prhs); }
    else if (commandIs(prhs[0], "CREATE_CALLBACKS")) { processCreateCallbacks(nlhs, plhs, nrhs, prhs); }
    else if (commandIs(prhs[0], "INSTANCIATE")) { processInstanciate(nlhs, plhs, nrhs, prhs); }
    else { mexErrMsgTxt("Unknown command or command not implemented yet."); }
}

// Processing 'SETUP' command
void processSetup(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    // Free previous library
    if (hDll != NULL) { FreeLibrary(hDll); hDll = NULL; }

    // Load the new one
    char* librayPath = getThisMexPath();
    ... load library from '*prhs' (i.e. hDll = loadLibrary(fullpath))...
    mxFree(librayPath);

    // Bind functions pointers
    createCallBacks = (createCallBacksPtr)GetProcAddress(hDll, "createCallbacks");
    if (createCallBacks == NULL) { mexErrMsgTxt("Failed to map 'createCallBacks'"); return; }

    instanciate = (instanciatePtr)GetProcAddress(hDll, "instanciate");
    if (instanciate == NULL) { mexErrMsgTxt("Failed to map 'instanciate'"); return; }
}

// Processing 'CREATE_CALLBACKS' command
void processCreateCallbacks(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    ... unpack '*prhs' ...

    int hCallbacks = (int)createCallback(...); // Function has been binded during setup

    ... pack 'hCallbacks' into '*plhs' ...
}

// Processing 'INSTANCIATE' command
void processInstanciate(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    ... unpack '*prhs' ...

    int hInstance = (int)instanciate(...); // Function has been binded during setup

    ... pack 'hInstance' into '*plhs' ...
}

// Check if some command is really some givent one
bool commandIs(const mxArray* mxCommand, const char* command)
{
    double result;
    mxArray* plhs1[1];
    mxArray* prhs1[1];
    mxArray* plhs2[1];  
    mxArray* prhs2[2];

    if (mxCommand == NULL) { mexErrMsgTxt("'mxCommand' is null"); return false; }
    if (command == NULL) { mexErrMsgTxt("'command' is null"); return false; }
    if (!mxIsChar(mxCommand)) { mexErrMsgTxt("'mxCommand' is not a string"); return false; }

    // First trim
    prhs1[0] = (mxArray*)mxCommand;
    mexCallMATLAB(1, plhs1, 1, prhs1, "strtrim");

    // Then compare
    prhs2[0] = mxCreateString(command);
    prhs2[1] = plhs1[0];
    mexCallMATLAB(1, plhs2, 2, prhs2, "strcmpi");

    // Return comparison result
    result = mxGetScalar(plhs2[0]);  
    return (result != 0.0);
}

// Obtain the path of current mex file
// CAREFUL: Use mxFree on return pointer !!
char* getThisMexPath()
{
    mxArray *rhs[1], *lhs[1];
    char *path, *name;

    size_t lenpath, lenname, n;
    rhs[0] = mxCreateString("fullpath");
    mexCallMATLAB(1, lhs, 1, rhs, "mfilename");
    mxDestroyArray(rhs[0]);
    path = mxArrayToString(lhs[0]);
    mxDestroyArray(lhs[0]);

    mexCallMATLAB(1, lhs, 0, rhs, "mfilename");
    name = mxArrayToString(lhs[0]);
    mxDestroyArray(lhs[0]);

    lenpath = strlen(path);
    lenname = strlen(name);
    n = lenpath - lenname;
    path[n] = '\0';

    mxFree(name);

    return path; // Don't forget mxFree !!! 
}

I hope you will like this alternative solution. Main advantage of it is that you don't have to bother making sure structures created within matlab will fit with c-side code. Everything is delegated to the .mex file (i.e. in c) and is passed to matlab as handles.