编写一个替代Matlab C API用于编写.mat文件的程序。

3
我正在研究一个研究模型,该模型将结果输出到Matlab的.mat文件格式,并最初链接了Matlab库以使用其文件输出功能。最近,要求发生了变化(谁能想到),之前仅适用于Linux的代码现在必须在Windows上编译,最好不需要Matlab进行构建,但仍然要输出.mat文件。
因此,我搜索并找到了libmatio(http://sourceforge.net/projects/matio/)。虽然在Linux上很容易链接(只需从存储库中安装即可),但在Windows上非常糟糕(基本上没有任何有关在Windows上构建它的信息)。实际上,似乎在2008年版本1.3.3中已经悄悄地放弃了对Windows的支持。
此外,APi与Matlab提供的完全不同,这将要求我重写/重新结构化大量代码。

所以我想出了这个疯狂的想法...... 我需要一个Matlab API的插入替换,最好不使用库(以使非程序员轻松编译),因此我开始编写一个。
我只实现了我需要的功能(编写双倍、字符串和复杂双倍的数组,以及结构和结构嵌套)。所有这些都已经正常工作,除了一个:结构数组。

所有Matlab数据都包含在一个称为“mxArray”的结构中,根据其类型,它包含指向double、complex double或一个或多个其他mxArray的指针。
在将mxArray写入文件之前的最后一步是通过调用calcArraySize()计算其字节大小(以及其子级的大小)。这会导致某些时候发生段错误,因为我正在尝试访问空指针。为了找出原因,我通过valgrind运行了代码。像往常一样,我尝试按照出现的顺序解决任何问题,因为它们可能是后面发生的事情的原因。
所以valgrind告诉我的第一件事是:

==8405== Invalid write of size 8
==8405==    at 0x00404541: mxSetFieldByNumber (mxSetFieldByNumber.c:18) [A]
==8405==    by 0x00411679: calcAllRayInfo (calcAllRayInfo.c:156)
==8405==    by 0x0041dd42: main (cTraceo.c:111)
==8405==    Address 0x5500250 is 0 bytes inside a block of size 4 alloc'd
==8405==    at 0x04c28f9f: malloc (vg_replace_malloc.c:236)
==8405==    by 0x00401066: mallocChar (toolsMemory.c:69)
==8405==    by 0x00404314: mxCreateStructMatrix (mxCreateStructMatrix.c:43) [B]
==8405==    by 0x00411235: calcAllRayInfo (calcAllRayInfo.c:105)
==8405==    by 0x0041dd42: main (cTraceo.c:111)

注意:在下面的代码中,我标记了 [A] 和 [B]。
结构定义(仅显示相关成员):

struct mxArray{
  bool           isStruct;  //determines if this mxArray is a structure (which contains other mxArrays)
  bool           isChild;   //determines wheter this mxArray is a Child of another (when set, its name will not be written to the matfile, as it is already defined in the parent's fieldnames
  uintptr_t      nFields;
  char           **fieldNames;  //something like: {"theta","r","z"};
  struct mxArray **field; //pointer to member mxArrays. only used when isStruct is set.
};
typedef struct mxArray mxArray;

我使用的函数用于为structMatrix分配内存以及其内容:

mxArray* mxCreateStructMatrix(uintptr_t nRows, uintptr_t nCols, uintptr_t nFields, const char **fieldNames){
  /*
   * creates a 2D array of structures
   */
  mxArray*  outArray = NULL;

  /* do some input value validation */

  // allocate memory
  outArray  = malloc(nRows*nCols*sizeof(mxArray));
  if (outArray == NULL){
    fatal("mxCreateStructMatrix(): memory allocation error.");
  }

  // allocate memory for structure members (fields)
  for (uintptr_t iStruct=0; iStruct<nCols*nRows; iStruct++){
    outArray[iStruct].nFields       = nFields;
    outArray[iStruct].fieldNames        = malloc(nFields*sizeof(char*));

    //copy fieldnames into struct info
    for (uintptr_t iField=0; iField<nFields; iField++){
      //NOTE: strlen returns length of string not including the terminating NULL character
      outArray[iStruct].fieldNames[iField] = mallocChar(strlen(fieldNames[iField])+1);  // [B] <=======
      strncpy(outArray[iStruct].fieldNames[iField], fieldNames[iField], strlen(fieldNames[iField]));
    }

    outArray[iStruct].field     = NULL;
    outArray[iStruct].field     = malloc(nFields*sizeof(mxArray*));
    if (outArray[iStruct].field == NULL){
      fatal("mxCreateStructMatrix(): memory allocation error.\n");
    }
  }
return outArray;
}

mxArray还存在另外两个分配函数:

mxArray* mxCreateDoubleMatrix(uintptr_t nRows, uintptr_t nCols, uintptr_t numericType){
  /*
   * creates a 2D array of double precision floating point values.
   * can be real or complex.
   */
  [snip]
}
mxArray* mxCreateString(const char *inString)
  /*
   * creates an mxArray containing a string.
   */
  [snip]
}

