关于SAP2000插件开发的一些总结

摸索了这么多,总结一下SAP2000插件的开发,以下总结的部分信息引用自CSI SAP2000的英文帮助文档。(中文文档是从英文翻译过来的二手文档,有时候不够新。所以情况许可的话尽量阅读英文文档)

SAP2000的帮助文档中,关于插件的开发,写给开发者的有这么几条,以下分别解读。

  • SAP2000 will look for the plug in by Type Library name (usually the name of the PlugIn project), which you define when developing your COM server DLL. We suggest using a unique name such as SAP2000PlugIn_xxx_yyy, where xxx is your (company) name, and yyy is the name to distinguish the plug in from other plug ins that you develop. For example, SAP2000PlugIn_ABCinc_Template1.

  • SAP2000 is not looking for specific GUID’s. Choose unique GUIDs for the plug in.

以上是说,SAP2000的插件是一个COM服务。SAP2000在调用COM的时候,不会通过GUID(对COM来说也就是CLSID)来调用特定对象,而是"look for the plug in by Type Library name"(通过类型库名字来调用插件)。 其实这句话写得相当模糊。COM调用的时候,要么是CLSID,要么是ProgID。而SAP2000的主程序是VB写的(通过peid.exe查看),VB中通过名字来调用COM对象的,主要就是 CreateObject() 方法了。这个方法接受的就是COM对象的 ProgID。

所以我们在给SAP2000插件创建COM对象的时候,ProgID一定要写对。在C++中,一般是IDL文件定义的library名字加上cPlugin。 比如我的第一个C++插件,IDL中定义的名字是 library ObjectCountLib ,相应的ProgID就是 ObjectCountLib.cPlugin  (为什么要跟一个cPlugin,后面会提及)

在C#中,将Class Library导出成COM对象是由VS自动完成的,如果项目名称是 ObjectCount,类名是 cPlugin,那么生成的COM对象的ProgID就是 ObjectCount.cPlugin 这样就对了。

  • The plug in must reference the Sap2000 v12 type library, which must be registered on the developer’s and the user’s systems.

  • Before SAP2000 can call the plug in, the plug in must be registered for COM (or for COM interop for .NET DLLs) on the user’s system. It is your responsibility to instruct the user how to install the plug in. This should be done after SAP2000 has been installed. 

这里是说Plugin程序必须引用 sap2000 的类型库(SAP2000.TLB),并注册为COM对象。注册过程需要开发者去完成(一般做成安装程序在安装的时候注册COM)。这两条都是很自然的事情,因为不导入 SAP2000.TLB 根本没法完成编译。

  • We will attempt to maintain a stable interface in SAP2000, however, that cannot be guaranteed, and updates to your plug in may be required for future versions of SAP2000. 

这一条是说插件的接口在以后的版本中可能会变动,这是以后的事情了,目前是活在当下做好眼前事。

  • All functionality must be implemented in a class called cPlugin.

  • Class cPlugin must have a subroutine  cPlugin.Main() that expects a reference to Sap2000.cSapModel and Sap2000.cSapPlugin:

               Public Sub Main(ByRef SapModel As Sap2000.cSapModel, ByRef ISapPlugin As Sap2000.cSapPlugin)

这里是说,所有的功能要写在一个名为cPlugin的类中,而且在cPlugin中得有一个规定好的 Main() 方法作为插件的主体程序。这就是SAP2000向插件开发者开放的接口了。其实这里说得不太对,对于VB,VC#来说,写一个名为cPlugin的类是必须的(见第一条解读,因为它们的ProgID是由程序根据类名自动确定的,为了生成规定格式的ProgID,必须有一个cPlugin类);而对于C++来说,并无必要。事实上,我在第一个C++插件的例子中,就把类名定成Plugin,只是把ProgID设成了ObjectCountLib.cPlugin 。 由于C++中可以自己定义ProgID,所以我们只需要按规定写好ProgID即可,没必要死搬硬套地写一个cPlugin类。

  • Class cPlugin may have an optional function cPlugin.Info() that expects a reference to a string, and returns a long.  The return value should be zero if successful. The string is to be filled in by the function, and may be plain text or rich text. If this function is found and returns zero, the string will be displayed when the user first adds the plug in to SAP2000. You can use this string to tell the user the purpose and author of the plug in. This is in addition to any information you may provide when the user executes the plug in.

           Public Function Info(ByRef Text As String) As Integer

