过程与上一篇《用C#开发SAP2000第一个插件》很相似,因为从本质上讲,都是写一个 COM 对象,给SAP2000主程序调用,从而实现插件功能。
我的开发环境:Win7 SP1 + Visual Studio 2010 + SAP2000 v15.2.1
1. 首先启动VS2010,新建一个 ATL 项目。我们需要 VC++ 中的 ATL 框架来实现COM架构。
2. 在Class View中,添加一个类,选择 ATL Simple Object,设置如下图所示。其中ProgID的设置很重要,必须为 LibraryName.cPlugin 的形式。(结构狮会编程http://cnblogs.com/sap2000/ 版权所有)
3. 在下一步的设置中,一定要选择“Dual Interface”也就是双接口。双接口允许以IDispatch接口调用COM中的方法,这对SAP2000主程序很重要,原因在后面讲。
4. 下一步是在新建立的 _cPlugin 接口中,添加方法。SAP2000支持两个方法,分别是用作插件主程序的 Main() 方法,一个显示插件信息的 Info() 方法。它们在IDL接口定义文件中的声明如下:
[id(1)] HRESULT Main([in,out] _cSapModel** ppSapModel, [in,out] _cSapPlugin** ppSapPlugin); [id(2)] HRESULT Info([in,out] BSTR* info, [out,retval] LONG* pRetVal);
5. 添加好方法之后,就可以在 cpp 文件中开始写程序了。我写好的两个方法的示例代码如下:
主方法Main()
// Main method for sap2000 plugin // Demonstrated by http://cnblogs.com/sap2000/ STDMETHODIMP CPlugin::Main(_cSapModel** ppSapModel, _cSapPlugin** ppSapPlugin) { // com calling result HRESULT hr; // parameters long num = 0; SAFEARRAY* pObjTypes = NULL; SAFEARRAY* pObjNames = NULL; // call sap2000 api hr = (*ppSapModel)->SelectObj->GetSelected(&num, &pObjTypes, &pObjNames); // check return value using pre-defined macro if(SUCCEEDED(hr)) { // prepare message TCHAR *str = new TCHAR[512]; wsprintf(str, _T("Selected %ld object"), num); // message box output ::MessageBox(NULL, str, _T("Selected"), MB_OK | MB_ICONINFORMATION); delete[] str; } if(ppSapPlugin) { // return from sap2000 plugin to the main program // required by sap2000 (*ppSapPlugin)->Finish(0); } return S_OK; }
插件信息方法 Info()
// Information method for sap2000 plugin // Demonstrated by http://cnblogs.com/sap2000/ STDMETHODIMP CPlugin::Info(BSTR* info, LONG* pRetVal) { // text output const TCHAR *pText = _T("Count Selected Object.\r\nPlugin by xiaoding."); // output SysReAllocString(info, pText); // return zero to indicate a successful call, required by sap2000 (*pRetVal) = 0; return S_OK; }
6. 这些方法中,用到了 SAP2000 中导出的类型,现在编译器还不识别,我们需要导入相关的TypeLib,以顺利完成编译。将SAP2000安装目录下的SAP2000.TLB文件拷到VC++的工程目录中,与源程序文件放在一起。然后在 stdafx.h 头文件中,添加以下代码导入sap2000的typelib(此代码引用自CSI的OAPI帮助文件)
#import "Sap2000.tlb" high_property_prefixes("Get_","Put_","PutRef_") no_smart_pointers no_namespace raw_native_types rename("min", "sap2000v12_min") rename("SetProp", "sap2000v12_SetProp") rename("GetProp", "sap2000v12_GetProp") rename("Yield", "sap2000v12_Yield")
注意这些代码应该写在一行上。这样就向项目中导入了sap2000的typelib,可以使用导出的类型了。
7. 现在程序还不能编译,原因是IDL文件中也使用了相关的导出类,导致IDL文件编译失败。我们要向IDL文件中导入sap2000.tlb这个类型库,在IDL文件的library定义中,加入 importlib("sap2000.tlb"); 这一句。另外,还要把 interface _cPlugin 的定义移到 library 的定义中,放到 importlib 语句的后边。这样MIDL编译器才能在导入sap2000.tlb类型库后识别我们在interface中使用到的自定义类型。
我的IDL文件定义如下,其结构可以作为参考。(默认生成的IDL文件,其interface的定义是在library上面的)
// ObjectCount.idl : IDL source for ObjectCount // Courtesy by http://cnblogs.com/sap2000/ // This file will be processed by the MIDL tool to // produce the type library (ObjectCount.tlb) and marshalling code. import "oaidl.idl"; import "ocidl.idl"; [ uuid(B6B0D7FC-FBDF-4734-BAB0-B95B80A26D3D), version(1.0), ] library ObjectCountLib { importlib("stdole2.tlb"); importlib("sap2000.tlb"); [ uuid(43393962-C98D-4AC0-BF4F-F636D2032ACC) ] coclass Plugin { [default] interface _cPlugin; }; [ object, uuid(C54C20F7-E99D-4AD9-A4C8-1D7EC50753A5), dual, nonextensible, pointer_default(unique) ] interface _cPlugin : IDispatch{ [id(1)] HRESULT Main([in,out] _cSapModel** ppSapModel, [in,out] _cSapPlugin** ppSapPlugin); [id(2)] HRESULT Info([in,out] BSTR* info, [out,retval] LONG* pRetVal); }; };
8. 这样,我们的设置就顺利完成了,可以成功编译。然后在SAP2000中添加相应的插件。插件的截图如下:
可以看到插件已经添加成功。可以从菜单中调用:
这是调用结果:
同时,在插件管理界面,也能向用户显示关于插件的信息。我们这里显示了一小段程序和版权信息,内容与我们在Info()方法中设置的相同(废话!),如图:
以上就是用C++为SAP2000编写插件的全部介绍了。由于CSI的官方文档不够全面,很多技术问题写得比较模糊,网上的介绍又非常少(包括英文的也很少)。很多地方都是我自己一点点摸索出来的,在这里介绍出来,也算是给后来者一点帮助。
--
Courtesy of 结构狮会编程