powered by simpleCommunicator - 2.0.36     © 2025 Programmizd 02
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Проверить наличие метода в dynamic + System.__ComObject
25 сообщений из 30, страница 1 из 2
Проверить наличие метода в dynamic + System.__ComObject
    #40024689
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Делаю на позднем связывании коллбэк из дотнетовский библиотеки в VBA.

На VBA делаю класс с произвольным методом - Class1.OnComplete(x as String, y as Variant)
Передаю его в библиотеку: obj.Execute New Class1

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
public void Execute(
            [Optional, DefaultParameterValue(null)] object callback,
            [Optional, DefaultParameterValue("OnComplete")] string methodName)
        {
            if (callback != null)
            {
                _callback = callback; //_callback имеет тип dynamic
                var method = ((object)_callback).GetType().GetMethod(methodName);
                if (method == null)
                    LogAndThrow(new InvalidOperationException($"У объекта обратного вызова отсутствует метод {methodName}"));

            }
        }



Сам по себе _callback.OnComplete("xxx", 123) работает, в VBA код исполняется, но GetMethod(methodName) ничего не возвращает.
Соответственно, при попытке выполнить несуществующий метод вываливается exception "System.__ComObject" не содержит определения". А если сигнатура не соответствует передаваемым аргументам, то всякие другие ошибки.

Но нужно проверить наличие метода и его сигнатуру до вызова.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024694
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
GetMethod(methodName) ничего не возвращает

Конечно не возвращает. Потому что если можно сделать
Код: c#
1.
2.
dynamic foo = GetSomething();
foo.Bar();


то это еще не значит, что у объекта foo действительно есть метод Bar. Дайнемики могут использовать рефлекшен как механизм связывания, но совсем не обязательно. Можешь, например, посмотреть, как это в случае ExpandoObject делается. Ключевой момент там это интерфейс IDynamicMetaObjectProvider - когда объект его реализует, то динамическое связывание происходит с его помощью. Рефлекшен используется только как фоллбек если такого интерфейса у объекта нет.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024695
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
ЯННП. Как проверить наличие метода и его сигнатуру у COM-объекта?
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024719
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Нашел такой класс: https://gist.github.com/AlbertoMonteiro/d2d25923cc2aa2941a93

Вроде делает что нужно, но заточен под fw 1.1, в более поздних нет TypeToTypeInfoMarshaler
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024731
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Разобрался с TypeToTypeInfoMarshaler, но это ничем не помогло. В тестовом примере создаются системные com-объекты, для которых создается соответствующий .net-тип, а в случае с объектом VBA создается typeof(object).
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024756
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
Как проверить наличие метода и его сигнатуру у COM-объекта?

В случае позднего связывания - практически никак. Runtime callable wrapper генерится на лету при вызове метода какими очень глубоко спрятанными средствами CLR, причем там есть отличия от раннего связывания.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024823
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Кстати, есть один вариант. Раз эта штука работает с поздним связыванием, то, скорее всего, она имплементирует IDispatch. Можно импортировать в код объявление этого интерфейса, привести к нему объект, а потом дернуть у результата GetTypeInfoCount и GetTypeInfo (см. https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nn-oaidl-idispatch):
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
class Program
{
  const int S_OK = 0; //From WinError.h
  const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
  static void Main(string[] args)
  {
    var type = Type.GetTypeFromProgID("Scripting.FileSystemObject");
    dynamic instance = Activator.CreateInstance(type);
    if (!(instance is IDispatch iDisp))
      throw new ApplicationException();
    var hr = iDisp.GetTypeInfoCount(out var typeInfoCount);
    if (!(hr == S_OK && typeInfoCount > 0))
      throw new ApplicationException();
    iDisp.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out var result);
    if (result==null)
    {
      Marshal.ThrowExceptionForHR(hr);
      throw new TypeLoadException();
    }
    Console.WriteLine(result.AssemblyQualifiedName);
    foreach(var mi in result.GetMembers())
      Console.WriteLine(mi);
    Console.WriteLine("done");
    Console.ReadKey(true);
  }
}

