2

我想为 Matlab 代码使用面向对象的设计,然后将其转换为 C,最后它应该用于 DSP - 处理器。

我不确定的是如何进行错误处理。据我所知,在 C 中它应该与枚举一起使用,如果我在我的 matlab 代码中使用异常,我不确定 matlab 将如何将它转换为 C。

另一种可能性是实现枚举类并使用它而不是异常。

由于我在软件架构方面没有太多经验,因此欢迎任何建议。

4

1 回答 1

2

这是一个相当古老的问题,但我认为这是一个非常重要的问题,因为它涉及到一些关于如何最好地使用 MATLAB Coder 和构建代码的基本问题。

一个好的错误处理系统对于任何中等规模的代码库都非常重要,因此使其结构化很重要。在针对 MATLAB Coder 的代码中,您不能使用任何正常的异常函数(try、catch、error 等),甚至“断言”在 Coder 中也具有特殊含义。这意味着您需要自己创建一个良好的错误处理系统。这是不幸的,但并非不可克服。

您可以使用不同的错误代码创建一个枚举类型并返回适当的枚举。这是一种非常标准的 C 做事方式,它可以非常清晰地从 MATLAB 转换为 C。它有几个缺点:

  1. 您必须维护枚举类以添加每个错误条件。
  2. 它不直接促进堆栈跟踪。
  3. 相同的错误很可能会在不同的地方重复使用,这不允许对错误位置进行明确的跟踪。
  4. 它不提供人类可读的错误语句。
  5. 它不允许返回运行时数据,例如在可能导致错误的底层函数中使用的变量值。

这些都是内置 MATLAB 系统确实提供的所有东西,所以拥有其中一些东西会很好。

在我的代码库中,我开发了一个非常适合我的错误系统。它使用 Coder 中一些强大的工具来制作一个干净、易于使用的错误系统,看起来有点像 MATLAB 自己的错误系统。它还为最终调用 Coder 生成的库的程序中的错误处理提供了很好的连接。

第一个函数是错误容器。这是一个包含存储运行时错误的持久变量的函数。

function [ idnum, errorid, errorstring, inneridnum ] = errorcontainer(functionname, varargin ) %#codegen
%ERRORCONTAINER The container function for error processing
%   This function contains the error ID table, and can assign new error
%   codes or look up error codes.  
%  
%   Examples:
%     To register a new error and retrieve a unique error ID:
%       idnum = errorcontainter( 'register', ID, MESSAGE )
%       where ID and MESSAGE are strings.  ID is a short machine readable
%       string, and MESSAGE is human-readable.
%
%   Optionally, an INNERIDNUM can be provided to indicate an error code for
%   a inner (lower-level) error related to this.  This will allow
%   chaining of error messages:
%
%       idnum = errorcontainter( 'register', ID, MESSAGE, inneridnum )
%
%   ID and MESSAGE can have maximum lengths of 2048 characters.  Anything
%   longer will be truncated.
%
%     e.g.  idnum = errorcontainer('register', ...
%                                  'FOO_INVALIDTYPE', ...
%                                  'First input must be an int.');
%           idnum will be a negative int32 returned.
%
%        If the ID matches an existing ID, the message will be overwritten
%        and the same idnum will be returned (the database does not grow).
%
%     To lookup an existing error code:
%        [idnum, errorid, errorstring] = errorcontainer('lookup', idnum);
%
%   See also:  errorcode2string, registererror


persistent IDLIST;
persistent ERRORSTRINGLIST;
persistent INNERID;

width = 2048;

if isempty(IDLIST)
    IDLIST = char(zeros(1,width));
    tempstr = 'ERRORCONTAINER_UNKNOWNERRORID';
    IDLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(ERRORSTRINGLIST)
    ERRORSTRINGLIST = char(zeros(1,width));
    tempstr = 'Unknown Error';
    ERRORSTRINGLIST(1,1:numel(tempstr)) = tempstr;
