通过运行对象表(ROT)调用SOLIDWORKS插件的函数
本文提供了详细的逐步说明,介绍了如何通过运行对象表(ROT)从独立应用程序或脚本中调用SOLIDWORKS插件的函数。这可以被视为在插件本身中启用应用程序编程接口(API)。
这种方法允许通过从进程ID提取它来连接到API对象。这种方法的主要好处之一是能够在不直接使用任何SOLIDWORKS API命令甚至添加SOLIDWORKS类型库或互操作的情况下控制插件API。
在此示例中,SOLIDWORKS插件允许计算所选实体的面数。它提供了一个菜单供用户点击。
{ width=350 }
结果显示在消息框中。
{ width=450 }
插件还提供了一个API对象,供第三方调用。API扩展了UI的功能,并允许传递参数以过滤面的最小面积。
API方法和用户界面命令处理程序都调用相同的函数。
这是插件和API对象的解决方案树。GeometryHelperApiObject编译为dll,并包含插件接口的定义。它不包含任何实现,并且不引用任何SOLIDWORKS互操作。此dll在实现API对象的SOLIDWORKS插件项目中被引用,并且还将被所有第三方应用程序引用以访问API。因此,没有第三方应用程序需要引用启用抽象级别的主插件dll。
{ width=450 }
请查看下面的源代码和解释,以了解如何实现此框架的更多详细信息。
GeometryHelperApiObject项目
该项目包含应该由插件公开的API方法和接口的定义(签名)。
GeometryHelperApiObjectFactory.cs
这是一个帮助COM对象,它将简化从进程中按其ID检索API对象实例的访问。它负责从进程中检索API对象的实例。
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace CodeStack.GeometryHelper
{
[ComVisible(true)]
public interface IGeometryHelperApiObjectFactory
{
string GetName(int prcId);
IGeometryHelperApiObject GetInstance(int prcId);
}
[ComVisible(true)]
[ProgId("GeometryHelper.ApiObjectFactory")]
public class GeometryHelperApiObjectFactory : IGeometryHelperApiObjectFactory
{
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
public string GetName(int prcId)
{
return $"GeometryHelperApiObjectFactory_PID_{prcId}";
}
public IGeometryHelperApiObject GetInstance(int prcId)
{
return FindObjectByMonikerName<IGeometryHelperApiObject>($"!{GetName(prcId)}");
}
private T FindObjectByMonikerName<T>(string monikerName)
where T : class
{
IBindCtx context = null;
IRunningObjectTable rot = null;
IEnumMoniker monikers = null;
try
{
CreateBindCtx(0, out context);
context.GetRunningObjectTable(out rot);
rot.EnumRunning(out monikers);
var moniker = new IMoniker[1];
while (monikers.Next(1, moniker, IntPtr.Zero) == 0)
{
var curMoniker = moniker.First();
string name = null;
if (curMoniker != null)
{
try
{
curMoniker.GetDisplayName(context, null, out name);
}
catch (UnauthorizedAccessException)
{
}
}
if (string.Equals(monikerName,
name, StringComparison.CurrentCultureIgnoreCase))
{
object app;
rot.GetObject(curMoniker, out app);
return (T)app;
}
}
}
finally
{
if (monikers != null)
{
Marshal.ReleaseComObject(monikers);
}
if (rot != null)
{
Marshal.ReleaseComObject(rot);
}
if (context != null)
{
Marshal.ReleaseComObject(context);
}
}
return null;
}
}
}
IGeometryHelperApiObject.cs
这是向第三方应用程序公开的API的接口。在此示例中,该函数将根据面积过滤器返回面的数量。
using System.Runtime.InteropServices;
namespace CodeStack.GeometryHelper
{
[ComVisible(true)]
public interface IGeometryHelperApiObject
{
int GetFacesCount(double minArea);
}
}
GeometryHelperAddIn项目
该项目是一个SOLIDWORKS插件。在此示例中,它使用SwEx.AddIn Framework进行开发,但是任何其他框架或SDK都将受到支持。
GeometryHelperService.cs
插件内的帮助类,用于调用SOLIDWORKS API以计算基于最小面积的所选实体的面数。
using SolidWorks.Interop.sldworks;
using System;
using System.Linq;
namespace CodeStack.GeometryHelper
{
internal class GeometryHelperService
{
private readonly ISldWorks m_App;
internal GeometryHelperService(ISldWorks app)
{
m_App = app;
}
internal int GetFacesCountFromSelectedBody(double minArea)
{
var model = m_App.IActiveDoc2;
if (model != null)
{
var body = model.ISelectionManager.GetSelectedObject6(1, -1) as IBody2;
if (body != null)
{
var faces = body.GetFaces() as object[];
if (faces != null)
{
return faces.Count(f => (f as IFace2).GetArea() >= minArea);
}
else
{
throw new NullReferenceException("No faces in the body");
}
}
else
{
throw new NullReferenceException("Body is not selected");
}
}
else
{
throw new NullReferenceException("Model is not opened");
}
}
}
}
插件必须实现API对象的功能。有两种常见的方法。
- 通过直接在继承类中实现功能,如下所示
GeometryHelperApiObject.cs
API对象实现在其类中直接使用SOLIDWORKS API来处理请求并提供API调用的响应。
using System.Runtime.InteropServices;
namespace CodeStack.GeometryHelper
{
[ComVisible(true)]
public class GeometryHelperApiObject : IGeometryHelperApiObject
{
private readonly GeometryHelperService m_GeomSvc;
internal GeometryHelperApiObject(GeometryHelperService geomSvc)
{
m_GeomSvc = geomSvc;
}
public int GetFacesCount(double minArea)
{
return m_GeomSvc.GetFacesCountFromSelectedBody(minArea);
}
}
}
MainAddIn.cs
这是主SOLIDWORKS插件类的实现。将处理功能的服务直接传递给API对象实现,并从那里调用以处理API调用。
using CodeStack.SwEx.AddIn;
using CodeStack.SwEx.AddIn.Attributes;
using SolidWorks.Interop.swconst;
using System;
using System.Runtime.InteropServices;
namespace CodeStack.GeometryHelper
{
[AutoRegister("GeometryHelperSwAddIn")]
[ComVisible(true), Guid("2ACE1951-6182-496C-A643-B5FA6CFDFFD8")]
public class MainAddIn : SwAddInEx
{
private IGeometryHelperApiObject m_ApiObject;
private GeometryHelperService m_GeometryService;
[SwEx.Common.Attributes.Title("Geometry Helper")]
public enum Commands_e
{
[SwEx.Common.Attributes.Title("Get Faces Count")]
GetFacesCount
}
public override bool OnConnect()
{
m_GeometryService = new GeometryHelperService(App);
m_ApiObject = new GeometryHelperApiObject(m_GeometryService);
RotHelper.Register(m_ApiObject, new GeometryHelperApiObjectFactory().GetName(App.GetProcessID()));
this.AddCommandGroup<Commands_e>(OnButtonClick);
return true;
}
private void OnButtonClick(Commands_e cmd)
{
switch (cmd)
{
case Commands_e.GetFacesCount:
try
{
var count = GetFacesCount(0);
App.SendMsgToUser2($"Selected body contains: {count} face(s)",
(int)swMessageBoxIcon_e.swMbInformation,
(int)swMessageBoxBtn_e.swMbOk);
}
catch(Exception ex)
{
App.SendMsgToUser2(ex.Message,
(int)swMessageBoxIcon_e.swMbStop,
(int)swMessageBoxBtn_e.swMbOk);
}
break;
}
}
private int GetFacesCount(double minArea)
{
return m_GeometryService.GetFacesCountFromSelectedBody(minArea);
}
}
}
- 通过实现代理API对象。这种方法可能被认为更有益和安全,因为它不会在其结构中公开任何内部对象。所有请求都在代理类之外处理。
GeometryHelperApiObjectProxy.cs
代理对象不包含对插件的任何对象的引用。相反,它将生成请求事件,由插件处理和处理。
using System;
using System.Runtime.InteropServices;
namespace CodeStack.GeometryHelper
{
[ComVisible(true)]
public class GeometryHelperApiObjectProxy : IGeometryHelperApiObject
{
internal event Func<double, int> GetFacesCountRequested;
public int GetFacesCount(double minArea)
{
if (GetFacesCountRequested != null)
{
return GetFacesCountRequested.Invoke(minArea);
}
else
{
throw new Exception("API object not connected");
}
}
}
}
MainAddIn.cs使用代理API对象
在插件中处理事件并提供结果。
public override bool OnConnect()
{
m_GeometryService = new GeometryHelperService(App);
var proxy = new GeometryHelperApiObjectProxy();
proxy.GetFacesCountRequested += OnGetFacesCountRequested;
m_ApiObject = proxy;
RotHelper.Register(m_ApiObject, new GeometryHelperApiObjectFactory().GetName(App.GetProcessID()));
this.AddCommandGroup<Commands_e>(OnButtonClick);
return true;
}
private int OnGetFacesCountRequested(double minArea)
{
return GetFacesCount(minArea);
}
RotHelper.cs
为了使API对象可用,需要在运行对象表(ROT)中注册它。这个辅助类允许按名称注册对象。
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace CodeStack.GeometryHelper
{
public static class RotHelper
{
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
private static extern IRunningObjectTable GetRunningObjectTable(
int reserved);
[DllImport("ole32.dll", CharSet = CharSet.Unicode,
ExactSpelling = true, PreserveSig = false)]
private static extern IMoniker CreateItemMoniker(
[In] string lpszDelim, [In] string lpszItem);
public static void Register(object obj, string name)
{
IRunningObjectTable rot = null;
IMoniker moniker = null;
try
{
rot = GetRunningObjectTable(0);
moniker = CreateItemMoniker("!", name);
const int ROTFLAGS_REGISTRATIONKEEPSALIVE = 1;
var cookie = rot.Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, obj, moniker);
}
finally
{
if (moniker != null)
{
Marshal.ReleaseComObject(moniker);
}
if (rot != null)
{
Marshal.ReleaseComObject(rot);
}
}
}
}
}
从独立应用程序调用API
现在,可以从任何支持COM的编程语言调用插件API。
VBA宏
从Excel VBA宏调用API的示例。
{ width=450 }
添加对类型库的引用以启用早期绑定。
{ width=450 }
宏通过其Prog ID创建Factory对象的实例,并通过其ID从进程中检索API对象的实例。
Function GetFacesCount() As Integer
Dim geomObjFactory As Object
Set geomObjFactory = CreateObject("GeometryHelper.ApiObjectFactory")
Dim geomHelper As Object
Set geomHelper = geomObjFactory.GetInstance(13004)
GetFacesCount = geomHelper.GetFacesCount(0)
End Function
C#控制台应用程序
从C#控制台应用程序调用API的示例。
如下所示,只需添加对API对象dll的引用,其中包含接口的定义。不需要添加任何其他引用(包括插件dll或任何SOLIDWORKS互操作)。
{ width=450 }
API调用的结果将打印到控制台窗口中。
{ width=450 }
using CodeStack.GeometryHelper;
using System;
using System.Diagnostics;
using System.Linq;
namespace StandAlone
{
class Program
{
static void Main(string[] args)
{
try
{
var minArea = double.Parse(args[0]);
var swPrcId = Process.GetProcessesByName("SLDWORKS").First().Id;
var geomHelperFactory = new GeometryHelperApiObjectFactory();
var geomHelperApi = geomHelperFactory.GetInstance(swPrcId);
var count = geomHelperApi.GetFacesCount(minArea);
Console.WriteLine($"Selected body contains {count} faces of area more or equal to {minArea}");
}
catch(Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write(ex.Message);
Console.ResetColor();
}
}
}
}
无法从运行对象表(ROT)中检索对象(即返回null)的最常见原因是运行SOLIDWORKS和独立应用程序的帐户级别不同。例如,SOLIDWORKS以管理员身份运行,而独立应用程序不是,反之亦然。这是Windows的限制,需要以相同的权限级别运行两个应用程序以启用通信。
在GitHub上下载源代码。