【问题标题】:How to import other source code files in dm script如何在dm脚本中导入其他源代码文件
【发布时间】:2020-07-14 06:03:14
【问题描述】:

有没有办法在 dm-script 中使用多个代码文件来构造代码?比如:

import "path/to/utility_functions.s";

utility_functions.do_something_general();

请注意,如果可能,我不希望将代码作为菜单项。该代码仅包含我在主脚本中使用的函数。


我尝试了以下方法:

文件 1:test.s

void test(){
    result("test\n");
}

文件 2:require-test.s

AddScriptFileToPackage("path/to/test.s", "test", 3, "test-function", "", "", 1);

ExecuteScriptString("test()"); // works immediately but feels wrong
test(); // works after restart

现在我有以下问题:

  • 执行此脚本后我必须重新启动 DigitalMicrograph,否则 test() 不起作用(ExecuteScriptString("test()"); 有效,但使用字符串调用代码感觉不对,如果可能的话我想避免这种情况)
  • 当我再次重新启动 DigitalMicrograph 时 AddScriptFileToPackage() sais '无法添加脚本,因为包存在并且是只读的。 [...]'。有没有办法解决或者我必须使用try 块?

我觉得我在某些地方没有做错。

【问题讨论】:

  • 我现在添加了一个广泛的例子。我希望这能解决问题。
  • 根据您对您正在寻找的内容的进一步澄清,我添加了第二个答案,该答案描述了与模块和构建脚本一起使用的交互式开发范例。它还阐明了实际上可以多次调用 AddScriptFileToPackage 以在单个 DM 会话期间重新生成相同的包。无需每次重启 DM。
  • 实际上,要让上面的示例代码在不重新启动 DM 的情况下工作,您需要将文件 2 分成两个单独的脚本,一个调用 AddScriptFileToPackage,另一个实际使用新添加的代码。跨度>

标签: import require libraries dm-script code-structure


【解决方案1】:

DM 脚本不支持按需加载包,但有两种不同的方法可以实现您想要的:

使用库包

当您“安装”脚本时,您可以选择将其安装为菜单命令作为库。这对于通过菜单命令(存储在全局首选项文件中)或通过脚本命令(可用于 创建 .gtk 文件,然后可以从插件中添加/删除 文件夹)。

“菜单”选项将安装一个脚本,以便通过菜单项调用一次,但不会保留在内存中。

“库”选项将在启动时执行一次脚本并将脚本本身保持在范围内。因此,您可以在库文件中定义方法(或类)并使其普遍可用。如果您需要一些启动操作,您可以将一些可执行代码放入库中。

使用脚本库作为 .gtk 插件可能是实现您想要的推荐方式。但是,它们总是被加载。

忠告:如果您制作库,请确保您使用非常唯一的类和方法名称以避免任何冲突。我建议使用一些库名称预先修复所有类/方法名称,即使用 MyLib_MyClass 而不是 MyClass 等。

澄清:作为库包添加的脚本永久添加到软件中,即这些包被创建一次 然后放在插件文件夹中。它们将始终在 DM 启动时加载并可用。库包方法不适合临时“加载”外部脚本。 DM 脚本不支持这种“按需导入”。

创建包的脚本命令是一种实用命令,可帮助人们以一种简单且易于管理的方式创建包。通常,人们会创建一个“创建包 XY”脚本,其中包含几个这样的命令,将某个位置的所有脚本添加到包中。该脚本将被调用一次以创建包文件(之后它已经在插件文件夹中。) 仅当包含的脚本发生更改并因此需要更新包时,才会再次调用 create-package 脚本。请注意,在这种情况下,首先需要从插件文件夹中删除包文件并在不加载它的情况下启动 DigitalMicrograph,以便创建一个 new 包。否则脚本会追加到包中,如果包中已经存在同名的方法,这是不可能的。

F1 帮助文档有一个示例脚本:


典型示例,使用 GMS 3.4.0

脚本存储在:C:\Tmp\testLib.s

void TestCall()
{
  Result("\nTest")
}

脚本存储在:C:\Tmp\menuAction.s

Result("\nPerforming an action here.")

一次性运行脚本安装包:

