Отсутствует сообщение компилятора VBA для неправильного имени метода

Рассмотрим следующий код:

Public Sub VBACompilerIsMad()

    Dim Ap As Application     
    Dim Wb As Workbook
    Dim Ws As Worksheet

    Debug.Print Ap.XXX ' No compile error
    Debug.Print Wb.XXX ' No compile error
    Debug.Print Ws.XXX ' Compile error

End Sub

Когда я компилирую это, я получаю ошибку компилятора для ссылки на несуществующий член Worksheet, Однако, если я закомментирую последнюю строку, ошибки компилятора не будет, даже если Application ни Workbook есть метод или свойство XXX, Это как если бы я объявил Ap а также Wb как Object переменные.

Почему компилятор лечит Application / Workbook в отличие от Worksheet?

Есть ли какие-либо другие классы, подобные этому, которые компилятор, кажется, обрабатывает так, как будто они Object?

4 ответа

Решение

Как я уже объяснил (слава соответственно), это функция COM.

По умолчанию COM предполагает, что интерфейс является расширяемым, то есть он позволяет добавлять элементы во время выполнения. Если это не желаемое поведение, можно применить [nonextensible] атрибут определения интерфейса, который объявляет, что интерфейс принимает только методы, явно определенные в библиотеке типов.

dispinterface _Application а также dispinterface _Workbook не устанавливайте этот флаг в библиотеке типов Excel, dispinterface _Worksheet делает.

Точно так же ADO dispinterface _Connection не имеет [nonextensible], dispinterface _Command делает.

Чтобы узнать, какие являются расширяемыми, добавьте ссылку на TypeLib Info в Референции проекта и запустите:

Dim t As tli.TLIApplication
Set t = New tli.TLIApplication

Dim ti As tli.TypeLibInfo
Set ti = t.TypeLibInfoFromFile("excel.exe")

Dim i As tli.InterfaceInfo
For Each i In ti.Interfaces
    If (i.AttributeMask And tli.TYPEFLAG_FNONEXTENSIBLE) <> tli.TYPEFLAG_FNONEXTENSIBLE Then
      Debug.Print i.Name
  End If
Next

Вы увидите, что почти все интерфейсы здесь расширяемы, поэтому большинство из них выталкиваются из окна отладки, и вы увидите только последние. Изменить <> в = чтобы напечатать те, которые не являются расширяемыми, их намного меньше.

Немного гипотеза:

Вы можете вызвать хранимую процедуру для объекта ADODB.Connection, как собственный метод (внизу). (Примеры для этого на нескольких сайтах MSDN выглядят странно запутанными).
Таким образом, в VBS/VBA есть некоторый механизм, например "анонимные / динамические методы". Это может быть аналогичный механизм, активированный здесь для Application а также Workbook занятия - хотя я не вижу, где и как именно.

Тест поддерживает основную идею:
Я проверил это со ссылкой на Microsoft ActiveX Data Objects 2.8 Library:

Public Sub testCompiler()
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command

    Debug.Print cn.XXX
    Debug.Print cmd.XXX
End Sub

cn.XXX не выдает ошибку компиляции, cmd.XXX делает.

Ответ GSerg действительно выдающийся, мне нравится весь IDL библиотеки типов COM и то, как некоторые атрибуты могут управлять поведением в Excel VBA IDE. Долго может быть передано это тайное знание COM! И я понимаю, что этот вопрос был задуман, чтобы дать этому ответу больше репутации, но когда щедрость установлена, она появляется на моем радаре, и у меня есть взгляд на этот вопрос.

Таким образом, хотя ответ GSerg дает механизм, он не дает обоснования, то есть он дает как, но не почему. Я попытаюсь ответить почему.

Некоторые из ответов, почему Мартин Роллер (OP) уже дает свои комментарии о Application а также WorksheetFunction, Для меня это убедительная причина сохранить Application расширяемый и я не буду рассматривать Application в дальнейшем.