这个函数将一个 mxArray 分配为另一个 mxArray 的子项:

void    mxSetFieldByNumber(mxArray* mxStruct,       //pointer to the mxStruct
                           uint32_t index,      //linear index of the element 
                           uint32_t iField,     //index of the structure's field which we want to set.
                           mxArray* inArray){       //the mxArray we want to assign to the mxStruct
  /* 
   * Assigns an mxArray to one of the fields of a structArray
   */
  inArray->isChild = true;  //determines that this mxArray is a child of another one
  mxStruct[index].field[iField] = inArray;  // [A] <===============
}

使用方法如下:

//create parent mxArray:
mxStruct = mxCreateStructMatrix(1, //number of rows
                                1, //number of columns
                                2, //number of fields in each element
                                fieldNames1);   //list of field names

//create children:
mxY = mxCreateDoubleMatrix(1 ,1, mxREAL);
mxZ = mxCreateDoubleMatrix(1 ,1, mxREAL);
mxSubStruct = mxCreateStructMatrix(1, //number of rows
                                   1, //number of columns
                                   3, //number of fields in each element
                                   fieldNames2); //list of field names

/* copy some values into the mxArrays */
[snip]

//link children to parents
mxSetFieldByNumber( mxStruct, //pointer to the parent mxArray
                    0,        //index of the element (linear)
                    0,        //position of the field (in this case, field 0 is "w"
                    mxY);     //the mxArray we want to add to the mxStruct

mxSetFieldByNumber( mxStruct,   0,  1,  mxZ);

mxSetFieldByNumber( mxSubStruct,    0,  0,  mxY);
mxSetFieldByNumber( mxSubStruct,    0,  1,  mxZ);

mxSetFieldByNumber( mxStruct,   0,  2,  mxSubStruct);

显然,mxStruct[index].field[iField] = inArray;正在写入mxStruct[index].fieldNames,从而使mxStruct[index].field[iField] == NULL,这会导致我尝试访问它时出现段错误。
这是怎么回事?在调用mxCreateStructMatrix时,两者都被正确分配,那么这些指针如何重叠?我忽略了什么?


有没有可能使用干净的类、std::vectors等C++呢? - Gabriel
1
你需要编写哪个版本的.mat文件?最近的Matlab版本使用hdf5格式,所以你可以使用hdf5库并添加Matlab特定的信息。 - Jonas
@Gabriel:我从未使用过C++,只用过C。 - Emanuel Ey
@Jonas:我正在编写第6版mat文件。我也检查了hdf5,但整个重点是不必更改我的现有代码库,该库已使用Matlab API来编写文件。这就是为什么我正在编写具有与API中找到的相同名称的函数。 - Emanuel Ey
@EmanuelEy:也许我对你的问题有所误解,但是matfile类型不仅取决于文件写入API吗?换句话说,你可以不使用或逆向工程Matlab API来编写v6 mat文件,而是可以创建更简单的v7代码。再次强调,我可能对你的情况有所误解,并且我不了解你的所有限制,但是如果将来无法使用Matlab API,考虑选择编写v7可能是值得考虑的。 - Jonas
@Jonas:所以我已经解决了最初的错误,并且已经使用我的“matOut”代码写入mat文件几周了。它可能不是最漂亮的代码,但它能用,所以现在已经上传到github上了:http://github.com/EyNuel/matOut/wiki - Emanuel Ey
1个回答

2
我认为问题出在你最后一句话上:
mxSetFieldByNumber( mxStruct,   0,  /* THIRD FIELD */ 3,  mxSubStruct);

您正在尝试将mxStruct的第三个字段分配为另一个嵌套结构变量,问题在于mxStruct仅定义了两个字段:

mxStruct = mxCreateStructMatrix(1, 1, /* TWO */ 2, fieldNames1);

与MATLAB不同,据我所知,您的代码不支持即兴添加结构字段:

%# -- MATLAB code --
s = struct('f1',[], 'f2',[]);
s.f3 = 99;       %# add a new field

这并不难实现,你只需重新分配指针数组以容纳一个更多的字段,并增加字段计数。


实际上,您正在尝试访问结构的第四个字段(在C中基于零的索引)。 - Amro
关于动态添加结构字段:这是一个有用的功能,也许我以后会实现它 - 谢谢! - Emanuel Ey
2
虽然你的答案不完全正确,但它让我再次检查了我的代码,结果发现问题出在我写入一个不存在的结构体数组位置上——如此简单...但是,这也意味着我的代码实际上是有效的 :) 只需要添加一个更多的条件来验证输入即可。 - Emanuel Ey
这实际上是我正在开发的一个开源项目的一部分(用于水声射线追踪模型)。我可能会将其拆分并在Github上发布。Scilab实际上使用libmatio与mat文件交互,不确定Octave是否也是如此 - 感谢您指出这一点。 - Emanuel Ey
1
这个小项目我已经在日常使用中一段时间了,所以我将它上传到 github - Emanuel Ey
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接