// General package parameters
// *********************************************
string pkNa = "myPkg"       // Filename of plugin
number pkLe = 3             // level 3 (.gtk) only needed for load order
string pkLo = "user_plugin" // plugin location

string scriptRoot = "C:\\Temp\\"        

// List of Scripts to be installed as menu items
// *********************************************
// Each entry needs a (unique) command-name, a menu-name and an optional sub-menu name. 
// The "isLibary" flag is set to 0
// It is possible to add the same script multiple times. The script will be executed when the menu item
// is chosen. Methods and Classes of the script are not available otherwise
// A separator can be added by installing and empty script with a (unique) command name starting with "-"
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 1", "MyMenu", "MySubMenu", 0 ) 
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 2", "MyMenu", "", 0 ) 
AddScriptToPackage( "", pkNa, pkLe, pkLo, "-sep1", "MyMenu", "", 0 ) 
AddScriptFileToPackage( scriptRoot + "menuAction.s", pkNa, pkLe, pkLo, "Call 3", "MyMenu", "", 0 ) 

// List of Scripts to be installed as library 
// *********************************************
// Each entry needs a (unique) command-name. Menu-name and sub-menu name are "". 
// The "isLibary" flag is set to 1
// The script will be executed once on startup (if there is executable code). It is also executed once
// here during the install.
// Methods and Classes of the script are permanently available and need unique names.
// Adding a script to the package as libary can be used to create on-load-version info output.
AddScriptFileToPackage( scriptRoot + "testLib.s", pkNa, pkLe, pkLo, "library-1", "", "", 1 ) 
AddScriptToPackage( "Result(\"Script packages myPkg loaded.\\n\")", pkNa, pkLe, pkLo, "myPkg-versionInfo", "", "", 1 )

运行安装脚本后会出现:

  • 这样的菜单:
  • 在结果窗口中输出如下:
  • 文件夹C:\Users\USERNAME\AppData\Local\Gatan\Plugins\myPkg.gtk中的一个包文件
  • 脚本命令TestCall()在所有脚本中普遍可用。

只要 .gtk 文件保留在 plugins 文件夹中,每次 DM 启动时都会加载该包。

从脚本中调用脚本代码

脚本语言支持从脚本中调用脚本的两个命令:

  • Number ExecuteScriptString( String text )

  • Number ExecuteScriptFile( String file_path )

使用命令从光盘执行脚本可以做你想做的事,但以这种方式维护一个有用的“库”可能很乏味。它也不允许您安装类。


从脚本中调用脚本的示例:

// Direct example
void Demo()
{
    ClearResults()
    Result( "I am a test call.\n")
    number n = 5
    Result( "I am working on the number: " + n )
}
Demo()

//Having the script as a string
number otherNumber = 11 // To show how you can modify a script call as an example
string scriptStr
scriptStr += "void Demo()\n{" + "\n"
scriptStr += "ClearResults()" + "\n"
scriptStr += "Result( \"I am a test call.\\n\")" + "\n"
scriptStr += "number n = " + otherNumber + "\n"
scriptStr += "Result( \"I am working on the number: \" + n )"+ "\n"
scriptStr += "}\n"
scriptStr += "Demo()\n"

If ( TwoButtonDialog("Script-call","Show it", "Run it") )
{
    ClearResults()
    Result( scriptStr ) 
}
else
    ExecuteScriptString( scriptStr )

【讨论】:

  • 根据个人经验,我可以确认使用 AddScriptFileToPackage 函数来构建多模块库包是一个很好的方法。它允许通过等效的构建脚本自动生成软件包。我在创建一个完整的类库框架中广泛使用了这种方法,这些类库显着扩展了 DM 以及基于该框架(Enabler)构建的应用程序包的功能。
  • 我尝试了您的解决方案,它确实有效。仍然可以添加一个小例子吗?我在您对我的问题的解决方案中添加了一些细节,但我仍然觉得我不应该提出新问题。
  • 好的,非常感谢您的示例。我想我会使用ExecuteScriptFile() 并处理它而不是处理对象。使用AddScriptFileToPackage() 不能与think 开发一起使用。
  • @miile7 如果您要走这条路,那么一个想法是让脚本创建“模板”对象,将它们保存在内存中,并报告 objectID。因此调用脚本可以通过 ID 获取这些对象,然后使用它的方法。它虽然有点破解...
  • 我今天玩了一下。我无法克服的障碍是,从脚本调用的 any 命令或方法需要在解释器启动时为脚本所知,对于对象的方法也是如此。所以不可能“导入”一个脚本并在同一个脚本执行中使用它的方法,除非它们事先明确声明为一个接口——这违背了目的。
