问题描述
我在.Net中编写一个Windows窗体应用程序以列出第三方CAD / CAM软件(在这种情况下为CATIA)的所有运行实例,并让用户选择其中一个来执行几个自动化任务。对于执行自动化任务,我需要获得COM对象的具体实例 - 相比Getobject(),它给我一个非特定的COM实例。有没有办法使用窗口句柄或任何其他方法来获取特定的COM实例?
I am writing a Windows Form application in .Net to list all running instances of a third-party CAD/CAM software (in this case CATIA) and let user to choose one of them to perform couple of automated tasks. For performing automated tasks, I need to get the specific instance of COM objects - compared to Getobject() which gives me a non-specific COM instance. Is there a way to get a specific COM instance using window handle or any other methods?
更新:
正如Raymond所说,没有一个解决方案适用于所有COM对象;但是我设法使用下面的代码(它使用ROT填充一个列表中所有CATIA COM实例名称)获得CATIA COM对象:
UPDATE:As Raymond said there is no single solution for all COM objects; however I managed to get CATIA COM objects using following code (Which uses ROT to fill a list with all CATIA COM Instances name):
<DllImport("user32.dll", CharSet:=CharSet.Auto)> Private Shared Sub GetClassName(ByVal hWnd As System.IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) End Sub
<DllImport("ole32.dll", ExactSpelling:=True, PreserveSig:=False)> Private Shared Function GetRunningObjectTable(ByVal reserved As Int32) As IRunningObjectTable End Function
<DllImport("ole32.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True, PreserveSig:=False)> Private Shared Function CreateItemMoniker(ByVal lpszDelim As String, ByVal lpszItem As String) As IMoniker End Function
<DllImport("ole32.dll", ExactSpelling:=True, PreserveSig:=False)> Private Shared Function CreateBindCtx(ByVal reserved As Integer) As IBindCtx End Function
Try
Dim ROTObject As Object = Nothing
Dim runningObjectTable As IRunningObjectTable
Dim monikerEnumerator As IEnumMoniker = Nothing
Dim monikers(1) As IMoniker
runningObjectTable = GetRunningObjectTable(0)
runningObjectTable.EnumRunning(monikerEnumerator)
monikerEnumerator.Reset()
Dim numFetched As IntPtr = New IntPtr()
While (monikerEnumerator.Next(1, monikers, numFetched) = 0)
Dim ctx As IBindCtx
ctx = CreateBindCtx(0)
Dim runningObjectName As String = ""
monikers(0).GetDisplayName(ctx, Nothing, runningObjectName)
runningObjectName = runningObjectName.ToUpper
If (Not runningObjectName.Equals("")) Then
Dim runningObjectIns As Object = Nothing
runningObjectTable.GetObject(monikers(0), runningObjectIns)
'Check if object is a Catia object
Try
Dim catiaIns As INFITF.Application = Nothing
catiaIns = DirectCast(runningObjectIns, INFITF.Application)
ListCATIA.Items.Add(catiaIns.Windows.Count)
Catch Exc As Exception
MessageBox.Show(Exc.ToString())
End Try
End If
End While
Catch Exc As Exception
Throw Exc
End Try
但是,所有CATIA实例请参阅加载的第一个CATIA应用程序。
However, all CATIA instances refer to first CATIA application loaded. No idea why, anybody?
推荐答案
代码中的问题是调用 GetObject
始终返回它在运行对象表(ROT)中找到的第一个活动服务器。枚举ROT不会改变这种行为,并且有点令人沮丧,因为它确实表明ROT中有多个服务器。请注意,枚举中返回的一些项目可能实际上并未运行:
GetObject
返回第一个活动的服务器 - 不一定返回第一个
The "problem" in your code is that calling GetObject
always returns the first active server that it finds in the Running Object Table (ROT). Enumerating the ROT doesn't change that behavior and is a little frustrating because it does show that there is more than one server in the ROT. Note that some of the items returned in the enumeration may not actually be running: GetObject
returns the first active server -- not necessarily the first one returned by the enumeration.
然而,在CATIA的情况下,特别是可以获得特定的实例。我怀疑对于许多应用程序是可能的,如果你可以获得感兴趣的特定实例运行一些代码,之前你实际获得一个指向COM实例的指针。
However, in the case of CATIA in particular it is possible to get a specific instance. I suspect it is possible with many applications if you can get the particular instance of interest to run some code, on demand, before you actually get a pointer to the COM instance.
对于CATIA,这是我使用的过程的大致轮廓:
For CATIA, this is a rough outline of the process I use:
1. Make a dll with two functions:
HRESULT __stdcall CoMarshalToFile(IUnknown* punk, const char* const filePath)
/* uses `::CreateStreamOnHGlobal`, `::CoMarshalInterface`, `::CoGetMarshalSizeMax`,
and `::GetHGlobalFromStream` to marshal the IUnknown to the specified file.
*/
HRESULT __stdcall CoMarshalFromFile(IUnknown** ppunk, const char* const filePath)
/* uses `::CreateStreamOnHGlobal` and `::CoUnmarshalInterface` to marshal
from the file to an IUnknown pointer.
*/
2. In CATIA:
Note: this only needs to be done on the development computer.
Make a new "VBA projects" macro library.
Add "declare" statements for:
"LoadLibrary" (Windows API)
"CoMarshalToFile" (DLL specified above)
Add a function
Public Function MarshalCatiaToFile _
(marshalInstanceFilePath As String, _
marshalDllFolder As String) As Long
MarshalCatiaToFile calls "LoadLibrary" to load the C++ DLL
and then calls CoMarshalToFile (in DLL) to marshal the CATIA instance
to a file.
Remove the macro library from CATIA's list of macro libraries.
3. Create a file:
"C:\Temp\CatiaOnTheFlyCatScripts\OnTheFlyCatScript.catvbs"
The file can be empty.
4. In CATIA:
Note: this must be done for *each* user of CATIA on *each* computer used.
It may be possible to make this available to all users without individual
setup required: it is saved in "FrameUserAliases.CATSettings"
It may also be possible to reverse engineer the settings file and set up
the needed data from outside CATIA.
Add "C:\Temp\CatiaOnTheFlyCatScripts\" as a new "Directories" macro library.
Make the added library "current"
Use "Tools --> Customize --> Commands --> Macros" to assign a
"User Alias:" to the "OnTheFlyCatScript.catvbs" script file.
Name the alias "ExecuteOnTheFlyCatScript".
Remove the macro library from CATIA's list of macro libraries.
Close CATIA at this point to force the changes to be saved.
5. VB.net / C# program:
Add the DLL (from step 1) and the CatVBA macro library (from step 2) as
"Embedded Resource" to the project.
During program execution:
Extract the DLL and macro library to an appropriate location.
Load the DLL into session using "LoadLibrary".
Create the file:
"C:\Temp\CatiaOnTheFlyCatScripts\OnTheFlyCatScript.catvbs"
The "OnTheFlyCatScript.catvbs" will be executed in CATIA. It
uses CATIA.SystemService.ExecuteScript to execute the
"MarshalCatiaToFile" function in the CatVBA macro library.
Add method of choice to this file to indicate success/failure.
I use a dialog box with the appropriate title.
To execute the "OnTheFlyCatScript.catvbs":
Using the Windows API functions, get the window handle for the
"Power Input" box at the bottom right of the "desired"
CATIA window.
Using the Windows API functions (*NOT* "SendKeys") send
"c:ExecuteOnTheFlyCatScript" + {Enter} to the "Power Input".
Wait for the "completion" signal from the script. If you used
a dialog box, use the Windows API function to close it.
Assuming the script succeeded in marshaling the CATIA instance to
a file, call the DLL function CoMarshalFromFile to get the CATIA
instance.
这是与许多移动部分的很多工作,但它允许您自动化多个CATIA会话同时。适用于我的目的:从一组CATIA模型自动提取数据,以及一次使用多个CATIA会话自动创建一组CATIA模型。我的应用程序的瓶颈是单个CATIA会话 - 不是CPU资源(使用双处理器4或6核每个处理器机器);添加更多会话可提高吞吐量。
It's a lot of work with many "moving" parts but it does allow you to automate multiple CATIA sessions "simultaneously". Works well for my purposes: automated extraction of data from a set of CATIA models and automated creation of a set of CATIA models using more than one CATIA session at a time. The bottleneck for my application is the individual CATIA session -- not CPU resources (using a dual processor 4 or 6 core per processor machine); adding more sessions improves throughput.
这篇关于在VB.Net中获取COM对象的特定实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!