[
  ComImport,
  Guid("00020400-0000-0000-C000-000000000046"), // IDispatch GUID
  InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
]
public interface IDispatch
{
  int GetTypeInfoCount(out int typeInfoCount);
  void GetTypeInfo(
    int typeInfoIndex,
    int lcid,
    [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]
    out Type typeInfo
  );
}


В консоли:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
Scripting.IFileSystem3, Scripting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Scripting.Drives get_Drives()
System.String BuildPath(System.String, System.String)
System.String GetDriveName(System.String)
System.String GetParentFolderName(System.String)
System.String GetFileName(System.String)
System.String GetBaseName(System.String)
System.String GetExtensionName(System.String)
System.String GetAbsolutePathName(System.String)
System.String GetTempName()
Boolean DriveExists(System.String)
Boolean FileExists(System.String)
Boolean FolderExists(System.String)
Scripting.Drive GetDrive(System.String)
Scripting.File GetFile(System.String)
Scripting.Folder GetFolder(System.String)
Scripting.Folder GetSpecialFolder(Scripting.SpecialFolderConst)
Void DeleteFile(System.String, Boolean)
Void DeleteFolder(System.String, Boolean)
Void MoveFile(System.String, System.String)
Void MoveFolder(System.String, System.String)
Void CopyFile(System.String, System.String, Boolean)
Void CopyFolder(System.String, System.String, Boolean)
Scripting.Folder CreateFolder(System.String)
Scripting.TextStream CreateTextFile(System.String, Boolean, Boolean)
Scripting.TextStream OpenTextFile(System.String, Scripting.IOMode, Boolean, Scripting.Tristate)
Scripting.TextStream GetStandardStream(Scripting.StandardStreamTypes, Boolean)
System.String GetFileVersion(System.String)
Scripting.Drives Drives

Чтобы приведенный код работал, в референсы нужно добавить сборку ComMarshalers. Она нужна для использования TypeToTypeInfoMarshaler - стандартно IDispatch::GetTypeInfo возращает ссылку на COM-интерфейс ITypeInfo , но с ним возиться не особенно интересно, а этот маршаллер по данным ITypeInfo возращает нормальный дотнетовский Type.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40024824
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Мда, по ссылке выше я и не ходил. TypeToTypeInfoMarshaler живет в отдельной сборке, см. выше. Прекрасно работает под фреймворком 4.7.2 (код выше).
Antonariy
а в случае с объектом VBA создается typeof(object)

Т.е. реализации IDispatch нет, только IUnknown? Что за объект? COM-интерфейс у него вообще какой-нибудь реализован?
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025055
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны
Мда, по ссылке выше я и не ходил. TypeToTypeInfoMarshaler живет в отдельной сборке, см. выше. Прекрасно работает под фреймворком 4.7.2 (код выше).
Antonariy
а в случае с объектом VBA создается typeof(object)

Т.е. реализации IDispatch нет, только IUnknown? Что за объект? COM-интерфейс у него вообще какой-нибудь реализован?

Это голый VBA-класс в экселе. И IDispatch он реализовывает, DispatchUtility.ImplementsIDispatch возвращает true.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025057
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны
Чтобы приведенный код работал, в референсы нужно добавить сборку ComMarshalers. Она нужна для использования TypeToTypeInfoMarshaler - стандартно IDispatch::GetTypeInfo возращает ссылку на COM-интерфейс ITypeInfo , но с ним возиться не особенно интересно, а этот маршаллер по данным ITypeInfo возращает нормальный дотнетовский Type.
С этим я разобрался, но в качестве дотнетовского типа возвращается object. Я полагаю это из-за того, что VBA-объекты существуют только в памяти и не зарегистрированы в реестре.

Однако dynamic способен вызывать их методы без каких-либо плясок с бубном. То есть способ получить имя метода VBA-объекта таки есть. Максимум, чего я пока добился - получил имя VBA-класса через TypeDescriptor.GetClassName. Но инфу о методах TypeDescriptor не возвращает (даже функционала такого нет), только о свойствах и событиях.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025065
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy,