end
if isempty(INNERID)
    INNERID = zeros(1,1,'int32');
end

coder.varsize('IDLIST', 'ERRORSTRINGLIST', 'INNERID', [], [1,1]);
coder.varsize('errorstring', 'errorid');

switch lower(functionname)
    case 'register'
        % First see if the listed ID matches any in the database.

        errorid = varargin{1};
        if numel(errorid) > width
            errorid = errorid(1:width);
        end

        if (nargin == 4)
            inneridnum = int32(varargin{3});
        else
            inneridnum = int32(0);
        end

        errorstring = varargin{2};
        if numel(errorstring) > width
            errorstring = errorstring(1:width);
        end

        matchindex = 0;
        for i = 1:size(IDLIST,1)
            if ( strcmpi(errorid, deblank(IDLIST(i,:))) && (inneridnum == INNERID(i) ) )
                matchindex = i;
            end
        end


        if (matchindex > 0)
            idnum = int32(-matchindex);
        else
            idnum = int32(-(size(IDLIST,1)+1));
            tempstr = char(zeros(1,width));
            tempstr(1:numel(errorid)) = errorid;
            IDLIST = [IDLIST ; tempstr];  % In Coder, cannot grow with indexing.  Have to concatinte and reassign.

            tempstr = char(zeros(1,width));
            tempstr(1:numel(errorstring)) = errorstring;
            ERRORSTRINGLIST = [ERRORSTRINGLIST ; tempstr];

            INNERID = [INNERID; inneridnum];

        end

    case 'lookup'
        idnum = varargin{1};
        tidnum = idnum;
        if ((-tidnum > size(IDLIST,1)) || (-tidnum <= 0 ))
            tidnum = int32(-1);
        end
        errorid = deblank(IDLIST(-tidnum,:));
        errorstring = deblank(ERRORSTRINGLIST(-tidnum,:));
        inneridnum = INNERID(-tidnum);

    otherwise
        idnum = int32(-1);
        errorid = deblank(IDLIST(-idnum,:));
        errorstring = deblank(ERRORSTRINGLIST(-idnum,:));
        inneridnum = int32(0);
end
end

这个函数的核心是一个大的 switch 语句,它允许选择要执行的任务。errorcontainer 函数旨在作为错误处理系统的内部存储和服务例程。

用于“抛出”错误的主要函数是registererror函数,如下所示:

function idnum = registererror(messageid, errorstring, varargin) %#codegen
%REGISTERERROR Registers a message id and error string with error system
%  Given a message ID string and an error code string (human-readable),
%  will register the strings with the error subsystem and generate a
%  negative ID number to return.
%
%  Example:
%    idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo');
%  
%  Optionally, an inner id number can be handed to the registration to
%  assocaite this error with a lower-level error previously registered.
%    idnuminner = registererror('BAR_INTERNALERROR', 'Internal error in bar');
%    idnum = registererror('FOO_INVALIDINPUT', 'Invalid input to function foo', idnuminner);
%
%  See Also: errorcode2string, errorcontainer

coder.inline('never')
switch nargin
    case 2
        idnum  = errorcontainer('register', messageid, errorstring);
    case 3
        idnum  = errorcontainer('register', messageid, errorstring, varargin{1});
end
end

registererror函数接受一个错误标记字符串(通常是发生错误的函数名称,后跟一个冒号,然后是发生了什么的一些标识符)和一个人类可读的错误消息。它在全局存储中注册错误并以 int32 形式返回错误标识符。我的代码的规则是 int32(0) 的值表示没有错误,负值是这些注册的错误标识符之一。

registererror 返回的值不是每个错误条件唯一的。它们是错误容器的引用句柄,可用于检索错误标记字符串和人类可读的字符串。

在我的代码中, registererror 的使用通常是这样的:

...
output = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (output ~= 0)
    result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename]);
    return;
