跳转至

使用 ROUTINE_INFO 实现动态关键字验证

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/dynamic-keyword-validation-using-routine-info

17025 给这篇文章评分:

暂无评分

使用 ROUTINE_INFO 实现动态关键字验证

匿名作者 2014年3月27日,星期四

在 IDL 中,存在三种 _EXTRA 关键字的变体:_EXTRA_REF_EXTRA_STRICT_EXTRA_EXTRA_REF_EXTRA 用于定义例程的签名,而在调用任何例程时,可以使用 _EXTRA_STRICT_EXTRA。正如我的同事 Jim Pendleton 在几年前所写的那样,_REF_EXTRA 关键字相较于普通的 _EXTRA 是一个潜在的改进。这是因为它使用了引用传递语义,而不是 _EXTRA 所使用的值传递语义,因此您不会在每次函数调用时复制变量。其缺点是,通过 _REF_EXTRA 传递的值是可变的,所以需要特别注意,但它可以节省大量分配这些副本所需的内存和时间。

_EXTRA_REF_EXTRA 的缺点是,它们都是通用的“收纳袋”,允许调用者使用任何他们想要的、多余的参数调用您的函数,并且无法知道哪些关键字实际上被使用了,哪些是多余的。_STRICT_EXTRA 关键字应运而生,其相关内容在帮助文档“关键字继承”中有所讨论。通常,当您调用一个例程时,您使用 _EXTRA 关键字作为将关键字集合传递给例程的方式。_EXTRA 包中属于例程签名的任何关键字都会被正确映射,其余的关键字如果存在 _EXTRA/_REF_EXTRA 则会被捕获,否则将被丢弃。如果您使用 _STRICT_EXTRA 而不是 _EXTRA 调用同一个例程,那么如果有任何关键字无法映射到例程签名,它将抛出错误。我们可以通过下面这个精心设计的例子来观察其运行情况,该例子使用最简化的过程来演示验证逻辑:

pro callByValue, _EXTRA=extra
  help, extra
end
pro callByReference, _REF_EXTRA=refExtra
  help, refExtra
end
pro callWithNoExtra, FOO=foo
  help, foo
end
pro callWrapper, _REF_EXTRA=extra
  callByValue, _EXTRA=extra
  callByReference, _EXTRA=extra
  callWithNoExtra, _EXTRA=extra
end
pro callStrictWrapper, _REF_EXTRA=extra
  callByValue, _STRICT_EXTRA=extra
  callByReference, _STRICT_EXTRA=extra
  callWithNoExtra, _STRICT_EXTRA=extra
end
pro extra_tests, _REF_EXTRA=extra
  callWrapper, PI=!pi
  callStrictWrapper, PI=!pi
end

运行 extra_tests 的输出是:

** Structure <1307e5a0>, 1 tags, length=4, data length=4, refs=1:
   PI              FLOAT           3.14159
REFEXTRA        STRING    = Array[1]
FOO             UNDEFINED = <Undefined>
** Structure <1307e650>, 1 tags, length=4, data length=4, refs=1:
   PI              FLOAT           3.14159
REFEXTRA        STRING    = Array[1]
% Keyword PI not allowed in call to: CALLWITHNOEXTRA
% Execution halted at: CALLSTRICTWRAPPER      32 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
%                      EXTRA_TESTS       43 C:\Users\brian\IDLWorkspace83\Default\extra_tests.pro
%                      $MAIN$

_EXTRA_REF_EXTRA 关键字能够毫无意外地接受 PI 关键字。当 callWrapper 调用 callWithNoExtra 时,PI 关键字被丢弃,而 FOO 保持未定义状态。但是当 callStrictWrapper 调用 callWithNoExtra 时,PI 关键字与例程签名不匹配,从而抛出错误。需要注意的是,callByValuecallByReference_STRICT_EXTRA 配合使用完全正常,因为它们的 _EXTRA_REF_EXTRA 关键字(分别)会毫无问题地“吸收”掉 PI 关键字。

因此 _STRICT_EXTRA 有其局限性,特别是在您想要调用多个不包含某种形式 _EXTRA 的例程时。为了实现这一点,我们需要查询 IDL 运行时环境,使用强大的 ROUTINE_INFO 函数来获取有关例程签名的信息。当您将此函数与它的 /PARAMETERS 关键字一起使用时,它将返回一个结构,使您可以获取给定例程签名中的所有关键字列表。让我们看一个简单的例子,了解 ROUTINE_INFO 返回的内容:

pro myPro, PARAM1=p1, PARAM2=p2, PARAM3=p3
  print, 'in myPro'
  help, p1, p2, p3
end

function myFunc, PARAM2=p2, PARAM4=p4
  print, 'in myFunc'
  help, p2, p4
  return, 0
end

