将COM接口传递回库的正确方法是什么?
问题描述:
当试图将接口作为参数从.NET传递到DLL时,我得到一个System.InvalidCastException
运行时错误。代码在.NET方面失败了,从来没有交给Delphi方面。将COM接口传递回库的正确方法是什么?
在Delphi中构建一个简单的ActiveX库dll。两个自动化对象:MyContent
和MyContainer
。 MyContainer
具有单一方法Add
,其需要由IMyContent
继承的IBase
接口。 IBase
是IDispatch
[
uuid(E29018FF-F142-4BAE-B7A4-AE0A8847E930),
version(1.0)
]
library DelphiComLib
{
importlib("stdole2.tlb");
interface IMyBase;
interface IMyContainer;
coclass MyContainer;
interface IMyContent;
coclass MyContent;
[
uuid(07D17021-9E7F-4D7A-B861-59A35EC686A0),
dual,
oleautomation
]
interface IMyBase: IDispatch
{
};
[
uuid(A6AB6F7D-8BEB-459F-A2F8-BC06FF81A45D),
helpstring("Dispatch interface for MyContainer Object"),
dual,
oleautomation
]
interface IMyContainer: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall Add([in] IMyBase* AMyBase);
};
[
uuid(AB82964C-13D7-423B-9B16-A789D5D30421),
helpstring("Dispatch interface for MyContent Object"),
dual,
oleautomation
]
interface IMyContent: IMyBase
{
};
[
uuid(DDDF77E5-E6A6-4429-BD4A-D9695E9E6CED),
helpstring("MyContainer Object")
]
coclass MyContainer
{
[default] interface IMyContainer;
};
[
uuid(134BF8B2-30C3-4C37-8F74-3F677808300A),
helpstring("MyContent Object")
]
coclass MyContent
{
[default] interface IMyContent;
};
};
类的实现并不重要。为了保持最小的样本,Add
的实现可以留空。这里是集装箱
unit Unit1;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiComLib_TLB, StdVcl;
type
TMyContainer = class(TAutoObject, IMyContainer)
protected
procedure Add(const AMyBase: IMyBase); safecall;
end;
implementation
uses ComServ;
procedure TMyContainer.Add(const AMyBase: IMyBase);
begin
end;
initialization
TAutoObjectFactory.Create(ComServer, TMyContainer, Class_MyContainer,
ciMultiInstance, tmApartment);
end.
...和内容
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiComLib_TLB, StdVcl;
type
TMyContent = class(TAutoObject, IMyContent)
protected
end;
implementation
uses ComServ;
initialization
TAutoObjectFactory.Create(ComServer, TMyContent, Class_MyContent,
ciMultiInstance, tmApartment);
end.
注册库并添加引用到一个控制台应用程序。如果我在实现IBase
我可以通过接口回成功.NET创建一个类,下面的代码将产生运行时错误
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DelphiComLib;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
IMyContainer container = new MyContainer();
IMyContent content = new MyContent();
container.Add(content);
}
}
}
。
这里是.NET
堆栈跟踪at System.StubHelpers.InterfaceMarshaler.ConvertToNative(Object objSrc, IntPtr itfMT, IntPtr classMT, Int32 flags)
at DelphiComLib.IMyContainer.Add(IMyBase AMyBase)
at ConsoleApplication2.Program.Main(String[] args) in c:\users\jaspers\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs:line 15
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
答
您的问题无关的接口是如何传递到您的COM DLL。
错误System.InvalidCastException
仅仅是因为从Delphi实现端投射IMyContent
到IMyBase
失败。原因是严格地说你的实现并不直接实现IMyBase
接口(尽管它实现了派生的IMyContent
接口)。你可以尝试一下:
var
MyContent: IMyContent;
MyBase: IMyBase;
begin
MyContent := CoMyContent.Create;
MyBase := MyContent as IMyBase; // EIntfCastError is raised
end;
你的实现类必须实现IMyBase
明确的铸造工作:
这将改变你的实现类的声明:
type
TMyContent = class(TAutoObject, IMyContent, IMyBase)
...
end;
这反过来将使任何COM clie的铸造工作nt代码(Delphi,C++,.NET等)