【解决方案2】:

以下构建脚本使用的显式示例可能更接近您要查找的内容。它表明,在单个 DM 会话过程中,可以编辑模块源文件并重复重建包,而无需重新启动 DM,这与 BmyGuest 的答案中提供的关于包创建的说明相反。该示例还使用了非常方便的 GetCurrentScriptSourceFilePath 函数,当您可以将构建脚本和模块源文件定位在同一文件夹中时,该函数大大简化了文件路径引用(这是我在自己的开发项目中采用的方法)。

这是我在这个例子中的文件安排:

这两个源模块都是非常简单的函数和类库。

这是模块 1:

    void Module1SayHello()
    {
        OKDialog("Hello from module 1");
    }

这是模块 2:

    class Module2TestClass
    {
        void Module2SayHello(Object self)
        {
            OKDialog("Hello from module 2");
        }
    }

这是构建脚本:

    void main()
    {
        // Establish the source code directory relative to the current build script location
        String buildScriptSourceFilePath;
        GetCurrentScriptSourceFilePath(buildScriptSourceFilePath);
        String sourceFileDir = buildScriptSourceFilePath.PathExtractDirectory(0);

        // Add the modules
        AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module1.s"), "MultiModuleTest", 3, "Module1", "", "", 1);
        AddScriptFileToPackage(sourceFileDir.PathConcatenate("Module2.s"), "MultiModuleTest", 3, "Module2", "", "", 1);
    }

    main();

与上述说明相反,此构建脚本可以在 DM 会话期间运行多次,并且每次都会替换包文件的内容。所以现在有一个非常好的开发环境,可以打开一个模块的源文件,根据需要编辑它,保存它,然后重建包文件。可以使用以下测试脚本来查看行为在编辑、保存和重建模块源文件中的任何函数或方法的实现时发生变化:

    void main()
    {
        Module1SayHello();
        Alloc(Module2TestClass).Module2SayHello();
    }

    main();

由于 DM 脚本解释器解析、标记和执行代码的方式,脚本中任何地方调用的所有函数和方法都必须在执行脚本之前预先定义。这就是为什么上述测试脚本或任何其他使用添加的模块的脚本不能简单地附加到构建脚本的末尾(除非嵌入在传递给 ExecuteScriptString 函数的字符串中,如提出的问题中所指出的那样) .因此,导入代码模块的概念(例如在 Python 中)在 DM 脚本中实际上是不可能的(正如 BmyGuest 对答案的评论中所指出的那样)。从这个意义上说,DM 脚本源于 1990 年的编码概念,该概念通常涉及单独的编译、链接和执行阶段。

尽管如此,此处描述的构建脚本方法允许人们利用真正的集成开发环境 (IDE) 的功能。例如,可以将模块源文件(和构建脚本)添加到 Visual Studio 中的项目中,并获得现代多文件代码编辑器和修订控制(例如通过 Git)的所有好处。这就是我使用 Enabler 框架所做的事情。

需要注意的是,一旦 DM 会话关闭,插件(包)文件确实会以某种方式最终确定,因此在未来的 DM 会话中无法再被构建脚本替换。在这种情况下,必须先从插件文件夹中删除包文件,然后才能在 DM 中恢复另一个开发会话(如 BmyGuest 的说明中所述)。

【讨论】:

  • 感谢您的回答。在弄清楚它是如何工作的之后,我认为这尽可能接近。但是您能否补充一下,正如@BmyGuest 在他的回答中提到的那样,不可能在Build MultiModuleTest 中执行任何模块功能。你的回答对我来说还不够清楚,这与通常的import 陈述非常不同。
  • @miile7 希望上面的修改能让事情更加明确和清晰。
  • 感谢迈克的广泛回答。即使在使用 DM 脚本多年之后,也总是需要学习新的东西!以前从未见过GetCurrentScriptSourceFilePath 命令,太酷了!
