跳转至

如何从 Call_Procedure 检索输出关键字

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/how-to-retrieve-output-keywords-from-call-procedure

14158 对此文章评分:

暂无评分

如何从 Call_Procedure 检索输出关键字

匿名 2015年2月26日,星期四

在 IDL 中工作时,常常会觉得有些事情就是无法实现。直到你与知道一些你从未想到过的语言技巧的合适人选交谈后,情况才会改变。就我而言,这发生在我为封装任何 IDL 过程而开发新的 ENVITask 子类时。我想使用 Call_Procedure 来调用它,但无法弄清楚如何从过程中获取输出关键字的值,因此不得不采用更灵活但更慢的 Execute() 方法。后来,我与 ENVI 原始版本创作者、全方位的 IDL 专家 Adam Lefkoff 进行了交谈,他向我展示了一个可以利用并与 Call_Procedure 配合使用的技巧。

在演示解决方案之前,让我先退一步更好地描述这个问题。假设有一个你想调用的 IDL 过程,但你只有作为字符串字面量的关键字名称,而不是在编译时拥有它们的先验知识。也许它们来自数据库或外部源。调用这个过程你有两种选择:Execute 和 Call_Procedure:

pro testProcedure, FOO=foo, BAR=bar, _REF_EXTRA=refExtra
  compile_opt idl2

  help, foo, OUTPUT=fooOut
  help, bar, OUTPUT=barOut
  print, fooOut, barOut

  if (ISA(refExtra)) then begin
    foreach keyword, refExtra do begin
      value = Scope_VarFetch(keyword, /REF_EXTRA)
      help, value, OUTPUT=valueOut
      print, keyword, valueOut
    endforeach
  endif
end

pro Execute_Wrapper, procName, _REF_EXTRA=refExtra
  compile_opt idl2

  params = Hash()

  foreach keyword, refExtra do begin
    params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
  endforeach

  print, 'in Execute_Wrapper'
  command = procName + ', _EXTRA=params.ToStruct()'
  ret = Execute(command)
end

pro Call_Procedure_Wrapper, procName, _REF_EXTRA=refExtra
  compile_opt idl2

  params = Hash()

  foreach keyword, refExtra do begin
    params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
  endforeach

  print, 'in Call_Procedure_Wrapper'
  Call_Procedure, procName, _EXTRA=params.ToStruct()
end

pro test_procedure_wrapping
  compile_opt idl2

  Execute_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2)
  Call_Procedure_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2)
end

这个例子可能有点刻意,但它演示了如何 Scope_VarFetch() _REF_EXTRA 中的每个值并将它们放入 Hash 对象中。然后在调用过程时将 Hash 转换为结构体,并通过将该结构体分配给 _EXTRA 关键字,它将适当地连接所有关键字。我们从输出中可以看到 Execute 和 Call_Procedure 都能工作:

in Execute_Wrapper
FOO             STRING    = 'foo'
BAR             FLOAT     =       3.14159
BAZ VALUE           LIST  <ID=1  NELEMENTS=2>
QUX VALUE           HASH  <ID=6  NELEMENTS=1>
in Call_Procedure_Wrapper
FOO             STRING    = 'foo'
BAR             FLOAT     =       3.14159
BAZ VALUE           LIST  <ID=31  NELEMENTS=2>
QUX VALUE           HASH  <ID=36  NELEMENTS=1>

问题是这个过程只有输入关键字,没有输出关键字。如果我们希望能够动态调用具有输出关键字的过程,则需要付出更多的努力。以下是更新后的 Execute 包装器版本:

pro testProcedure, FOO=foo, BAR=bar, OUT_HASH=outHash, _REF_EXTRA=refExtra
  compile_opt idl2

  outHash = Hash('FOO', foo, 'BAR', bar)

  if (ISA(refExtra)) then begin
    foreach keyword, refExtra do begin
      outHash[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
    endforeach
  endif
end

pro Execute_Wrapper, procName, OUT_NAMES=outNames, _REF_EXTRA=refExtra
  compile_opt idl2

  params = Hash()

  foreach keyword, refExtra do begin
    if (outNames.HasValue(keyword)) then continue
    params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
  endforeach

  print, 'in Execute_Wrapper'
  command = procName + ', _EXTRA=params.ToStruct()'
  foreach name, outNames do begin
    command += ', ' + name.ToUpper() + '=' + name.ToLower()
  endforeach
  ret = Execute(command)

  foreach name, outNames do begin
    (Scope_VarFetch(name, /REF_EXTRA)) = Scope_VarFetch(name)
  endforeach
end

pro test_procedure_wrapping
  compile_opt idl2

  execOutHash = 0
  Execute_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2), OUT_NAMES='OUT_HASH', OUT_HASH=execOutHash
  print, execOutHash, /IMPLIED