这一条是说插件中可以有一个可选的方法Info(),用来向用户输出一段关于此插件信息。一般我们用来显示插件的说明和版权信息。插件的返回值固定是0,表示成功。

  • Sap2000.cSapPlugin contains a Finish() subroutine that is to be called right before the plug in is ready to close (e.g., if the plug in has a single main window, at the end of the close event of that form). It expects an error flag (0 meaning no errors) to let SA2000 know if the operation was successful or not. SAP2000 will wait indefinitely for Sap2000.cSapPlugin.Finish() to be called, so the plug in programmer must make sure that it is called when the execution of the plug in code is completed.

             Public Sub Finish(ByVal iVal As Integer)

  • It is OK for cPlugin.Main() to return before the actual work is completed. (e.g., return after displaying a form where the functionally implemented in the plug in can be accessed through different command buttons). However, it is imperative to remember to call Sap2000.cSapPlugin.Finish() to return the control back to SAP2000 when the plug in is ready to close. 

这一条很重要,插件主程序Main()在结束的时候,一定要调用 cSapPlugin 中的 Finish() 方法(参数0表示成功),用来向SAP2000主程序表示插件调用结束。第二条是说,Main() 方法可以提前返回,比如在Main()中显示一个对话框,具体工作在对话框的不同按钮上完成,显示完对话框Main()方法就可以返回了。但务必在插件结束的时候调用 cSapPlugin 中的 Finish() 方法,将控制权交回给 SAP2000 主程序。

  • Our testing has indicated that modal forms in .NET DLL’s are problematic when shown within a SAP2000 external plug in, especially if you try to perform refresh operations to the views or windows.

CSI说,他们在测试中发现在SAP2000中显示 .NET 中的模态对话框会有问题(具体是什么问题没说),特别是在插件中显示模态对话框后又试图刷新SAP2000的主界面窗口。所以作为开发者,要避免这么做。

  • If you want to provide multiple functionality in your plug in, you can provide options for the user when subroutine Main is called. Options for the user to obtain information about the product, developer, technical support, and help should be provided. Support for your plug in will not be provided by Computers and Structures, Inc.

  • As currently implemented, the cPlugin object will be destroyed between invocations from the SAP2000 Tools menu command that calls it, so data cannot be saved. All operations in the plug in must be completed before the user can perform any other operations within SAP2000 itself.

这里说了两件事,其一是,想在一个插件中实现多个功能,就得显示一个选择界面,让用户来选择实现的功能。

其二是,按目前的实现,插件对象在每次调用后都会被销毁,这样插件的数据不会在内存中保存。

 

总结一下SAP2000的COM插件需要满足的要求

  1. 必须按规定格式写ProgID。一般格式是 LibraryName.cPlugin ,LibraryName就是项目的名字(C++中是IDL文件中library语句定义的名字)。在SAP2000中添加插件的时候,写的名字必须是 LibraryName。 (举例,我的C++项目名字是 ObjectCount ,而 IDL 中定义的 library 名字是 library ObjectCountLib 自动加了一个Lib上去。这样我在写ProgID的时候就必须是 ObjectCountLib.cPlugin, 在SAP2000中添加插件的时候,写的插件名就是ObjectCountLib 
  2. 必须实现双接口。其实质是要实现 IDispatch 接口。在C#中,需要在类名前加上属性 [ClassInterface(ClassInterfaceType.AutoDual)] 来实现双接口;在C++中,Class Wizzard生成类的时候,默认就是双接口。
  3. 这一切都是由SAP2000中调用方式决定的。SAP2000主程序由VB写成,猜测它调用插件的时候,是用 CreateObject() 根据我们在插件管理中输入的名字再加上 .cPlugin 后缀,生成一个特定的 COM Object ,然后直接调用 Object 中的 Main() 方法。(调用的时候由VB解释器在 IDispatch 接口中用 GetIDsOfNames() 方法查找 Main() 方法对应的 Dispatch ID,然后用 Invoke() 方法根据特定的 Dispatch ID 来调用 Main() 方法)。基于这种猜测,以上的两点就极为必要,通过开发实例,也证实了以上的猜测

--

Courtesy of 结构狮会编程

posted @ 2013-03-28 17:10  结构狮会编程  阅读(1349)  评论(0编辑  收藏  举报