Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple instances of Autocad #520

Open
sindzicat opened this issue Mar 6, 2024 · 7 comments
Open

Multiple instances of Autocad #520

sindzicat opened this issue Mar 6, 2024 · 7 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@sindzicat
Copy link

Hello!

I know, that if Autocad is already running, we can use comtypes.client.GetActiveObject:

import comtypes.client as cc
app = cc.GetActiveObject("AutoCAD.Application")

But what to do, if multiple instances of Autocad is running?

I found the code to solve this issue, but it's in C#:

[DllImport("ole32.dll")]
static extern int CreateBindCtx(
    uint reserved,
    out IBindCtx ppbc);
 
[DllImport("ole32.dll")]
public static extern void GetRunningObjectTable(
    int reserved,
    out IRunningObjectTable prot);
 
// Requires Using System.Runtime.InteropServices.ComTypes
// Get all running instance by querying ROT
private List<object> GetRunningInstances(string[] progIds)
{
    List<string> clsIds = new List<string>();
 
    // get the app clsid
    foreach (string progId in progIds)
    {
        Type type = Type.GetTypeFromProgID(progId);
 
        if(type != null)
            clsIds.Add(type.GUID.ToString().ToUpper());
    }
 
    // 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];
 
    // go through all entries and identifies app instances
    while (monikerEnumerator.Next(1, monikers, pNumFetched) == 0)
    {
        IBindCtx bindCtx;
        CreateBindCtx(0, out bindCtx);
        if (bindCtx == null)
            continue;
 
        string displayName;
        monikers[0].GetDisplayName(bindCtx, null, out displayName);
               
        foreach (string clsId in clsIds)
        {
            if (displayName.ToUpper().IndexOf(clsId) > 0)
            {
                object ComObject;
                Rot.GetObject(monikers[0], out ComObject);
 
                if (ComObject == null)
                    continue;
 
                instances.Add(ComObject);
                break;
            }
        }
    }
 
    return instances;
}
 
void TestROT()
{
    // Look for acad 2009 & 2010 & 2014
    string[] progIds =
    {
        "AutoCAD.Application.17.2",
        "AutoCAD.Application.18",
        "AutoCAD.Application.19.1"
    };
 
    List<object> instances = GetRunningInstances(progIds);
 
    foreach (object acadObj in instances)
    {
        try
        {
            // do some stuff ...  
        }
        catch
        {
           
        }
    }
}

I know it's possible to translate the C# code above to Python, but I don't know C# to do so. Anybody know it to translate, please?

@sindzicat
Copy link
Author

I now found the following: https://stackoverflow.com/questions/63562678/how-to-reference-the-com-objects-of-all-the-running-excel-application-instances
The same task but for Excel Applications, and on Python.

@junkmd
Copy link
Collaborator

junkmd commented Mar 7, 2024

Upon examining the C# code, moniker seems to be relevant. CoGetObject functions may be relevant.

Here are the relevant code snippets:

  • def CoGetObject(
    displayname: str,
    interface: Optional[Type[comtypes.IUnknown]] = None,
    dynamic: bool = False,
    ) -> Any:
    """Create an object by calling CoGetObject(displayname).
    Additional parameters have the same meaning as in CreateObject().
    """
    if dynamic:
    if interface is not None:
    raise ValueError("interface and dynamic are mutually exclusive")
    interface = automation.IDispatch
    punk = comtypes.CoGetObject(displayname, interface)
    if dynamic:
    return comtypes.client.dynamic.Dispatch(punk)
    return _manage(punk, clsid=None, interface=interface)

  • def CoGetObject(displayname: str, interface: Optional[Type[IUnknown]]) -> IUnknown:
    """Convert a displayname to a moniker, then bind and return the object
    identified by the moniker."""
    if interface is None:
    interface = IUnknown
    punk = POINTER(interface)()
    # Do we need a way to specify the BIND_OPTS parameter?
    _ole32.CoGetObject(str(displayname), None, byref(interface._iid_), byref(punk))
    return punk # type: ignore

However, as I am not well-versed in C#, I hope that those who proficient in both Python and C# provide further insights.