А если так подумать, то зачем тебе нужен сам метод? Просто вызывай его и перехватывай ексепшен на случай если его нет.

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
dynamic dyn = new object();

try
{
    dyn.NoMethod();
}
catch (RuntimeBinderException)
{
    Console.WriteLine("Method not found");
}
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025082
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
Antonariy,

А если так подумать, то зачем тебе нужен сам метод? Просто вызывай его и перехватывай ексепшен на случай если его нет.

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
dynamic dyn = new object();

try
{
    dyn.NoMethod();
}
catch (RuntimeBinderException)
{
    Console.WriteLine("Method not found");
}

АТО я еще не перехватил! Да целых пять типов эксепшенов, чтобы рассказать поподробнее, как именно накосячено в сигнатуре.

Смысл в том, что это асинхронная обертка для синхронных COM-методов, то есть ВБА запускает в обертке метод, передавая коллбэк, и завершает работу, можно заняться в экселе другими делами. Метод долго что-то делает, а когда завершает, обертка дергает коллбэк и передает в него возвращаемое значение метода (если есть) или текст ошибки, а коллбэк тоже должен что-то сделать с ответом в экселе. Сейчас, если коллбэк реализован криво, то будет перехваченная ошибка, но выходит, что метод работал впустую, эксель коллбэк не получит. Поэтому нужно проверять сигнатуру заранее.

Знаю, что можно реализовать интерфейс, о котором знает обертка, и он есть, но в задании сказано ясно: позднее связывание тоже должно работать. И оно работает, но я хочу, чтобы работало лучше.

Следующая фича, которая не требуется, но которую хотелось бы сделать - передавать имя метода коллбэка. А вызов Invoke, который понимает только net-типы, обломается, точно так же не найдя у object указанного метода.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025160
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
С этим я разобрался, но в качестве дотнетовского типа возвращается object. Я полагаю это из-за того, что VBA-объекты существуют только в памяти и не зарегистрированы в реестре.

Однако dynamic способен вызывать их методы без каких-либо плясок с бубном. То есть способ получить имя метода VBA-объекта таки есть. Максимум, чего я пока добился - получил имя VBA-класса через TypeDescriptor.GetClassName. Но инфу о методах TypeDescriptor не возвращает (даже функционала такого нет), только о свойствах и событиях.