IDL> info1 = Routine_Info('myPro', /PARAMETERS)
IDL> info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)
IDL> info1
{
    NUM_ARGS: 0,
    NUM_KW_ARGS: 3,
    KW_ARGS: ["PARAM1" "PARAM2" "PARAM3"]
}
IDL> info2
{
    NUM_ARGS: 0,
    NUM_KW_ARGS: 2,
    KW_ARGS: ["PARAM2" "PARAM4"]
}

结构的隐含打印输出很方便,因为它显示了标签,并且还展开了数组,而 helpprint 命令对于结构体都不会这样做。您会注意到,当我请求关于函数(而非过程)的信息时,我不得不添加 /FUNCTIONS 关键字。因此,我可以使用 info 结构体的 KW_ARGS 成员来标识要为每次例程调用使用的 _REF_EXTRA 包中的哪些成员。我还可以用它来验证没有多余的参数传入包装器。

一旦验证了传入包装器的关键字是有效的,您就需要构建将要传递到每个被包装例程中的值子集。动态实现此操作的方法是通过手动构建一个结构体传递给 _EXTRA 关键字。我们通过使用 CreateStruct 仅将那些关键字和值增量地追加到结构体来实现。当使用 _REF_EXTRA 时,您会得到一个包含用于调用您方法的关键字的字符串数组。要获取每个关键字的值,您必须使用 Scope_VarFetch 及其 /REF_EXTRA 关键字来获取本地作用域中该值的引用。以下是针对上面定义的 myPromyFunc 例程的包装器示例:

function myDynamicReferenceWrapper, _REF_EXTRA=refExtra
  compile_opt idl2
  ; 首先确保 _REF_EXTRA 已定义,否则退出
  if (~ISA(refExtra)) then return, -1

  info1 = Routine_Info('myPro', /PARAMETERS)
  info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTION)

  ; 检查 _REF_EXTRA 中的无效关键字
  foreach keyword, refExtra do begin
    if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
        (Total(keyword eq info2.KW_ARGS) eq 0)) then begin
      Message, 'Invalid keyword ' + keyword
    endif
  endforeach

  ; 使用 _REF_EXTRA 中的适当关键字调用 myPro
  extra1 = {}
  foreach keyword, info1.KW_ARGS do begin
    w = where(keyword eq refExtra, found)
    if (found gt 0) then begin
      value = Scope_VarFetch(keyword, /REF_EXTRA)
      extra1 = Create_Struct(extra1, keyword, value)
    endif
  endforeach
  myPro, _EXTRA=extra1

  ; 使用 _REF_EXTRA 中的适当关键字调用 myFunc
  extra2 = {}
  foreach keyword, info2.KW_ARGS do begin
    w = where(keyword eq refExtra, found)
    if (found gt 0) then begin
      value = Scope_VarFetch(keyword, /REF_EXTRA)
      extra2 = Create_Struct(extra2, keyword, value)
    endif
  endforeach
  return, myFunc(_EXTRA=extra2)
end

如果您担心被包装的方法会修改您的变量,并希望使用 _EXTRA 而不是 _REF_EXTRA,那么可以对此包装器进行一些调整:

function myDynamicValueWrapper, _EXTRA=extra
  compile_opt idl2
  ; 首先确保 _EXTRA 已定义,否则退出
  if (~ISA(extra)) then return, -1

  info1 = Routine_Info('myPro', /PARAMETERS)
  info2 = Routine_Info('myFunc', /PARAMETERS, /FUNCTIONS)

  ; 检查 _EXTRA 中的无效关键字
  myExtraKeywords = Tag_Names(extra)
  foreach keyword, myExtraKeywords do begin
    if ((Total(keyword eq info1.KW_ARGS) eq 0) && $
        (Total(keyword eq info2.KW_ARGS) eq 0)) then begin
      Message, 'Invalid keyword ' + keyword
    endif
  endforeach

  ; 使用 _EXTRA 中的适当关键字调用 myPro
  extra1 = {}
  foreach keyword, info1.KW_ARGS do begin
    w = where(keyword eq myExtraKeywords, found)
    if (found gt 0) then begin
      extra1 = Create_Struct(extra1, keyword, extra.(w[0]))
    endif
  endforeach
  myPro, _EXTRA=extra1

  ; 使用 _EXTRA 中的适当关键字调用 myFunc
  extra2 = {}
  foreach keyword, info2.KW_ARGS do begin
    w = where(keyword eq myExtraKeywords, found)
    if (found gt 0) then begin
      extra2 = Create_Struct(extra2, keyword, extra.(w[0]))
    endif
  endforeach
  return, myFunc(_EXTRA=extra2)
end

通过添加一个字符串数组参数来指定您想要包装的例程名集合,而不是将 myPromyFunc 硬编码,上述任何一个包装器都可以变得真正通用。

无人机日益扩大的作用 您的雷达上有什么?