@sindzicat
Copy link
Author

Unfortunately, I have no ideas what displayname should be passed to CoGetObject and where I can get it.

I'm trying now to rewrite the following code from here from pywin32 to comtypes:

from pythoncom import (
  CreateBindCtx         as create_bind_context_com_interface,
  IID_IDispatch         as dispatch_com_interface_iid,
  GetRunningObjectTable as get_running_object_table_com_interface,
)
from win32com.client import (
  Dispatch as dispatch,
)

def get_excel_instances():
  '''
  Returns a list of the running Microsoft Excel application
  instances as component object model (COM) objects.
  '''
  running_object_table_com_interface = get_running_object_table_com_interface()
  bind_context_com_interface = create_bind_context_com_interface()
  excel_application_class_clsid = '{00024500-0000-0000-C000-000000000046}'
  excel_application_clsid = '{000208D5-0000-0000-C000-000000000046}'
  excel_instance_com_objects = []
  for moniker_com_interface in running_object_table_com_interface:
    display_name = moniker_com_interface.GetDisplayName(bind_context_com_interface, None)
    if excel_application_class_clsid not in display_name:
      continue
    unknown_com_interface = running_object_table_com_interface.GetObject(moniker_com_interface)
    dispatch_com_interface = unknown_com_interface.QueryInterface(dispatch_com_interface_iid)
    dispatch_clsid = str(object=dispatch_com_interface.GetTypeInfo().GetTypeAttr().iid)
    if dispatch_clsid != excel_application_clsid:
      continue
    excel_instance_com_object = dispatch(dispatch=dispatch_com_interface)
    excel_instance_com_objects.append(excel_instance_com_object)
  return excel_instance_com_objects

excel_instances = get_excel_instances()
input()

My code:

import ctypes

get_running_object_table = ctypes.oledll.ole32.GetRunningObjectTable

Now I need to pass 2 parameters to get_running_object_table function: number 0 and IRunningObjectTable (docs), but I have no ideas, what I need to do to create IRunningObjectTable...

@junkmd
Copy link
Collaborator

junkmd commented Mar 7, 2024

The IRunningObjectTable interface is published in the Microsoft win32metadata repository.
https://github.com/microsoft/win32metadata/blob/d1702f3d2b32b31fe2ffbe684ae6cff96e3a39b0/generation/WinSDK/RecompiledIdlHeaders/um/ObjIdl.Idl#L394-L440

By referring to this, it should be possible to define a subclass that inherits from IUnknown.

Moreover, when searching GitHub with IRunningObjectTable, it was possible to find numerous codebases that seem to be useful as references.

@junkmd
Copy link
Collaborator

junkmd commented Mar 16, 2024

Is there an update on this issue?

@sindzicat
Copy link
Author

sindzicat commented Mar 16, 2024

Sorry, your last answer was still difficult for me. A few days ago I found this article how to use ctypes to work with Win32 API. After this excellent article, I have a much better understanding of your answer.

Now I decided to learn a basics of C programming language, because I still have a poor C knowledge to use ctypes and comtypes efficiently. I steel not sure how to model IRunningObjectTable with ctypes/comtypes, because this object is an interface, not usual C structure. This object is looked like C structure of functions, but I'm not sure about this. Also, as an example, I don't know yet, what two stars means (**ppenumMoniker).

As for now using this answer from StackOverflow is the best way to solve this problem, so we can close this issue. Alternatively, you could create a function in comtypes that finds all instances. Anyway, it's interesting topic how to solve this using ctypes/comtypes and I hope I'll be able to do this in the future.

@junkmd
Copy link
Collaborator

junkmd commented Mar 16, 2024

Your attempt is wonderful.

I'm not familiar with C language either, and these are unknown territories for me.
However, if comtypes provides the statically defined IRunningObjectTable interface and implements GetRunningObjectTable in a Python-friendly manner, I think that would be fantastic.

If you could write a description of the usecase and tests, I can make resources to review your PR.

For sharing difficulties and technical consultations, I think we should keep this issue open.

@junkmd junkmd added enhancement New feature or request help wanted Extra attention is needed labels Mar 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants