MSCE C#官网一步步学习搬运9 第九章、用C++/CLI编写Addins
第九章、用C++/CLI编写Addins
上一章中简单演示了如何在Addins端封装调用NativeCode端导出的函数,虽然通过PInvoke可以调用到大部分NativeCode端导出的函数,但是C/C++中一些特殊的函数是无法通过PInvoke进行封装的,例如构造函数,本章演示的例子就属于这种情况。在第零章中已经提到过CE版本上Addins中有两套开发框架,在第二章中简单演示了分别在两套开发框架下如何创建元素。在实际开发过程中,我们有时候会遇到要将元素转换到另一套开发框架下的情况。这个时候您会发现,Addins中并没有接口去完成这样的功能。但是如果您仔细研究的话,会发现两套开发框架下的Element对象都留有接口能获取到NativeCode端与元素构造相关的对象,以及从NativeCode端元素相关的对象构造其对象实例。COM框架下的Element对象有MdlElementRef和MdlElementDescrP两个成员函数可以获取到NativeCode端元素的ElementRefP和MSElementDescrP指针。Bentley.Interop.MicroStationDGN.Application 下的MdlCreateElementFromElementDescrP成员函数可以从MSElementDescrP构造出COM框架下的Element。而新的开发框架下的Element有一个ElementHandle的属性能获取到NativeCode端ElementHandle的指针。其有一个保护类型的成员函数InitializeFromElementHandle可以从NativeCode端ElementHandle的指针构造出其对象实例。在NativeCode端,ElementHandle的几个重载构造函数中有接收ElementRefP或者MSElementDescrP为参数的构造函数,且ElementHandle也有获取ElementRefP和MSElementDescrP的成员函数。所以以NativeCode的对象ElementHandle为桥梁我们就可以完成两套编程框架下的元素相互转换。如果我们用PInvoke的技术来实现的话,就需要在NativeCode端导出这样两个转换函数,一个以ElementRefP或者MSElementDescrP指针为参数以ElementHandleP指针为返回值的转换函数实现从COM框架下的Element到新的编程框架下的Element的转换。另外一个是以ElementHandleP指针为参数,以MSElementDescrP指针为返回值的转换函数实现从新的编程框架下的Element到COM框架下的Element的转换。不管是哪个方向的转换,我们都需要在Addins端有一个接收返回的指针并将其最终转换为Element对象实例。如果在C++/CLI中我们可以在一个代码块里边同时使用托管和非托管的对象,所以不管是哪个方向的转换我们只需要一个函数就能实现。
在学习本章例子之前,需要安装好Mstn CE SDK。如何获取Mstn CE SDK请参考另一篇NativeCode的学习博客。接下来就让我们来一步一步实现这两个转换函数吧。
1、在D盘建立:D:\Files\BentleyMstn\MstnCE\Mixed\SampleMixed目录。
2、启动一个文本编辑器(当然可以启动VS2015用作编辑器),在其中键入如下内容并保存为文件D:\Files\BentleyMstn\MstnCE\Mixed\SampleMixed\SampleMixed.h。该文件中含有转换函数所在类的类型声明。以及一个继承与Bentley.DgnPlatformNET.Elements.Element的类ElementDerive。
#pragma once #pragma managed #using <Bentley.MicroStation.dll> #using <Bentley.GeometryNET.Structs.dll> #using <Bentley.DgnPlatformNET.dll> #using <Bentley.Interop.MicroStationDGN.dll> #include <Mstn\MdlApi\MdlApi.h> #include <Mstn\ISessionMgr.h> #include <Mstn\MdlApi\mselems.h> #include <msclr/gcroot.h> #include <Mstn\MdlApi\dloadlib.h> USING_NAMESPACE_BENTLEY_DGNPLATFORM; USING_NAMESPACE_BENTLEY_MSTNPLATFORM; USING_NAMESPACE_BENTLEY_MSTNPLATFORM_ELEMENT; using namespace msclr; namespace GeoNet = Bentley::GeometryNET; namespace DgnNetEle = Bentley::DgnPlatformNET::Elements; namespace InteropMstn = Bentley::Interop::MicroStationDGN; namespace SampleMixed { private ref class ElementDerive :public DgnNetEle::Element { public: ElementDerive(ElementHandleP); }; public ref class ElementOperation { public: static DgnNetEle::Element^ ConvertToDgnNetEle(InteropMstn::Element^ ele); static InteropMstn::Element^ ElementOperation::ConvertToInteropEle(DgnNetEle::Element^ ele); }; }
这里定义了一个ElementDerive类,是因为我们要借助于Bentley.DgnPlatformNET.Elements.Element的InitializeFromElementHandle函数来初始化构造Bentley.DgnPlatformNET.Elements.Element。因为InitializeFromElementHandle是保护类型的成员函数,不能从类的外边访问此成员函数,所以从Bentley.DgnPlatformNET.Elements.Element派生出ElementDerive类。在ElementDerive的成员函数中我们可以访问到基类的保护类型成员,所以可以通过InitializeFromElementHandle初始化基类Bentley.DgnPlatformNET.Elements.Element的成员。而ElementDerive是从Bentley.DgnPlatformNET.Elements.Element派生出来的,当然可以作为Bentley.DgnPlatformNET.Elements.Element来使用了。
3. 在文本编辑器中再另建一个新文件,在其中键入如下内容并保存为文件D:\Files\BentleyMstn\MstnCE\Mixed\SampleMixed\SampleMixed.cpp。该文件中包含有ElementDerive和两个转换函数的实现。
using namespace System::Reflection; #include <Mstn\MdlApi\mselemen.fdf> #include <vcclr.h> #include "SampleMixed.h" namespace SampleMixed { [assembly:AssemblyDelaySignAttribute(false)]; ElementDerive::ElementDerive(ElementHandleP eehp) { InitializeFromElementHandle(*eehp); } DgnNetEle::Element^ ElementOperation::ConvertToDgnNetEle(InteropMstn::Element^ ele) { ElementDerive^ rtnEle = nullptr; if ((long)ele->MdlElementRef() != 0) { EditElementHandle eeh((ElementRefP)(void*)ele->MdlElementRef(), (DgnModelRefP)(void*)ele->ModelReference->MdlModelRefP()); rtnEle = gcnew ElementDerive(&eeh); return rtnEle; } MSElementDescrP elmdscrP = (MSElementDescrP)(void*)ele->MdlElementDescrP(false); EditElementHandle eeh(elmdscrP, true, false, (DgnModelRefP)(void*)ele->ModelReference->MdlModelRefP()); rtnEle = gcnew ElementDerive(&eeh); return rtnEle; } InteropMstn::Element^ ElementOperation::ConvertToInteropEle(DgnNetEle::Element^ ele) { cli::pin_ptr<unsigned char> pb = &(ele->ElementHandle[0]); ElementHandleP ehp = reinterpret_cast<ElementHandleP>(pb); InteropMstn::Application^ msApp = Bentley::MstnPlatformNET::InteropServices::Utilities::ComApp; MSElementDescrP elmdscrp = const_cast<MSElementDescrP>(ehp->GetElementDescrCP()); long long* lp = reinterpret_cast<long long*>(&*elmdscrp); long long elmDscrp = *reinterpret_cast<long long*>(&lp); InteropMstn::Element^ rtnEle = msApp->MdlCreateElementFromElementDescrP(elmDscrp); return rtnEle; } }
ConvertToDgnNetEle实现从COM框架下的Element到新的编程框架下的Element的转换。这个函数通过COM框架下Element的MdlElementRef或者MdlElementDescrP获取到ElementRefP或者MSElementDescrP指针,通过这两个指针可以初始化构造非托管类型EditElementHandle。ElementDerive的构造函数需要一个ElementHandle的指针作为参数,而EditElementHandle派生于ElementHandle。所以我们通过EditElementHandle就可以初始化构造ElementDerive对象了,最后返回ElementDerive对象实例即可。ConvertToInteropEle实现从新的编程框架下的Element到COM框架下的Element的转换。函数中通过新的编程框架下的Element的ElementHandle属性获取到ElementHandle的指针。因为.Net中的对象实例在垃圾回收过程中会被重定位,所以我们使用C++/CLI中的类型cli::pin_ptr告诉编译器,在此cli::pin_ptr的作用域中ele->ElementHandle在垃圾回收时不被重新定位。这样就可以保证我们的ElementHandle指针一直有效。然后我们通过ElementHandle获取到元素的MSElementDesrP指针,进而去调用Bentley.Interop.MicroStationDGN.Application 下的MdlCreateElementFromElementDescrP成员函数的到COM框架下的Element实例。
4. 在文本编辑器中再另建一个新文件,在其中键入如下内容并保存为文件D:\Files\BentleyMstn\MstnCE\Mixed\SampleMixed\SampleMixed.mke。该文件是SDK下的bmake工具用来编译源码来使用的,.mke文件的格式可以参考另一篇NativeCode的学习博客。
DemoSrcDir = $(_MakeFilePath) PolicyFile = MicroStationPolicy.mki appName = SampleMixed ASSEMBLY_NAME = $(appName) RIGHTSCOMPLIANT = false MDLMKI = $(MSMDE)mki/ mdlLibs = $(MSMDE)library/ %include mdl.mki outputDir = $(MS)Mdlapps/ always: ~mkdir $(o) objList = $(o)SampleMixed$(oext) %include compileForCLRStart.mki CCompDebugOptions = $(CCompDebugOffSwitch) CCompDebugOptions =% $[CCompDebugDefault] CCompOpts + -AI$(MS)Assemblies -AI$(MS)Assemblies/ECFramework -AI$(MS) -AI$(o) dirToSearch = ${msPrivInc} $(o)SampleMixed$(oext) : $(baseDir)SampleMixed.cpp %include compileForCLRStop.mki DLM_OBJECT_DEST = $(o) DLM_NAME = $(appname) RIGHTSCOMPLIANT = true DLM_DEST = $(outputDir) DLM_OBJECT_FILES = $(objList) DLM_NO_DEF = 1 DLM_NO_DLS = 1 DLM_NO_IMPLIB = 1 DLM_NO_SIGN = 1 ASSEMBLY_VERSION = 1.0.0.0 ASSEMBLY_TITLE = $(appName) ASSEMBLY_DESCRIPTION = MixedMode Test Application ASSEMBLY_PRODUCT_NAME = $(appName) ASSEMBLY_FILE_VERSION = 1.0.0.0 ASSEMBLY_COMPANY_NAME = Bentley Systems ASSEMBLY_COPYRIGHT = Copyright: (c) 2019 Bentley Systems, Incorporated. All rights reserved. LINKER_LIBRARIES + $(mdlLibs)BentleyAllocator.lib LINKER_LIBRARIES + $(mdlLibs)DgnPlatform.lib %include linkMixedAssembly.mki
4. 右键选择“开始>Bentley>MicroStation CONNECT Edition SDK”,选择“More>Run as administrator”启动bmake工具。在命令提示符后键入cd /d D:\Files\BentleyMstn\MstnCE\Mixed\SampleMixed并回车进入我们的源码所在目录,然后再键入bmake –a来生成SampleMixed.dll。生成的文件位于…\MicroStation\mdlapps目录下。
至此我们的两个转换函数已经封装好了,接下来我们在csAddins项目中去验证我们这两个函数。在csAddins项目中添加引用SmaleMixed.dll,具体添加方法参考第一章。打开commands.xml文件,新增命令两个命令csAddins CreateElement TestConvertToDgnNetEle和csAddins CreateElement TestConvertToInteropEle并指定其处理函数为csAddins.CreateElement. TestConvertToDgnNetEle和csAddins.CreateElement. TestConvertToInteropEle。如果您对XML格式的命令表文件还不熟悉,请参考第四章的相关主题。打开CreateElement.cs文件,CreateElement类的最后加入如下两个函数。
public static void TestConvertToDgnNetEle(string unparsed) { BIM.Application app = Bentley.MstnPlatformNET.InteropServices.Utilities.ComApp; BIM.Point3d ptStart = app.Point3dZero(); BIM.Point3d ptEnd = ptStart; ptStart.X = 10; BIM.LineElement lineEle = app.CreateLineElement2(null, ref ptStart, ref ptEnd); Element ele = SampleMixed.ElementOperation.ConvertToDgnNetEle(lineEle); ElementPropertiesSetter elePropSetter = new ElementPropertiesSetter(); elePropSetter.SetColor(1); elePropSetter.SetWeight(2); elePropSetter.Apply(ele); ele.AddToModel(); } public static void TestConvertToInteropEle(string unparsed) { DgnModel dgnModel = Session.Instance.GetActiveDgnModel(); ModelInfo modelInfo = dgnModel.GetModelInfo(); DPoint3d[] ptArr = new DPoint3d[5]; ptArr[0] = new DPoint3d(0 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas); ptArr[1] = new DPoint3d(1 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas); ptArr[2] = new DPoint3d(3 * UorPerMas, 8 * UorPerMas, 0 * UorPerMas); ptArr[3] = new DPoint3d(5 * UorPerMas, 12 * UorPerMas, 0 * UorPerMas); ptArr[4] = new DPoint3d(6 * UorPerMas, 10 * UorPerMas, 0 * UorPerMas); CurvePrimitive curPri = CurvePrimitive.CreateLineString(ptArr); Element ele = DraftingElementSchema.ToElement(dgnModel, curPri, null); BIM.Element eleInterop = SampleMixed.ElementOperation.ConvertToInteropEle(ele); eleInterop.Color = 3; eleInterop.LineWeight = 4; ele.AddToModel(); }
选VS菜单Build > Build Solution来生成本解决方案。启动Mstn,打开key-in窗口,输入csAddins CreateElement TestConvertToDgnNetEle,可以看到在生成了如图所示的元素。
元素我们是通过COM框架下的接口生成的,然后转换成新的编程框架下的元素,然后设置其颜色及线宽属性,最后添加到dgn文件中。接下来验证另一个函数,在key-in窗口,输入csAddins CreateElement TestConvertToInteropEle,可以看到在生成了如图所示的元素。
元素我们是通过新的编程框架下的接口生成的,颜色及线宽我们是通过COM框架下的接口设置的。本章例子只是简单演示了C++/CLI的用途,实际上C++/CLI能为我们做很多东西。当然其难度也是Mstn平台主要的三种开发语言中最高的,要想掌握C++/CLI必须先对C#和C/C++中对象的生命周期管理以及内存回收机制有很深的了解才行。建议读者要先熟练使用C#和C/C++之后,再开始利用C++/CLI进行开发。完整的csAddins解决方案源文件下载链接如下。非常欢迎大家批评指正,非常欢迎与他人分享该博客。