end

Execute_Wrapper 例程现在需要知道 _REF_EXTRA 中的哪些关键字是输入,哪些是输出。因此,当我们构造 Hash 时,会省略输出关键字,但随后将它们作为局部作用域中的新变量附加到命令字符串中。调用过程后,这些局部变量将具有正确的输出值,但我们需要那行看起来非常奇怪的、包含两次 Scope_VarFetch() 调用的代码,将它们复制到 _REF_EXTRA 包中,以便将它们传递回调用 Execute_Wrapper 的例程。左边的 Scope_VarFetch() 调用使用 /REF_EXTRA 关键字与调用栈连接,但我们需要将其封装在括号中以使其成为赋值操作。右边的 Scope_VarFetch() 调用用于获取要赋值的命名局部变量的值。我们还需要确保 OUT_HASH 关键字存在于对 Execute_Wrapper 的调用中,以便这个 _REF_EXTRA 技巧能够奏效。

虽然这可以工作,但看起来有点复杂,并且 OUT_HASH 关键字作为字符串字面量重复出现,显得冗余。另一个问题是它仍然在使用 Execute(),效率不如 Call_Procedure。但我们不能以同样的方式修改 Call_Procedure_Wrapper 例程并取得成功。原因在于,当构建要传递给 Call_Procedure 的结构体时,它包含的是每个关键字值的副本,而不是指向用于构建它的变量的引用。因此,我们需要引入一个新的函数来获取输出关键字的值:

pro testProcedure, FOO=foo, BAR=bar, OUT_HASH=outHash, _REF_EXTRA=refExtra
  compile_opt idl2

  outHash = Hash('FOO', foo, 'BAR', bar)

  if (ISA(refExtra)) then begin
    foreach keyword, refExtra do begin
      outHash[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
    endforeach
  endif
end

function Procedure_Wrapper, procName, _REF_EXTRA=refExtra
  compile_opt idl2

  Call_Procedure, procName, _EXTRA=refExtra

  results = Hash()
  foreach keyword, refExtra do begin
    results[keyword] = Scope_Varfetch(keyword, /REF_EXTRA)
  endforeach

  return, results
end

pro Call_Procedure_Wrapper, procName, OUT_NAMES=outNames, _REF_EXTRA=refExtra
  compile_opt idl2

  params = Hash()

  foreach keyword, refExtra do begin
    params[keyword] = Scope_VarFetch(keyword, /REF_EXTRA)
  endforeach

  print, 'in Call_Procedure_Wrapper'
  results = Procedure_Wrapper(procName, _EXTRA=params.ToStruct())

  foreach name, outNames do begin
    (Scope_VarFetch(name, /REF_EXTRA)) = results[name]
  endforeach
end

pro test_procedure_wrapping
  compile_opt idl2

  callOutHash = 0
  Call_Procedure_Wrapper, 'testProcedure', FOO='foo', BAR=!pi, BAZ=List(1,2), QUX=Hash(1,2), OUT_NAMES='OUT_HASH', OUT_HASH=callOutHash
  print, callOutHash, /IMPLIED
end

这个新函数的行为类似于修改后的 Execute_Wrapper 例程,但由于它将其所有关键字都传递给 Call_Procedure,因此它使用的是引用而不是值。然后,它可以使用 Scope_VarFetch(/REF_EXTRA) 将这些关键字中的值复制到一个 Hash 中,并返回给其调用者。在那里,我们再次使用 Scope_VarFetch(/REF_EXTRA) 将该 Hash 中的输出值复制到其调用例程的变量中。使用 OUT_NAMES 关键字并非完全必要,但它避免了将输入变量复制回调用者,这可能会也可能不会带来开销。

在 ENVITask 上下文中,我们知道哪些关键字是输入,哪些是输出,因此,与其传入 OUT_NAMES 关键字并返回一个值的 Hash,不如将 Procedure_Wrapper 设为一个成员函数,并直接设置输出参数值。但正如你在 ENVI 5.2 SP1 中将会看到的,我们可以将任何使用关键字的 IDL 过程包装在一个 ENVITaskFromProcedure 对象中,该对象知道如何自动将输入和输出关键字映射到 ENVITaskParameters。

文件存储:使用感兴趣区(ROI)工具 影像分析:如今成为好莱坞故事的一部分