Понятно. Да, этот TypeToTypeInfoMarshaler просто вызывает Marshal.GetTypeForITypeInfo , а там внутри делается попытка загрузки библиотеки типов, если попытка неудачна - возвращается typeof(object). Для пользовательского экселевского класса никакой библиотеки типов нет, поэтому возвращает object.
Тогда остается хардкор с ITypeInfo. Отправная точка: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/681c87bf-cffc-4b18-8dc9-c23b31877880
Получаем вот такое:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
[ComVisible(true)]
public class NetTestImpl
{
  const int S_OK = 0; //From WinError.h
  const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
  public string GetInfo(object instance)
  {
    try
    {
      if (!(instance is IDispatch iDisp))
        return "Not instance of IDispatch";
      var hr = iDisp.GetTypeInfoCount(out var typeInfoCount);
      if (!(hr == S_OK && typeInfoCount > 0))
        return $"Failed: HR={hr:X}, typeInfoCount={typeInfoCount}";
      var sb = new StringBuilder();
      if (instance is IDispatch2 iDisp2)
      {
        iDisp2.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out var reflectedType);
        if (reflectedType==null)
          return $"Failed: HR={hr:X}, typeInfoCount={typeInfoCount}";
        sb.AppendLine($".Net type: {reflectedType.AssemblyQualifiedName}");
        foreach (var mi in reflectedType.GetMembers())
          sb.AppendLine($".Net type method: {mi}");
      }
      iDisp.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out var result);
      if (result == IntPtr.Zero)
        return $"Null result: HR={hr:X}, typeInfoCount={typeInfoCount}";
      var typeInfo = (ITypeInfo)Marshal.GetObjectForIUnknown(result);
      typeInfo.GetTypeAttr(out var ppTypeAttr);
      if (ppTypeAttr == IntPtr.Zero)
        return "GetTypeAttr==0";
      if (!(Marshal.PtrToStructure(ppTypeAttr, typeof(TYPEATTR)) is TYPEATTR typeAttr))
        return "Cannot marshal pointer to TYPEATTR";
      sb.AppendLine($"cFuncs count: {typeAttr.cFuncs}");
      for (var i = 0; i < typeAttr.cFuncs; i++)
      {
        typeInfo.GetFuncDesc(i, out var ppFuncDesc);
        if (ppFuncDesc == IntPtr.Zero)
        {
          sb.AppendLine($"ppFincDesc=0 for i={i}");
          continue;
        }
        if (!(Marshal.PtrToStructure(ppFuncDesc, typeof(FUNCDESC)) is FUNCDESC funcDesc))
        {
          sb.AppendLine($"Cannot marshal pointer to FUNCDESC for i={i}");
          continue;
        }
        var names = new string[1024];
        typeInfo.GetNames(funcDesc.memid, names, names.Length, out var pcNames);
        if (pcNames == 0)
        {
          sb.AppendLine($"pcNames=0 for i={i}");
          continue;
        }
        var actualNames = new string[pcNames];
        Array.Copy(names, actualNames, pcNames);
        sb.AppendLine($"Found method: {actualNames.Aggregate((a, b) => a + " " + b)}");
      }
      return sb.ToString();
    }
    catch(Exception e)
    {
      return $"Shit happens: {e}";
    }
  }


  [
    ComImport,
    Guid("00020400-0000-0000-C000-000000000046"), // IDispatch GUID
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
  ]
  interface IDispatch
  {
    int GetTypeInfoCount(out int typeInfoCount);
    void GetTypeInfo(
      int typeInfoIndex,
      int lcid,
      out IntPtr typeInfo
    );
  }

  [
    ComImport,
    Guid("00020400-0000-0000-C000-000000000046"), // IDispatch GUID
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
  ]
  interface IDispatch2
  {
    int GetTypeInfoCount(out int typeInfoCount);
    void GetTypeInfo(
      int typeInfoIndex,
      int lcid,
      [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]
      out Type typeInfo
    );
  }
}


В экселе сделал класс TestClass с одной процедуркой:
Код: vbnet
1.
2.
3.
Public Sub Callback()
    Debug.Print "Callback"
End Sub


и вызываю метод библиотечки (разумеется, предварительно её зарегистрировав):
Код: vbnet
1.
2.
3.
4.
5.
Option Explicit
Sub test()
    Dim test As New NetTest.NetTestImpl, tc As New TestClass
    Debug.Print test.GetInfo(tc)
End Sub


В окошке Immediate VBA получаю:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
.Net type: System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
.Net type method: Boolean Equals(System.Object)
.Net type method: Boolean Equals(System.Object, System.Object)
.Net type method: Boolean ReferenceEquals(System.Object, System.Object)
.Net type method: Int32 GetHashCode()
.Net type method: System.Type GetType()
.Net type method: System.String ToString()
.Net type method: Void .ctor()
cFuncs count: 1
Found method: Callback

Последняя строка - от метода класса. При наличии у метода параметров их имена будут следовать после названия метода.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025166
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны,

Теоретически, проверить наличие метода/свойства/события можно и без typelib, каким-либо образом вызвав IDispatch::GetIDsOfNames . Но как это сделать не прибегая к хакам с unmanaged кодом у меня никаких идей нет.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025170
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
Сон Веры Павловны,

Теоретически, проверить наличие метода/свойства/события можно и без typelib, каким-либо образом вызвав IDispatch::GetIDsOfNames . Но как это сделать не прибегая к хакам с unmanaged кодом у меня никаких идей нет.

