Получение всех экземпляров приложения msaccess с помощью c# interop

Мне нужно получить список всех открытых в данный момент экземпляров MSACCESS в системе (окнах), чтобы иметь возможность закрыть любой из них из моего приложения. У меня нет проблем с EXCEL и WINWORD, но я не могу подключиться к Access.

Я использую Office 2016 и вижу, что MSACCESS создает отдельные процессы для каждого открытого файла базы данных. Поэтому я думаю, что мне нужно получать экземпляры приложений из оконных дескрипторов. Я пытался адаптировать этот код: Как перебирать экземпляр Excel C#

Я могу получить все процессы MSACCESS, но код Excel или Word не работает для MSACCESS. Строка кода:

if (buf.ToString() == "EXCEL7")

Всегда дает мне значение MsoCommandBarDock. Есть мысли о том, как я могу этого добиться?

имя класса главного окна MS Access оказывается OMain (стиль CS_OWNDC). почему это должен быть EXCEL7? имейте в виду, что это будет отличаться между версиями и может измениться в любое время, когда MS решит обновить. MsoCommandBarDock находится на один или два уровня глубже в иерархии окон.

Cee McSharpface 31.10.2018 12:53

В Access вы можете напрямую получить объект приложения из hWnd окна верхнего уровня. В Excel вам нужно получить конкретное вложенное окно и получить объект приложения из его hWnd. Это означает, что вы должны использовать значительно более простой подход для Access. Вы можете просто вызвать AccessibleObjectFromWindow напрямую с помощью hWnd, полученного от (int)p.MainWindowHandle. Вы можете отказаться от всего кода, перечисляющего дочерние окна. Я бы написал короткий ответ, но мой C# в лучшем случае начальный уровень, и у меня нет удобной тестовой среды, и это должно помочь вам начать.

Erik A 31.10.2018 13:24

если вы хотите просто закрыть приложение, почему бы просто не получить процесс по имени и не убить его?

Krish 31.10.2018 17:38

@krishKM Приложение должно сохранить данные и корректно закрываться. Простого убийства недостаточно :(

Alexander Smirnov 01.11.2018 10:43

@ErikvonAsmuth Вот и все! Чтобы получить доступ к классу приложения MSAccess, мне нужно пропустить процедуру поиска дочерних элементов. Отправьте новый ответ, чтобы я мог его принять.

Alexander Smirnov 01.11.2018 11:19

@dlatikay Выше я отмечал, что изучил код EXCEL, так как ничего не нашел для MSACCESS. И наткнулся именно на эту логику, поскольку понятия не имел, какое имя ожидать от MsAccess.

Alexander Smirnov 01.11.2018 11:21

@AlexanderSmirnov Я бы предпочел, чтобы вы сами написали ответ, поскольку, как уже говорилось, мой C# в лучшем случае начального уровня, и вы могли бы написать ответ, который включает требуемый код и который будет легко реализовать для будущих посетителей. Это важнее, чем любой набор повторений, который я мог бы получить.

Erik A 01.11.2018 11:24

это кажется более сложным, чем мы думаем: social.msdn.microsoft.com/Forums/en-US/… суть: неизвестно, какой из дескрипторов окна приложения доступа предоставляет IAccessible через AccessibleObjectFromWindow (и, что более важно, не задокументирован)

Cee McSharpface 01.11.2018 11:41

@dlatikay Возможно, это не задокументировано, но точно известно, я проверил это как для Access 2010, так и для Access 2016. Это окно верхнего уровня, которое имеет класс OMain. Хотя это может и не быть задокументировано, эти вещи редко документируются официально.

Erik A 01.11.2018 12:19

@dlatikay Кстати, этот пост, который вы связали, не соответствует третьему аргументу AccessibleObjectFromWindow. Это должен быть указатель на структуру guid, но вместо этого он передает идентификатор как байты по значению. Он заперт, поэтому я не могу ответить.

Erik A 01.11.2018 12:26
0
10
419
3

Ответы 3

На основе ответ для Excel версия Access аналогична:

const uint OBJID_NATIVEOM = 0xFFFFFFF0;

var procs = new List<Process>(Process.GetProcessesByName("MSACCESS.EXE"));

foreach (var p in procs)
{
    var mainHandle = (int)p.MainWindowHandle;
    if (mainHandle > 0)
    {
        var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        Microsoft.Office.Interop.Access.Application app = null;
        int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
        if (res >= 0)
        {
            Debug.Assert(app.hWndAccessApp == mainHandle);
            Console.WriteLine(app.Name);
        }
    }
}

Я тестировал его с помощью Access 2016 в Windows 10, локаль en-us. Основное отличие состоит в том, что иерархия доступа к окнам не такая запутанная, как в Excel, поэтому вы можете опустить итерацию дочерних окон.

Отказ от ответственности: это зависит от внутренней структуры приложения Windows с закрытым исходным кодом. Microsoft, как ее поставщик, не поощряет подобные уловки по очевидным причинам: они могут отправить и обновить или выпустить новую версию в любое время, когда внутренняя структура (иерархия окон) изменилась, нарушая код, который полагается на это. Кроме того, в MS Access был режим просмотра одного документа, который может представлять вам две версии иерархии окон в одном выпуске. Не делайте этого в коммерческих продуктах / продуктивном программном обеспечении.

Есть много способов сделать это, включая получение COM-объектов из ROT (таблица бегущих объектов). Поскольку вам нужно «просто», чтобы иметь возможность закрывать приложения, следующий код должен работать нормально.

using System.Diagnostics;
using System.Linq;

Process.GetProcessesByName("MSACCESS").All(x => x.CloseMainWindow());

Это отправляет закрыть сообщение в главные окна доступа все, что аналогично закрытию приложения пользователем.

Согласно отвечать от Cee McSharpface, в 2021 году (Microsoft Access для Microsoft 365 MSO (16.0.14326.20504) 64-бит и Windows 10 20H2) мне пришлось адаптировать решение следующим образом:

[DllImport("oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID, byte[] riid,
    ref Microsoft.Office.Interop.Access.Application ptr);  

const uint OBJID_NATIVEOM = 0xFFFFFFF0;

var procs = new List<Process>(Process.GetProcessesByName("MSACCESS"));

foreach (var p in procs)
{
    var mainHandle = (int)p.MainWindowHandle;
    if (mainHandle > 0)
    {
        var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        Microsoft.Office.Interop.Access.Application app = null;
        int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
        if (res >= 0)
        {
            Debug.Assert(app.hWndAccessApp() == mainHandle);
            Console.WriteLine(app.Name);
        }
    }
}

Обратите внимание на следующие изменения:

GetProcessesByName использует "MSACESS" вместо "MSACCESS.EXE", согласно документации это:

The process name is a friendly name for the process, such as Outlook, that does not include the .exe extension or the path

AccessibleObjectFromWindow использует ref Microsoft.Office.Interop.Access.Application ptr, потому что здесь нет объекта Window, как в случае взаимодействия с Excel.

Другие вопросы по теме