【解决方案3】:

对于其他需要这个的人,我现在使用AddScriptFileToPackage(),受到@BmyGuest 和@MikeKundmann 两者的启发。

以下main.s 始终在我的 GMS 中打开。我正在处理的真正代码在program.s。要测试您的代码执行main.s。此文件可以在一个会话中执行多次

为了打开 GMS,我使用下面的 (Windows) 批处理文件。这会自动删除已注册的插件,从而使 main.s 再次可用。为了调试,我创建了一个 python 脚本,它结合了main.s 中列出的所有文件。这样 GMS 会跳转到错误。这个python程序可以是downloaded from my github page

/**
 * File: main.s
 */

String __file__;
GetCurrentScriptSourceFilePath(__file__);
String __base__ = __file__.PathExtractDirectory(0);

/**
 * Load and add the file `filename`, the name will be the `filename` without
 * the extension.
 *
 * This is dynamic only for the current session. If GMS is restarted, using 
 * this will create errors except if the plugins folder does not contain the 
 * required files (delete `%LOCALAPPDATA%\Gatan\Plugins\` before starting).
 *
 * @param filename The filename (or path) relative to the path of this file
 * @param name The internal name to register the script with
 */
void require(String filename, String name){
    // AddScriptFileToPackage(
    //    <file_path>, 
    //    <packageName: filename of .gtk file in plugins>, 
    //    <packageLevel: load order [0..3]>,
    //    <command_name: id/name of the libary/command>,
    //    <menu_name: name of the menu, ignored if isLibrary=1>
    //    <sub_menu_name: name of the submenu, ignored if isLibrary=1>,
    //    <isLibrary: wheter to add as library (1) or as menu item (0)>
    // )
    AddScriptFileToPackage(__base__.PathConcatenate(filename), "__require_main_" + name, 3, name, "", "", 1);
}

/**
 * Require the file `filename` with the basename of the `filename` as the name.
 *
 * @see require(String filename, String name);
 *
 * @param filename The filename (or path) relative to the path of this file
 */
void require(String filename){
    require(filename, PathExtractBaseName(filename, 0));
}

void main(){
    // add libaries
    require("string-lib.s");

    // add main file
    require("program.s");
}

main();

用于启动 GMS 的 (Windows) 批处理文件。这会自动删除插件文件夹。那么main.s 不会造成任何问题。

@echo off

rem
rem File: start-gatan.bat
rem ---------------------

echo Deleting GMS cached libaries...

SET plugins_path=%LOCALAPPDATA%\Gatan\Plugins\
SET gms_path=%PROGRAMFILES%\Gatan\DigitalMicrograph.exe

if exist %plugins_path% (
    echo Deleting all .gtk files in %plugins_path%...
    del %plugins_path%__require_main_*.gtk /F /Q
    del %plugins_path%__require_main_*.gt1 /F /Q
    del %plugins_path%__require_main_*.gt2 /F /Q
    del %plugins_path%__require_main_*.gt3 /F /Q

    if exist "%gms_path%" (
        echo Starting GMS
        start "" "%gms_path%"
    ) else (
        echo GMS path %gms_path% does not exist.
        pause
    )
) else (
    echo Plugins path %plugins_path% does not exist.
    pause
)

【讨论】:

  • 小心删除插件文件夹中的所有 .gtk 文件!其中相当一部分是主安装的正确安装包,而不仅仅是用户安装的包,例如 f.e. VolumeTools.gtkMultivariate Histogram Analysis.gtk。其中一些甚至可能需要在 Camera Manager Library.gt1 等显微镜 PC 上运行硬件。
  • @BmyGuest 嗯。很好的一点。我没有使用任何库目前,但我知道会有一些。我想我会花几个小时来解释为什么它不起作用,只是因为我自己删除了它们。 - 我在导入的文件文件名之前添加了__require_main_ 前缀,现在我只删除以__require_main_ 开头的文件。这应该可以解决它。
猜你喜欢
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 2011-09-21
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 2013-12-25
  • 2021-09-14
相关资源
最近更新 更多