Давайте обратимся к Workbook а также Worksheet и лучше всего начать с некоторого кода для демонстрации, поэтому вам нужно начать с двух свежих рабочих книг, называть их MyWorkbook.xlsm а также OtherWorkbook.xlsm, Итак, некоторые инструкции:

В OtherWorkbook.xlsm иди код модуля ThisWorkbook и вставьте код

Option Explicit

Public Function SomeFunctionExportedOffOtherWorkbook() As String
    SomeFunctionExportedOffOtherWorkbook = "Hello Matt's Mug!"
End Function

В MyWorkbook.xlsm иди Sheet1 модуль кода и вставьте код

Option Explicit

Public Function SomeFunctionExportedOffCodeBehindSheet1() As String
    SomeFunctionExportedOffCodeBehindSheet1 = "Hello Martin Roller!"
End Function

Теперь в VBA IDE измените кодовое имя Sheet1 в codebehindSheet1Теперь в новом стандартном модуле MyWorkbook.xlsm добавьте следующий код

Sub TestingObjectLikeInterfacesOfWorkbookAndCodeBehindWorksheet_RunMany()

    '* For this example please rename the 'CodeName' for Sheet1 to be "codebehindSheet1" using the IDE
    Debug.Assert ThisWorkbook.Worksheets.Item("Sheet1").CodeName = "codebehindSheet1"


    Dim wb As Workbook
    Set wb = Application.Workbooks.Item("OtherWorkbook")

    '* Workbook dispinterface needs to not marked with nonextensible attribute
    '* so that it doesn't trip up over exported function in another workbook
    '* below SomeFunctionExportedOffOtherWorkbook is defined in the ThisWorkbook module of the workbook "OtherWorkbook.xlsm"
    Debug.Print wb.SomeFunctionExportedOffOtherWorkbook


    '*Not allowed --> Dim foo As Sheet1
    '*have to call by the 'code behind' name which is usually Sheet1 but which we changed to illustrate the point
    Debug.Print codebehindSheet1.SomeFunctionExportedOffCodeBehindSheet1


End Sub

Теперь запустите этот код выше.

Вы, наверное, прочитали код и, надеюсь, поняли смысл, который я изложил, но позвольте мне изложить его. Нам нужно Workbook оставаться расширяемым, потому что он может содержать ссылку на другую книгу, которая может экспортировать метод или функцию, и мы не хотели бы ошибок компиляции.

Однако для WorksheetЧтобы сделать аналогичный экспорт, мы снова добавляем код в код позади модуля, но есть разница в ссылке на модуль: один захватывает ссылку на этот код позади модуля, используя его кодовое имя VBA, большинство людей не изменяют это с Sheet1 (именно поэтому вы были приглашены изменить его выше).

Таким образом, интерфейс, полученный с помощью кода за именем модуля, должен быть расширяемым, а не интерфейс Excel.Worksheet.

PS Кто-нибудь получил копию TLI.dll?

В качестве обходного пути все еще можно создать свой собственный interface и реализовать этот интерфейс. Затем объявите переменную как INewInterface и все сообщения компилятора будут там:). Вот простой пример с пользовательским интерфейсом для UserForm, НТН

Интерфейс

Public CancelButton As MSForms.CommandButton
Public DataList As MSForms.ListBox
Public CommandBox As MSForms.TextBox

Реализация

Implements IMyForm

Private Property Set IMyForm_CancelButton(ByVal RHS As MSForms.ICommandButton)

End Property

Private Property Get IMyForm_CancelButton() As MSForms.ICommandButton

End Property

Private Property Set IMyForm_CommandBox(ByVal RHS As MSForms.IMdcText)

End Property

Private Property Get IMyForm_CommandBox() As MSForms.IMdcText

End Property

Private Property Set IMyForm_DataList(ByVal RHS As MSForms.IMdcList)

End Property

Private Property Get IMyForm_DataList() As MSForms.IMdcList

End Property

использование

Замечания: MyForm существует форма VBA, которая была добавлена ​​в проект.

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