Как создать функцию, определенную модулем, в библиотеке типов COM

Библиотека типов VBE7.dll, используемая VBA, имеет следующий MIDL для Conversion модуль:

[
dllname("VBE7.DLL"),
uuid(36785f40-2bcc-1069-82d6-00dd010edfaa),
helpcontext(0x000f6ebe)
]
module Conversion {
[helpcontext(0x000f6ea2)]
BSTR _stdcall _B_str_Hex([in] VARIANT* Number);
[helpcontext(0x000f652a)]
VARIANT _stdcall _B_var_Hex([in] VARIANT* Number);
[helpcontext(0x000f6ea4)]
BSTR _stdcall _B_str_Oct([in] VARIANT* Number);
[helpcontext(0x000f6557)]
VARIANT _stdcall _B_var_Oct([in] VARIANT* Number);
[hidden, helpcontext(0x000f6859)]
long _stdcall MacID([in] BSTR Constant);
[helpcontext(0x000f6ea9)]
BSTR _stdcall _B_str_Str([in] VARIANT* Number);
[helpcontext(0x000f658a)]
VARIANT _stdcall _B_var_Str([in] VARIANT* Number);
[helpcontext(0x000f659f)]
double _stdcall Val([in] BSTR String);
[helpcontext(0x000f64c8)]
BSTR _stdcall CStr([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
BYTE _stdcall CByte([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
VARIANT_BOOL _stdcall CBool([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
CY _stdcall CCur([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
DATE _stdcall CDate([in] VARIANT* Expression);
[helpcontext(0x000f6e7a)]
VARIANT _stdcall CVDate([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
short _stdcall CInt([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
long _stdcall CLng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
int64 _stdcall CLngLng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
LONG_PTR#i _stdcall CLngPtr([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
float _stdcall CSng([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
double _stdcall CDbl([in] VARIANT* Expression);
[helpcontext(0x000f64c8)]
VARIANT _stdcall CVar([in] VARIANT* Expression);
[helpcontext(0x000f64b5)]
VARIANT _stdcall CVErr([in] VARIANT* Expression);
[helpcontext(0x000f6c6d)]
BSTR _stdcall _B_str_Error([in, optional] VARIANT* ErrorNumber);
[helpcontext(0x000f6c6d)]
VARIANT _stdcall _B_var_Error([in, optional] VARIANT* ErrorNumber);
[helpcontext(0x000f649b)]
VARIANT _stdcall Fix([in] VARIANT* Number);
[helpcontext(0x000f6533)]
VARIANT _stdcall Int([in] VARIANT* Number);
[helpcontext(0x000f64c8)]
HRESULT _stdcall CDec(
[in] VARIANT* Expression,
[out, retval] VARIANT* pvar
);
};

Где мне особенно интересно, как VBA интерпретирует HRESULT возврате CDec функция (последняя функция в MIDL выше), так что в VBA, CDec функция имеет подпись

Function CDec(Expression)

Это кажется как VBA следит за HRESULT возвращая определение TLB, поэтому для проверки теории я хотел бы создать свой собственный TLB, который определяет HRESULT возвращающая функция в пределах module, а затем посмотрите, как VBA относится к этой функции.

Я не верю, что это можно сделать в C # или VB.NET, и когда я попытался определить функцию в стандартном модуле в VB6, модуль не был виден в DLL.

Возможно ли это с помощью C ++? Какой проект мне нужно создать? Что-нибудь особенное, что мне нужно сделать? Возможно, мне нужно отредактировать MIDL вручную?

Примечание: я специально не отмечаю этот вопрос как VBA, как я пытаюсь интерпретировать TLB из C #. Чтобы проверить, как хост VBA интерпретирует TLB, я хотел бы создать соответствующий TLB на любом языке, который его поддерживает. В моем распоряжении Visual Studio 6, 2003, 2013 и 2015.

8

Решение

Что важно в декларации CDec, так это атрибуты [out] и [retval] вместе взятые.
Инструменты, которые это понимают (например, VB / VBA), смогут упростить компиляцию вызовов этого метода, маскируя обработку ошибок, поэтому

HRESULT _stdcall CDec(
[in] VARIANT* Expression,
[out, retval] VARIANT* pvar
);

является эквивалент в

VARIANT _stdcall CDec([in] VARIANT* Expression);

эквивалент здесь не означает, что оно эквивалентно в двоичной форме, это просто означает, что инструменты, которые понимают, что синтаксис будет в порядке, чтобы использовать (и скомпилировать в конечной двоичной цели) первое выражение, когда они увидят второе.
Это также подразумевает, что если есть ошибка (ошибка HRESULT), то инструмент должен вызвать ошибку любым способом, который он сочтет нужным (VB / VBA сделает это).

Это простосинтаксический сахар».

Вы можете написать это, используя MIDL, но также .NET: просто создайте стандартную библиотеку классов с помощью Visual Studio и добавьте этот пример класса c #:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1
{
public object Test(object obj)
{
return obj;
}
}

Скомпилируйте это и запустите инструмент regasm, чтобы зарегистрировать его, с помощью команды, подобной этой:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\regasm "C:\mypath\ClassLibrary1\bin\Debug\classlibrary1.dll" /tlb /codebase

Это зарегистрирует класс как COM-объект и создаст C:\mypath\ClassLibrary1\bin\Debug\classlibrary1.tlb файл библиотеки типов.

Теперь запустите Excel (вы можете использовать любой клиент, совместимый с автоматизацией COM) и добавьте ссылку на ClassLibrary1 (режим разработчика, редактор VBA, Инструменты / Справочные материалы).
Если вы этого не видите, возможно, вы бежите с другой битностью. Можно использовать COM для связи 32-64, но пока просто убедитесь, что ваш клиент работает с той же битрейтом, что и ваш ClassLibrary1.dll был скомпилирован.

Получив ссылку, добавьте немного кода VB, например, так.

Sub Button1_Click()
Dim c1 As New Class1
output = c1.Test("hello from VB")
End Sub

Как вы увидите, VB intellisense покажет метод, который мы ожидаем, как в C #, и он отлично работает.

Теперь давайте попробуем использовать его из C ++: создайте консольный проект (снова убедитесь, что битность совместима) и добавьте в него следующий код:

#include "stdafx.h" // needs Windows.h

#import "c:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscorlib.tlb" // adapt to your context
#import "C:\\mypath\\ClassLibrary1\\bin\\Debug\\classlibrary1.tlb"
using namespace ClassLibrary1;

int main()
{
CoInitialize(NULL);

_Class1Ptr c1(__uuidof(Class1));
_variant_t output = c1->Test(L"hello from C++");

wprintf(L"output: %s\n", V_BSTR(&output));

CoUninitialize();
return 0;
}

Это также будет хорошо работать, и код выглядит близко к VB. Обратите внимание, что я использовал магию Visual Studio директива #import что очень круто, потому что он маскирует многие детали COM Автоматизация сантехника (как это делают VB / VBA), включая BSTR и различные умные классы.

Давайте нажмем на Test позвоните и выполните определение Goto (F12), вот что мы видим:

inline _variant_t _Class1::Test ( const _variant_t & obj ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = raw_Test(obj, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}

ха-ха! Это, в основном, то, что делает VB / VBA. Мы можем видеть, как выполняется обработка исключений. Опять же, если вы делаете F12 на _Class1Ptrвот что вы увидите (упрощенно):

_Class1 : IDispatch
{
// Wrapper methods for error-handling

...
_variant_t Test (
const _variant_t & obj );
...

// Raw methods provided by interface
...
virtual HRESULT __stdcall raw_Test (
/*[in]*/ VARIANT obj,
/*[out,retval]*/ VARIANT * pRetVal ) = 0;

};

Мы здесь. Как видите, Test Метод, сгенерированный C # в его двоичной форме, имеет [out, retval] Форма, как и ожидалось. Все остальное — сахар и обертки.
Большинство методов COM-интерфейсов на двоичном уровне разработаны с использованием [out, retval], поскольку компиляторы не поддерживают общий совместимый двоичный формат для возврата функции.

Что определяет VBE7, так это диспетчерский интерфейс, снова некоторая форма синтаксического сахара для определения интерфейсов поверх интерфейса COM raw / binary IUnknown.
Единственная загадка, почему CDec определяется иначе, чем другие методы в VBE7. У меня нет ответа на это.

Теперь конкретно о module Ключевое слово в IDL, IDL — это просто инструмент абстрактных определений (функций, констант, классов и т. д.), который при желании выводит артефакты (.H, .C, .TLB и т. д.), предназначенные для определенного языка (C / C ++ и т. д.). ) или для конкретных клиентов.

Бывает, что VB / VBA поддерживает константы и методы TLB. Он интерпретирует константы как то, что они есть, и функционирует в модулях как экспорт DLL из имени dll модуля.

Так что, если вы создаете это my.idl файл где-то на вашем диске:

[
uuid(00001234-0001-0000-0000-012345678901)
]
library MyLib
{
[
uuid(00001234-0002-0000-0000-012345678901),
dllname("kernel32.dll")
]
module MyModule
{
const int MyConst = 1234;

// note this is the real GetCurrentThreadId from kernel32.dll
[entry("GetCurrentThreadId")]
int GetCurrentThreadId();
}
}

Вы можете скомпилировать TLB из него следующим образом:

midl c:\mypath\my.idl /out c:\mypath

Это создаст my.tlb файл, на который вы можете ссылаться в VB / VBA. Теперь из VB / VBA у вас есть новая доступная функция (с ней будет работать intellisense), которая называется GetCurrentThreadId, Это работает, потому что ядро ​​Windows32.dll экспортирует GetCurrentThreadId функция.

Вы можете только создать DLL Exports из проектов C / C ++ (и из других языков / инструментов, таких как Delphi), но не из VB / VBA, а не из .NET.

На самом деле есть несколько хитростей для создания экспорта в .NET, но это не совсем стандартно: Можно ли экспортировать функции из C # DLL, как в VS C ++?

6

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]