In the previous chapter, it was briefly demonstrated how to encapsulate and call functions exported by the NativeCode side on the Addins side. Although most of the functions exported by the NativeCode side can be called through PInvoke, some special functions in C/C++ cannot be encapsulated by PInvoke. For example, the constructor. The example demonstrated in this chapter belongs to this situation. In the first chapter, it was mentioned that there are two development frameworks in Addins on the CE version. In the second chapter, we briefly demonstrate how to create elements in the two development frameworks. In the actual development process, we sometimes encounter situations where we need to convert elements to another development framework. At this time, you will find that there is no interface in Addins to complete such a function. But if you study carefully, you will find that the Element objects under the two development frameworks have interfaces to obtain the objects related to the element construction on the NativeCode side, and construct their object instances from the objects related to the element on the NativeCode side. The Element object under the COM framework has two member functions, MdlElementRef and MdlElementDescrP, to get the elements of the NativeCodeElementRefP and MSElementDescrP pointers.
Bentley.Interop.MicroStationDGN.Application under MdlCreateElementFromElementDescrP member functions from MSElementDescrP constructed COM in the framework of the Element . The Element under the new development framework, has an ElementHandle property that can get the pointer of the ElementHandle on the NativeCode side. It has a protected type member function InitializeFromElementHandle that can construct its object instance from the pointer of ElementHandle on the NativeCode side. On the NativeCode side, among the several overloaded constructors of ElementHandle, there are constructors that accept ElementRefP or MSElementDescrP as parameters, and ElementHandle also has member functions to get ElementRefP and MSElementDescrP. Therefore, using the NativeCode object ElementHandle as a bridge, we can complete the conversion of elements under the two programming frameworks. If we use PInvoke technology to achieve, we need to export these two conversion functions on the NativeCode side. One takes ElementRefP or MSElementDescrP pointer as a parameter and the ElementHandleP pointer as a return value to realize the conversion from Element under the COM framework. Another is ElementHandleP pointer as a parameter to MSElementDescrP pointer return value of the transfer function implemented in the new programming framework Element to the COM in the framework Element conversion. If in C++/CLI we can use managed and unmanaged objects in one code block at the same time, no matter which direction the conversion is, we only need one function to achieve.
#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); }; }
An ElementDerive class is defined here because we have to use the InitializeFromElementHandle function of Bentley.DgnPlatformNET.Elements.Element to initialize the construction of Bentley.DgnPlatformNET.Elements.Element. Because InitializeFromElementHandle is a member function of a protected type, this member function cannot be accessed from outside the class, so the ElementDerive class is derived from Bentley.DgnPlatformNET.Elements.Element. In the member function of ElementDerive, we can access the protected type members of the base class, so we can initialize the members of the base class Bentley.DgnPlatformNET.Elements.Element through InitializeFromElementHandle.
3. Create a new file SampleMixed.cpp and save it in the same directory. This file contains the implementation of ElementDerive and two conversion functions.
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 realizes the conversion from Element under the COM framework to Element under the new programming framework. This function obtains the ElementRefP or MSElementDescrP pointer through the MdlElementRef or MdlElementDescrP of the Element under the COM framework, and the unmanaged EditElementHandle can be initialized and constructed through these two pointers. The constructor of ElementDerive requires a pointer to ElementHandle as a parameter, and EditElementHandle is derived from ElementHandle. So we can initialize the ElementDerive object through EditElementHandle, and finally return the ElementDerive object instance. ConvertToInteropEle realizes the conversion from Element under the new programming framework to Element under the COM framework. The function obtains the pointer of ElementHandle through the ElementHandle property of Element under the new programming framework. Because the object instance in .NET will be relocated during garbage collection, we use the type cli::pin_ptr in C++/CLI to tell the compiler that ele->ElementHandle is in the garbage in the scope of this cli::pin_ptr, it will not be relocated during recycling. This will ensure that our ElementHandle pointer is always valid. Then we get the MSElementDesrP pointer of the element through ElementHandle, and then call the MdlCreateElementFromElementDescrP member function under Bentley.Interop.MicroStationDGN.Application to the Element instance under the COM framework.
4. Create a new file SampleMixed.mke and save it in the same directory. This file is used by the bmake tool under the SDK to compile the source code. To know more about the format of the .mke file, read here : [[Detailed explanation of mke file]]
BUILD_USING_VS2017 = 1 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
You can also download the SampleMixed source code from here and create a dll.
5. Right-click on MicroStation CONNECT Edition SDK and select "Run as administrator" to start the bmake tool. At the command prompt, move to SampleMixed directory where our source code is located, and then type bmake -a to generate SampleMixed.dll. The generated files are located in the …\MicroStation\Mdlapps directory.
So far our two conversion functions have been encapsulated, and then we will verify our two functions in the csAddins project. Add a reference to SampleMixed.dll in the csAddins project (Project > Add Reference And select appropriate dll). Open the commands.xml file, add two new commands, csAddins CreateElement TestConvertToDgnNetEle and csAddins CreateElement TestConvertToInteropEle, and specify their processing functions as csAddins.CreateElement.TestConvertToDgnNetEle and csAddins.CreateElement.TestConvertToInteropEle. If you are not familiar with XML format command list files, please refer to the related topics in Chapter 4. Open the CreateElement.cs file and add the following two functions at the end of the CreateElement class.
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(); }
6. Select the VS menu Build > Build Solution to generate this solution. Start MicroStation, open the key-in window, enter csAddins CreateElement TestConvertToDgnNetEle, you can see that the elements shown in the figure are generated. The element is generated through the interface under the COM framework, and then converted into the element under the new programming framework, and then set it's color and line width attributes, and finally added to the dgn file. Next, verify another function, in the key-in window, enter csAddins CreateElement TestConvertToInteropEle, you can see that the elements shown in the figure are generated.
The elements are generated through the interface under the new programming framework, and the color and line width are set through the interface under the COM framework. The examples in this chapter simply demonstrate the use of C++/CLI. In fact, C++/CLI can do a lot for us. Of course, the difficulty is also the highest among the three main development languages of the MSTN platform. To master C++/CLI, you must first have a deep understanding of the life cycle management of objects in C# and C/C++ and the memory recovery mechanism. It is recommended that readers use C# and C/C++ proficiently before starting to develop with C++/CLI.
Prev:[[Calling C/C++ Functions in Add-ins]] |