I'm working a project that uses API from a third party COM Server. The COM Server is a local server (out of process exe) on which I have no control.
I'm trying to access COM objects from the runnin object table to choose between the several instances of COM objects started with each instance of the application :
private static List<object> GetRunningInstances(string progId) {
// get Running Object Table ...
IRunningObjectTable Rot = null;
GetRunningObjectTable(0, out Rot);
if (Rot == null)
return null;
// get enumerator for ROT entries
IEnumMoniker monikerEnumerator = null;
Rot.EnumRunning(out monikerEnumerator);
if (monikerEnumerator == null) return null;
monikerEnumerator.Reset();
List<object> instances = new List<object>();
IntPtr pNumFetched = new IntPtr();
IMoniker[] monikers = new IMoniker[1];
while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0) {
IBindCtx bindCtx;
CreateBindCtx(0, out bindCtx);
if (bindCtx == null) continue;
Guid clsid = Type.GetTypeFromProgID(progId).GUID;
string displayName;
Guid monikerClsid;
Guid riid = Marshal.GenerateGuidForType(typeof(IApplication));
object obj;
monikers[0].GetDisplayName(bindCtx, null, out displayName);
monikers[0].GetClassID(out monikerClsid);
if (displayName.IndexOf(clsid.ToString(), StringComparison.OrdinalIgnoreCase) > 0) {
//monikers[0].BindToObject(bindCtx, null, ref unkid, out obj);
Rot.GetObject(monikers[0], out obj);
instances.Add((IApplication)obj);
}
}
}
If I start two instances of the target application, the ROT dump shows two instances of the corresponding COM object (named IApplication here) as GetDisplayName shows the right clsid for the interface IApplication registered in the registry.
The problem is that the objects I get from Rot.GetObject are described as System.__ComObject and cannot be cast to IApplication (InvalidCastException because QueryInterface failed with E_NOINTERFACE) even if their moniker describe the right clsid...
I tried casting it progammatically to every available type in my project just to see but the only success is casting it to System.__ComObject...
I also tried using IMoniker.BindToObject instead of Rot.GetObject but this time, I get a FileNotFound Exception when I provide the corresponding interface GUID. BindToObject works when I provide the riid for IUnknown but it gives me a System.__ComObject I can't cast (back to square one !).
For information, in the ROT dump, I can also shows a file moniker corresponding to an open project in the target app but I cannot create a COM object from this either.
Anyone has an idea about how to correctly retrieve the objects from the ROT ?
Thanks,
Regards.
EDIT :
After a few days out and a new eye on this problem, I discovered the CLSID in the moniker's display name is not the exact same one I want but has two digits off (the indexof test turns out to be wrong).
After carefully reading the clsid's it turns out that the clsid in the moniker's displayname is the clsid of the coclass of IApplication and not the clsid of IApplication.
I tried casting the object to "ApplicationClass" (the aforedmentionned coclass) but this gives me an exception. The additional info I get (in french) could translate as follows : Impossible to cast __ComObject wrapper instances to another class but it's possible to cast these instances to interfaces as long as the underlying com component supports QueryInterface calls for the interface IID.
Any idea on how to proceed from here ?
I don't now if you still have a problem but since there is no answer yet I want to share what I've done after couple of weeks research about handling multiple COM Interfaces on ROT.
I've fetched all the objects from ROT and store them in hashtable to ensure uniqueness via Key/Value but you can adjust it to a List as you prefer on question. You just need to fetch running object value to add your List.
public static Hashtable GetRunningObjectTable()
{
Hashtable result = new Hashtable();
IntPtr numFetched = new IntPtr();
IRunningObjectTable runningObjectTable;
IEnumMoniker monikerEnumerator;
IMoniker[] monikers = new IMoniker[1];
GetRunningObjectTable(0, out runningObjectTable);
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
{
IBindCtx ctx;
CreateBindCtx(0, out ctx);
string runningObjectName;
monikers[0].GetDisplayName(ctx, null, out runningObjectName);
object runningObjectVal;
runningObjectTable.GetObject(monikers[0], out runningObjectVal);
result[runningObjectName] = runningObjectVal;
}
return result;
}
After that you can cast the object stored in your List to your COM Interface. For instance, I've also shared my way which works for Rhapsody COM Interface in below;
Hashtable runningObjects = GetRunningObjectTable();
IDictionaryEnumerator rotEnumerator = runningObjects.GetEnumerator();
while (rotEnumerator.MoveNext())
{
string candidateName = (string)rotEnumerator.Key;
if (!candidateName.StartsWith("Rhapsody"))
continue;
rhapsody.RPApplication app = rotEnumerator.Value as rhapsody.RPApplication;
if (app == null)
continue;
// Do your stuff app (com object) in here..
}
Related
I have the following COM types: Project, ContainerItem and Node.
Project has a collection property with an Append function that accepts ContainerItems.
In C#, using type libraries I can send a Node object to the Append function and the library works as expected:
var prj = new Project();
var node = new Node();
prj.collection.Append(node);
In C++ I tried a direct pointer cast expecting this is what C# was doing, but it ends up in an error:
ProjectPtr prj;
prj.CreateInstance(__uuidof(Project));
NodePtr node;
node.CreateInstance(__uuidof(Node));
prj->collection->Append((ContainerItem**)&node.GetInterfacePtr());
Is there a specific way to these type of COM pointer casts in C++? What am I missing?
COM casting is done with the QueryInterface() method. The object is queried for its support of the interface (based on the GUID), and if the interface is supported, the internal reference counter is incremented (see AddRef()) and a pointer to the interface is returned. MSDN has more detail on the inner workings.
C++ does not directly support the code generation for the "COM cast" as C# does, but its implementation is simple enough.
struct bad_com_cast : std::runtime_error {
bad_com_cast() : std::runtime_error("COM interface not supported") {}
};
template <class To, class From>
To* qi_cast(From* iunknown)
{
HRESULT hr = S_OK;
To* ptr = NULL;
if (iunknown) {
hr = iunknown->QueryInterface(__uuidof(To), (void**)(&ptr));
if (hr != S_OK) {
throw bad_com_cast(); // or return NULL
}
}
return ptr;
}
Using the above "cast", the sample can be implemented as follows;
ContainerItem* ci = qi_cast<ContainerItem>(node);
prj->collection->Append(&ci);
If the ATL library is being used, you can use ATL::CComQIPtr<> directly to obtain the equivalent semantics;
auto ci = CComQIPtr<ContainerItem>(node);
if (ci) {
// ...
}
Just like #HansPassant commented, I had to use the QueryInterface function:
ContainerItem* ci = nullptr;
node.QueryInterface(__uuidof(ContainerItem), ci);
prj->collection->Append(&ci);
I have a console application that needs to create multiple objects of type <T> and T is inside another dll that I don’t own.
When an object of type T is created, it loads a XML in memory, but it never releases it.
So if you create too many objects of type T, an OutOfMemoryException is thrown.
The dll doesn't provide a dispose method for that Object and I can’t interact with the XML directly.
Is there a way to dispose of objects of a certain type that were created by a dll that I don’t own ?
I'm using .NET 4.6
The third-party dll is the dll of Trados Studio, for the people who know the program.
Just set the instance of the 3rd part object to null and create a new instance. The garbage collector will eventually clean up the object that you set to null and you wont get an out of memory exception anymore.
public class Class1
{
private StringBuilder sb = new StringBuilder();
public void loadFile()
{
using(StreamReader sr = new StreamReader("C:\\test.txt")) // Loads large text file.
{
sb.Append(sr.ReadToEnd());
}
}
}
static void Main()
{
fileloader.Class1 inst = new fileloader.Class1(); // Assume this is the instance of your 3rd party object.
do
{
if(inst == null)
{
inst = new fileloader.Class1();
}
for (int i = 0; i < 100; i++)
{
inst.loadFile();
}
inst = null; // allows the object to be GC'ed. Without this i get the OutOfMemoryException
Thread.Sleep(1000);
} while (true);
}
Calling GC.Collect() during runtime might solve the problem.
Ref: https://learn.microsoft.com/en-us/dotnet/api/system.gc.collect?view=net-5.0
I use this function to create DirectShow filter instances which works:
private IBaseFilter CreateFilter(Guid category, string name)
{
object source = null;
Guid guid = typeof(IBaseFilter).GUID;
foreach (DsDevice device in DsDevice.GetDevicesOfCat(category))
{
if ( device.Name == name )
{
device.Mon.BindToObject(null, null, ref guid, out source);
break;
}
}
return (IBaseFilter)source;
}
When I try to create an instance of "GPL MPEG-1/2 Decoder" filter using this function, my application crashes at BindToObject function. It used to works but I formatted my laptop and I'm facing this weird problem. Do you have any idea why I can't create instance of this filter?
P.S: GraphEdit can insert this filter and open property dialog.
I have three vendor supplied ole com libraries that provide an interface to different but similar devices. These libraries are not compatible between devices, and have differing functions embedded. They also all share the same ID's, so I can only register one on a machine at a time. Unfortunately I do not have the source and have no way of recompiling these.
My problem is that quite often I need to be able to communicate with more than one model of device.
I've tried loading the dll from a specific path and converting it to an assembly, creating an instance of it and pulling out the methodinfo for the functions I need. This appears to work. The type information is different for the different libraries as expected, but whenever I instantiate it it still appears to only load the registered dll, since it crashes and burns with an access violation if I call a function that is not in the currently registered dll. Does anyone have any ideas?
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
public static extern void LoadTypeLibEx(String strTypeLibName, RegKind regKind, [MarshalAs(UnmanagedType.Interface)] out Object typeLib);
public enum RegKind
{
RegKind_Default = 0,
RegKind_Register = 1,
RegKind_None = 2
}
private void Initialize() {
Type ct;
object typeLib;
try {
// Load DLL
LoadTypeLibEx(#"c:\temp\zkA11C\zkemkeeper.dll", RegKind.RegKind_None, out typeLib);
if (typeLib == null)
throw new Exception("LoadTypeLibEx returned null pointer");
TypeLibConverter converter = new TypeLibConverter();
ConversionEventHandler eventHandler = new ConversionEventHandler();
AssemblyBuilder zkA11C = converter.ConvertTypeLibToAssembly(typeLib, "zkA11C.dll", 0, eventHandler, null, null, null, null);
// Get class type
ct = zkA11C.GetType("zkA11C.CZKEMClass");
// Create instance of the class
dllClassInstance = Activator.CreateInstance(ct);
// Get method info for functions
fmiConnect = ct.GetMethod("Connect_Net");
...
}
catch (Exception e) {
throw new Exception("Failed to load library: " + e.Message);
}
}
i am using the arcGIS api to make a plugin for arcFM, when i try to run this code
Type t = Type.GetTypeFromProgID("esriFramework.AppRef");
System.Object obj = Activator.CreateInstance(t);
pApp = obj as IApplication;
i get
System.Runtime.InteropServices.COMException(0x8000FFFF): Creating an instance of the component with CLSID {Appref CLSID HERE} from the IClassFactory faileddue to the following error: 8000ffff
Thanks
This was impossible i needed to be using arcMap not ArcFM
In the AppRef CoClass documentation, it says:
Note you can only use the AppRef
object if your code is running inside
one of the ArcGIS application
processes.
Forum posts seem to confirm that this is the same error which is seen when this constraint has been violated:
From http://forums.esri.com/Thread.asp?c=93&f=1729&t=217861:
It is my understanding that there is
indeed no way to access the
IApplication instance from a
geoprocessing script.
In theory, if your task is purely
geoprocessing, you should be able to
do it all without accessing the
IApplication object.
It looks like the OP of the above forum post was able to get around their problem by "using IToolboxWorkspace and accessing directely the Esri-toolboxes". This was her code:
public IGPTool GetTool(string _sToolName, string _sToolboxName)
{
IWorkspaceFactory pGPTFact;
IToolboxWorkspace pToolboxWorkspace;
IGPToolbox pGPToolbox;
IGPTool pGPTool;
pGPTFact = new ToolboxWorkspaceFactoryClass();
pToolboxWorkspace = pGPTFact.OpenFromFile(
ArcGISInstallFolder + #"\ArcToolbox\Toolboxes", 0) as IToolboxWorkspace;
pGPToolbox = pToolboxWorkspace.OpenToolbox(_sToolboxName);
pGPTool = pGPToolbox.OpenTool(_sToolName);
return pGPTool;
}
private string ArcGISInstallFolder
{
get
{
if (string.IsNullOrEmpty(this.m_sArcGISInstallFolder))
{
Microsoft.Win32.RegistryKey regkey;
regkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
#"Software\ESRI\ArcGIS", false);
this.m_sArcGISInstallFolder = regkey.GetValue("InstallDir") as String;
}
return this.m_sArcGISInstallFolder;
}
}
Perhaps you can accomplish your goal either without the AppRef object or by running your script from inside the application.