Ну, если имя метода известно (а GetIDsOfNames мапит массив уже известных имен на массив с их DispId), то проще вызвать IDispatch::GetDispId. И по ссылке выше (на github) класс DispatchUtility уже содержит утильный метод TryGetDispId, который, если передать ему имя существующего метода, вернёт true и dispId этого метода, а если несуществующего - false.
Есть один нюанс: при вызове из C# IDispatch::GetDispId для несуществующего имени выбрасывает COMException, т.к. возвращает, по сути, HRESULT - фреймворк сам делает проверку на S_OK, и в случае неудачной проверки сам выбрасывает исключение.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025173
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны
И по ссылке выше (на github) класс DispatchUtility уже содержит утильный метод TryGetDispId

Antonariy
но заточен под fw 1.1, в более поздних нет TypeToTypeInfoMarshaler

В том-то и засада, что, похоже, просто так привести __ComObject к IDispatch нельзя.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025175
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
Сон Веры Павловны
И по ссылке выше (на github) класс DispatchUtility уже содержит утильный метод TryGetDispId

Antonariy
но заточен под fw 1.1, в более поздних нет TypeToTypeInfoMarshaler

В том-то и засада, что, похоже, просто так привести __ComObject к IDispatch нельзя.
22242905 - всё прекрасно приводится.
22242905 (в самом конце) - TypeToTypeInfoMarshaler перенесен в сборку CustomMarshallers. При её подключении всё прекрасно маршаллится и работает.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025180
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны
22242905 - всё прекрасно приводится.


Код: c#
1.
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]


TypeToTypeInfoMarshaler не поддерживается в .NET Core
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025186
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
Сон Веры Павловны
22242905 - всё прекрасно приводится.


Код: c#
1.
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]


TypeToTypeInfoMarshaler не поддерживается в .NET Core
В нем вообще COM поддерживается разве? Core же кроссплатформенный, где COM на других платформах?.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025187
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
TypeToTypeInfoMarshaler не поддерживается в .NET Core

А класс Marshal там поддерживается? В частности, с методом GetTypeForITypeInfo?
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025188
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
В нем вообще COM поддерживается разве? Core же кроссплатформенный, где COM на других платформах?.

Да вроде как вполне: https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cominterop
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025223
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
В нем вообще COM поддерживается разве? Core же кроссплатформенный, где COM на других платформах?.

Там даже класс Registry поддерживается изначально, и WinForms начиная с 3.1, просто оно на не-Win платформах работать не будет, хотя даже и вполне соберется.
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025419
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры Павловны
Antonariy
С этим я разобрался, но в качестве дотнетовского типа возвращается object. Я полагаю это из-за того, что VBA-объекты существуют только в памяти и не зарегистрированы в реестре.

Однако dynamic способен вызывать их методы без каких-либо плясок с бубном. То есть способ получить имя метода VBA-объекта таки есть. Максимум, чего я пока добился - получил имя VBA-класса через TypeDescriptor.GetClassName. Но инфу о методах TypeDescriptor не возвращает (даже функционала такого нет), только о свойствах и событиях.

Понятно. Да, этот TypeToTypeInfoMarshaler просто вызывает Marshal.GetTypeForITypeInfo , а там внутри делается попытка загрузки библиотеки типов, если попытка неудачна - возвращается typeof(object). Для пользовательского экселевского класса никакой библиотеки типов нет, поэтому возвращает object.
Тогда остается хардкор с ITypeInfo. Отправная точка: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oaut/681c87bf-cffc-4b18-8dc9-c23b31877880
Получаем вот такое:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
[ComVisible(true)]
public class NetTestImpl
{
  const int S_OK = 0; //From WinError.h
  const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
  public string GetInfo(object instance)
  {
    try
    {
      if (!(instance is IDispatch iDisp))
        return "Not instance of IDispatch";
      var hr = iDisp.GetTypeInfoCount(out var typeInfoCount);
      if (!(hr == S_OK && typeInfoCount > 0))
        return $"Failed: HR={hr:X}, typeInfoCount={typeInfoCount}";
      var sb = new StringBuilder();
      if (instance is IDispatch2 iDisp2)
      {
        iDisp2.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out var reflectedType);
        if (reflectedType==null)
          return $"Failed: HR={hr:X}, typeInfoCount={typeInfoCount}";
        sb.AppendLine($".Net type: {reflectedType.AssemblyQualifiedName}");
        foreach (var mi in reflectedType.GetMembers())
          sb.AppendLine($".Net type method: {mi}");
      }
      iDisp.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out var result);
      if (result == IntPtr.Zero)
        return $"Null result: HR={hr:X}, typeInfoCount={typeInfoCount}";
      var typeInfo = (ITypeInfo)Marshal.GetObjectForIUnknown(result);
      typeInfo.GetTypeAttr(out var ppTypeAttr);
      if (ppTypeAttr == IntPtr.Zero)
        return "GetTypeAttr==0";
      if (!(Marshal.PtrToStructure(ppTypeAttr, typeof(TYPEATTR)) is TYPEATTR typeAttr))
        return "Cannot marshal pointer to TYPEATTR";
      sb.AppendLine($"cFuncs count: {typeAttr.cFuncs}");
      for (var i = 0; i < typeAttr.cFuncs; i++)
      {
        typeInfo.GetFuncDesc(i, out var ppFuncDesc);
        if (ppFuncDesc == IntPtr.Zero)
        {
          sb.AppendLine($"ppFincDesc=0 for i={i}");
          continue;
        }
        if (!(Marshal.PtrToStructure(ppFuncDesc, typeof(FUNCDESC)) is FUNCDESC funcDesc))
        {
          sb.AppendLine($"Cannot marshal pointer to FUNCDESC for i={i}");
          continue;
        }
        var names = new string[1024];
        typeInfo.GetNames(funcDesc.memid, names, names.Length, out var pcNames);
        if (pcNames == 0)
        {
          sb.AppendLine($"pcNames=0 for i={i}");
          continue;
        }
        var actualNames = new string[pcNames];
        Array.Copy(names, actualNames, pcNames);
        sb.AppendLine($"Found method: {actualNames.Aggregate((a, b) => a + " " + b)}");
      }
      return sb.ToString();
    }
    catch(Exception e)
    {
      return $"Shit happens: {e}";
    }
  }


  [
    ComImport,
    Guid("00020400-0000-0000-C000-000000000046"), // IDispatch GUID
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
  ]
  interface IDispatch
  {
    int GetTypeInfoCount(out int typeInfoCount);
    void GetTypeInfo(
      int typeInfoIndex,
      int lcid,
      out IntPtr typeInfo
    );
  }

  [
    ComImport,
    Guid("00020400-0000-0000-C000-000000000046"), // IDispatch GUID
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
  ]
  interface IDispatch2
  {
    int GetTypeInfoCount(out int typeInfoCount);
    void GetTypeInfo(
      int typeInfoIndex,
      int lcid,
      [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(TypeToTypeInfoMarshaler))]
      out Type typeInfo
    );
  }
}


В экселе сделал класс TestClass с одной процедуркой:
Код: vbnet
1.
2.
3.
Public Sub Callback()
    Debug.Print "Callback"
End Sub


и вызываю метод библиотечки (разумеется, предварительно её зарегистрировав):
Код: vbnet
1.
2.
3.
4.
5.
Option Explicit
Sub test()
    Dim test As New NetTest.NetTestImpl, tc As New TestClass
    Debug.Print test.GetInfo(tc)
End Sub


В окошке Immediate VBA получаю:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
.Net type: System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
.Net type method: Boolean Equals(System.Object)
.Net type method: Boolean Equals(System.Object, System.Object)
.Net type method: Boolean ReferenceEquals(System.Object, System.Object)
.Net type method: Int32 GetHashCode()
.Net type method: System.Type GetType()
.Net type method: System.String ToString()
.Net type method: Void .ctor()
cFuncs count: 1
Found method: Callback

