Использование COM DLL из C# без библиотеки типов
Мне нужно использовать COM-компонент (DLL), разработанный в Delphi давным-давно. Проблема в том, что dll не содержит библиотеки типов... и каждая функция взаимодействия (например, TlbImp) в.NET, похоже, полагается на TLB. Компонент использовался здесь в программах Delphi в течение многих лет без проблем, потому что "Использование объектов COM из Delphi не представляет большой проблемы, потому что мы знаем интерфейсы" (цитата из Delphi Developer).
Есть ли способ, которым я могу использовать эту DLL из C# без TLB? Я пытался использовать DLL как неуправляемую, но единственный метод, который она экспортирует, это DllUnregisterServer
, DllRegisterServer
, DllCanUnloadNow
а также DllGetClassObject
, Я знаю названия классов и функций, которые я собираюсь использовать, если это может помочь.
ОБНОВЛЕНИЕ: я попытался реализовать предложение Джеффа, но я получаю эту ошибку:
"Невозможно привести объект COM типа" ComTest.ResSrvDll "к типу интерфейса" ComTest.IResSrvDll ". Эта операция завершилась неудачно, поскольку вызов QueryInterface для компонента COM для интерфейса с IID" {75400500-939F-11D4-9E44-0050040CE72C}'не удалось из-за следующей ошибки: такой интерфейс не поддерживается (Исключение из HRESULT: 0x80004002 (E_NOINTERFACE))."
Вот что я сделал:
Я получил это определение интерфейса от одного из Delphi-парней:
unit ResSrvDllIf;
interface
type
IResSrvDll = interface
['{75400500-939F-11D4-9E44-0050040CE72C}']
procedure clearAll;
function ResObjOpen(const aClientID: WideString; const aClientSubID: WideString;
const aResFileName: WideString; aResShared: Integer): Integer; {safecall;}
...
end;
implementation
end.
Из этого я сделал этот интерфейс
using System.Runtime.InteropServices;
namespace ComTest
{
[ComImport]
[Guid("75400500-939F-11D4-9E44-0050040CE72C")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IResSrvDll
{
int ResObjOpen(string aClientID, string aClientSubID, string aResFileName, int aResShared);
}
}
И этот класс (получил гид от дельфи-парней)
using System.Runtime.InteropServices;
namespace ComTest
{
[ComImport]
[Guid("75400503-939F-11D4-9E44-0050040CE72C")]
public class ResSrvDll
{
}
}
ОБНОВИТЬ
Решение от Джеффа - способ сделать это. Однако стоит отметить, что определение интерфейса должно точно соответствовать COM-компонентам! то есть. тот же порядок, те же имена и т. д.
7 ответов
Вам просто нужно CLS_ID и идентификатор интерфейса. Я написал об этой конкретной проблеме в своем блоге:
Если вам удалось создать экземпляр объекта, вы преодолели первое серьезное препятствие!
Теперь попробуйте это:
myObject.GetType().InvokeMember(
"ResObjOpen", // method name goes here
BindingFlags.InvokeMethod,
null,
myObject,
new object[] {
someClientID, // arguments go here
someSubId,
somFileName,
someInt} );
Я думаю, что вам может потребоваться сделать это потому, что объект Delphi COM не является "двойным" объектом. Он может поддерживать только позднюю привязку, то есть вид вызова, который вы видите выше.
(В C# 4.0 они делают это проще с dynamic
ключевое слово.)
РЕДАКТИРОВАТЬ: Просто заметил что-то очень подозрительное. IID для интерфейса и CLSID для самого объекта выглядят одинаково. Это не правильно.
Учитывая, что вам удалось создать объект, он будет выглядеть как CLSID объекта. Так что это не правильный IID. Вам нужно вернуться к своим людям в Delphi и попросить их рассказать, что такое IID интерфейса. IResSrvDll
является.
Изменить снова: вы можете попробовать изменить член enum, который вы указали ComInterfaceType
, Там должны быть для IDispatch
и "двойной" - хотя ваш объект не поддерживает IDispatch
ни один из них не должен быть правильным выбором. IUnknown
настройка (которая появляется в вашем примере кода) должна работать - предполагая, что IID неверен.
Написать обертку в VB.Net. VB.Net поддерживает истинную позднюю привязку (без грязного отражения). Все, что вам нужно, это progId. Вы также должны реализовать IDisposable для явного управления жизненным циклом компонента.
Очень часто вы сталкиваетесь с реализацией интерфейса, которая не поддерживается библиотекой типов (Delphi или иным образом). Расширения оболочки являются одним из примеров.
В основном вам нужно сделать вызов API Windows, чтобы создать экземпляр с помощью соответствующих вызовов функций COM. API позаботится об управлении DLL через экспортированные функции, которые вы упомянули ранее.
Вам нужно будет воссоздать определение интерфейса в коде C#, но после этого вы просто создаете объект, приводите его к интерфейсу, и он ничем не отличается от всего остального. Единственное реальное предостережение: в зависимости от вашего использования у вас могут возникнуть проблемы с многопоточностью, поэтому проверьте "модель многопоточности", которая использовалась для DLL, и рассмотрите возможность ее использования.
Вот ссылка на руководство по использованию интерфейсов, которые не основаны на TLB. Руководство
И да и нет.
Все, что нужно C# (и любому языку CLR) для связи с COM-объектом, - это подпись совместимого интерфейса. Типично указание методов, GUID и стиля квартиры интерфейса. Если вы можете добавить это определение в свою кодовую базу, тогда TLB не нужен.
С этим утверждением связана небольшая оговорка. Я полагаю, что у вас возникнут проблемы, если вы попытаетесь использовать COM-объект за пределами квартир и не зарегистрируете подходящий TLB. Я не могу вспомнить 100% по этому вопросу.
Вы также можете выполнить позднее связывание, а затем вызывать методы через отражение (myObject.InvokeMember("NameOfTheMethod", options, params, etc.)
).
Обертка должна, однако, предлагать лучшую производительность и более быстрое сортирование.
Я подозреваю, что dynamic
ключевое слово (C# 4.0) выполнит это. Если это произойдет, это даст результаты, которые в значительной степени эквивалентны вызову методов, то есть, как предлагает Груо.