end
...

在人类可读的字符串中,我可以附加来自实际执行的字符串数据。在这个例子中,我附加了在我的理论函数中失败的文件名。我将所有函数的返回值设为 int32,所有这些都可以来自对 registererror 的调用。

还有一个额外的可选调用 registererror。方便做的一件事是创建一堆错误。有时更深层次的函数中的错误会导致问题,我们想要完整的堆栈跟踪。这很容易做到,因为可以选择将内部错误 ID 交给 registererror。让我们看一个例子:

...
result = someFunctionThatShouldReturnZeroIfSuccessful(filename);
if (result ~= 0)
    result = registerror('MYFUNC:THESOMEFUNCTIONFAILED', ['The function that should return zero returned something else for filename: ', filename], result);
    return;
end
...

在这种情况下, someFunction... 返回了一个值,该值本身是通过调用 registererror 生成的,并且该结果值作为第三个参数添加到此处的 registererror 调用中,这将返回一个不同的错误值。我们可以用这个值做什么?

好吧,我们需要第三个函数,它通常可以使用我们的库通过代码调用。该函数称为errorcode2string,顾名思义,它可以采用我们的错误代码之一并返回两个字符串,以及与错误相关的任何内部错误代码。这是errorcode2string:

function [errorid, errorstring, innercode] = errorcode2string(errorcode) %#codegen
%ERRORCODE2STRING Return strings given an error code
%  Given an error code returned by any of the library functions, will
%  return a string with human-readable information about the error.  The
%  error codes are, in some cases, dynamically generated, so the codes
%  themselves should not be used for programmatic flow control except that
%  a negative value in a return always indicates an error occurred.
%
%  Example:
%    [errorid, errorstring, innercode] = errorcode2string(errorcode);
%
%  - errorcode is an int32 value. 
%
%  - errorid is a string that is machine-readable and can be used to trap
%  specific error codes.
%  
%  - errorstring is the returned 1 by N string of the human-readable error
%  information.
%
%  - innercode is the int32 error code of an inner error message if any.
%  It is a negative value of an error code if present, 0 if this is the
%  innermost error.
%
%  See Also: registererror

[~, errorid, errorstring, innercode] = errorcontainer('lookup', errorcode);

end

正如你所看到的,这个函数实际上只是错误容器的一个包装器,但是这样做可以使调用库的任何人保持整洁。

例子:

因此,让我们运行一个简单的示例。想象一个内部函数失败,并且 registererror 是这样调用的:

result = registererror('SOMEFUNC:SOMETASK', 'Something terrible happened.')
result = -2

调用函数注意到结果不是 0,而是一个负错误代码,并且它本身会抛出一个错误:

result = registererror('CALLINGFUNC:TOPTASK', 'Trying to do some high level thing failed.', result);
result = -3

这个值 -3 由我们的库返回给调用代码,因此它知道发生错误只是因为结果是否定的。然后它可以调用 errorcode2string 以获取有关错误的更多信息。

[errorid, errorstring, innercode] = errorcode2string(int32(-3))
errorid = CALLINGFUNC:TOPTASK
errorstring = Trying to do some high level thing failed.
innercode = -2

由于 innercode 仍然是负数,如果调用程序如此倾向于,它可以调用 errorcode2string 以在功能堆栈中查找更多信息。真的,这就是您找出发生的根本错误的方式。

[errorid, errorstring, innercode] = errorcode2string(int32(-2))
errorid = SOMEFUNC:SOMETASK
errorstring = Something terrible happened.
innercode = 0

现在内码为 0,所以我们知道我们可以停止。

该系统在我的项目中运行良好,希望对您有所帮助。这是我使用 Coder 时首先要弄清楚的事情之一,它教会了我很多关于 MATLAB Coder 中良好架构实践的知识,包括如何制作可全局访问的数据结构。

于 2015-04-16T02:52:24.330 回答