Последняя строка - от метода класса. При наличии у метода параметров их имена будут следовать после названия метода.
Спасибо, работает. Возвращает имена методов и аргументов. А типы аргументов как получить?
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025567
Сон Веры Павловны
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Antonariy
А типы аргументов как получить?

Да всё так же, вознёй с ITypeInfo. Класс в аттаче - много строк, используется как-то так:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
public string GetInfo(object instance)
{
  try
  {
    var sb = new StringBuilder();
    var reflector = ComReflector.Create(instance);
    sb.AppendLine($"Reflected type: {reflector.ReflectedType}");
    sb.AppendLine($"Type name: {reflector.TypeName}");
    foreach (var i in reflector.Interfaces)
      sb.AppendLine($"Implements: {i}");
    sb.AppendLine($"Methods: {reflector.MethodsCount}");
    sb.AppendLine("========================================");
    foreach (var mi in reflector.Methods.Where(m => m != null))
    {
      sb.AppendLine($"Method name={mi.Name}, PropertyGet={mi.PropertyGet}, PropertySet={mi.PropertySet}, PropertySetRef={mi.PropertySetRef}");
      sb.AppendLine($"Parameters: {mi.ParametersCount}");
      foreach (var pi in mi.Parameters.Where(p => p != null))
        sb.AppendLine(string.Format("\tName={0}, IsOut={1}, type={2}", pi.Name, pi.IsOut,
          pi.TypeDescription == null ? "null" : "\r\n" + pi.TypeDescription.ToString(2)
        ));
      if (mi.IsProperty) continue;
      sb.AppendLine(string.Format("Return type: {0}", mi.ReturnType == null ? "void" : mi.ReturnType.ToString(0)));
      sb.AppendLine("------------------------------------------------------");
    }
    return sb.ToString();
  }
  catch (Exception e)
  {
    return $"Shit happens: {e}";
  }
}


Для нормальных зарегистрированных типов выдается вполне вменяемая информация - например, для метода Scripting.FileSystemObject.CreateTextFile
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Method name=CreateTextFile, PropertyGet=False, PropertySet=False, PropertySetRef=False
Parameters: 3
        Name=FileName, IsOut=False, type=
                Kind=Other, VarEnum=VT_BSTR, Description=VT_BSTR
        Name=Overwrite, IsOut=False, type=
                Kind=Other, VarEnum=VT_BOOL, Description=VT_BOOL
        Name=Unicode, IsOut=False, type=
                Kind=Other, VarEnum=VT_BOOL, Description=VT_BOOL
Return type: Kind=Pointer, VarEnum=VT_PTR, Description=(empty)
        Kind=UserDefined, VarEnum=VT_USERDEFINED, Description=ITextStream

(информация о типе может быть рекурсивной, т.к. если используется ссылочный тип, то это VT_PTR, т.е. ссылка на другой тип).
А вот для методов класса из VBA выдаётся фигня:
Код: vbnet
1.
2.
3.
Public Function Callback(ByVal Application As Excel.Application, ByVal StrParam As String, ByVal IntParam As Long, ByVal VarParam As Variant) As String
    Debug.Print "Callback"
End Function


Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Method name=Callback, PropertyGet=False, PropertySet=False, PropertySetRef=False
Parameters: 4
    Name=Application, IsOut=False, type=
        Kind=Other, VarEnum=VT_EMPTY, Description=VT_EMPTY
    Name=StrParam, IsOut=True, type=
        Kind=Other, VarEnum=VT_NULL, Description=VT_NULL
    Name=IntParam, IsOut=True, type=
        Kind=Other, VarEnum=VT_EMPTY, Description=VT_EMPTY
    Name=VarParam, IsOut=True, type=
        Kind=Other, VarEnum=VT_EMPTY, Description=VT_EMPTY
Return type: Kind=Other, VarEnum=VT_BSTR, Description=VT_BSTR

Поэтому, по-моему, в данном случае уместнее было бы в дотнетовской библиотеке объявить COM-интерфейс для коллбэка:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
namespace NetTest
{
  [ComVisible(true)]
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
  [Guid("7ABA5EE3-BAFA-4C54-9436-7E87E92A6CC1")]
  public interface ICallback
  {
    string Callback(
      Microsoft.Office.Interop.Excel.Application Application,
      string StrParam,
      int IntParam,
      object VarParam
    );
    // Просто для примера
    string CallbackInfo();
  }
}


и реализовывать его в VBA-классе:
Код: vbnet
1.
2.
3.
4.
5.
6.
7.
8.
Implements NetTest.ICallback

Public Function ICallback_Callback(ByVal Application As Excel.Application, ByVal StrParam As String, ByVal IntParam As Long, ByVal VarParam As Variant) As String
    Debug.Print "Callback"
End Function
Public Function ICallback_CallbackInfo() As String
    ICallback_CallbackInfo = "Some callback info"
End Function


Тогда, если метод, принимающий объект с коллбэком, объявит в сигнатуре тип аргумента как ICallback:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
[ComVisible(true)]
[Guid("D74C1421-B3EF-4660-9F39-FC840DA1ED72")]
[ProgId("NetTest.NetTestImpl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class NetTestImpl
{
  public string GetInfo(ICallback instance)
  {
    ...


то при попытке передать в этот метод из VBA объект, не реализующий ICallback, на рантайме вылетит ошибка "Run-time error '13': Type mismatch". А если даже оставить сигнатуру с типом dynamic, то фреймворк вполне может проверить, реализует ли объект ICallback, и привести его к интерфейсу:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
[ComVisible(true)]
[Guid("D74C1421-B3EF-4660-9F39-FC840DA1ED72")]
[ProgId("NetTest.NetTestImpl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class NetTestImpl
{
  public string GetInfo(dynamic instance)
  {
    try
    {
      var sb = new StringBuilder();
      IComReflector reflector = ComReflector.Create(instance);
      sb.AppendLine($"Reflected type: {reflector.ReflectedType}");
      sb.AppendLine($"Type name: {reflector.TypeName}");
      if (instance is ICallback cb)
        sb.AppendLine($"Callback info: {cb.CallbackInfo()}");
      ....


- в окошке Immediate будет
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
Reflected type: System.Object
Type name: TestClass
Callback info: Some callback info
Implements: IDispatch
Methods: 2
========================================
Method name=ICallback_Callback, PropertyGet=False, PropertySet=False, PropertySetRef=False
Parameters: 4
    Name=Application, IsOut=False, type=
        Kind=Pointer, VarEnum=VT_PTR, Description=(empty)
            Kind=Other, VarEnum=VT_EMPTY, Description=VT_EMPTY
    Name=StrParam, IsOut=False, type=
        Kind=Other, VarEnum=VT_BSTR, Description=VT_BSTR
    Name=IntParam, IsOut=False, type=
        Kind=Other, VarEnum=VT_I4, Description=VT_I4
    Name=VarParam, IsOut=False, type=
        Kind=Other, VarEnum=VT_VARIANT, Description=VT_VARIANT
Return type: Kind=Other, VarEnum=VT_BSTR, Description=VT_BSTR
------------------------------------------------------
Method name=ICallback_CallbackInfo, PropertyGet=False, PropertySet=False, PropertySetRef=False
Parameters: 0
Return type: Kind=Other, VarEnum=VT_BSTR, Description=VT_BSTR
------------------------------------------------------
...
Рейтинг: 0 / 0
Проверить наличие метода в dynamic + System.__ComObject
    #40025570
Фотография Antonariy
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сон Веры ПавловныПоэтому, по-моему, в данном случае уместнее было бы в дотнетовской библиотеке объявить COM-интерфейс для коллбэка:В ТЗ прописано позднее связывание.
...
Рейтинг: 0 / 0
25 сообщений из 30, страница 1 из 2
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Проверить наличие метода в dynamic + System.__ComObject
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]