这个问题很长,所以我会用子弹点格式化以便于讨论
所以Apples界面和类的源代码是:
public interface IApples
{
string variety { get; set; }
int quantity { get; set; }
}
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IApples))]
public class Apples : IApples
{
public string variety { get; set; }
public int quantity { get; set; }
}
以上代码无可争议且工作正常。
FruitContainer接口和类的源代码是
public interface IFruitCounter
{
Apples[] enumerateApples();
}
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IFruitCounter))]
public class FruitCounter : IFruitCounter
{
public Apples[] enumerateApples()
{
List<Apples> applesList = new List<Apples>();
//* Add some apples - well, one in fact for the time being
Apples app = new Apples();
app.variety = "Braeburn";
app.quantity = 4;
applesList.Add(app);
// * finished adding apples want to convert to SAFEARRAY
return applesList.ToArray();
}
}
这适用于早期绑定,但不适用于后期绑定。
Sub TestEarlyBound() 'Tools -> References to type library LateBoundSafeArraysProblem.tlb Dim fc As LateBoundSafeArraysProblem.FruitCounter Set fc = New LateBoundSafeArraysProblem.FruitCounter Dim apples() As LateBoundSafeArraysProblem.apples apples() = fc.enumerateApples() Stop End Sub
当执行到达Stop时,可以检查数组内容以获得成功的编组。 早期结合的成功!
在同一模块中添加以下代码
Sub TestFruitLateBound0()Dim fc As Object 'LateBoundSafeArraysProblem.FruitCounter Set fc = CreateObject("LateBoundSafeArraysProblem.FruitCounter") Dim apples() As Object 'LateBoundSafeArraysProblem.apples apples() = fc.enumerateApples() '<==== Type Mismatch thrown Stop End Sub
运行此代码会在标记的行处引发类型不匹配(VB错误13)。因此,相同的COM服务器代码在Excel VBA后期绑定模式下不起作用。 失败的后期约束!
所以在调查过程中我写了一个返回类型为Object []的第二个方法,最初这个没有用,因为生成的idl下降为星号。 idl来自
{{1}}
到
// Return type Apples[] works for early binding but not late binding // works HRESULT enumerateApples([out, retval] SAFEARRAY(IApples*)* pRetVal);
使用MarshalAs属性修复了星号数
// Return type Object[] fails because we drop a level of indirection // (perhaps confusion between value and reference types) // does NOT work AT ALL (late or early) HRESULT enumerateApplesLateBound([out, retval] SAFEARRAY(VARIANT)* pRetVal); // dropped as asterisk becomes SAFEARRAY to value types, no good
这适用于后期绑定但不适用于绑定! Aaaargh!
我有两个方法正在工作,一个用于早期绑定,一个用于后期绑定,这是不能令人满意的,因为它意味着将每个方法加倍。
答案 0 :(得分:2)
我通过将返回的值编组到Variant
对此进行了一些测试,然后转储返回的VARIANT结构的内存以查看VARTYPE是什么。对于早期绑定调用,它返回Variant
,其中VARTYPE为VT_ARRAY & VT_DISPATCH
。对于后期绑定调用,它返回的是VT_ARRAY & VT_UNKNOWN
的VARTYPE。苹果 应该已经被定义为在tlb中实现IDispatch
,但由于某些原因,我不知道,VBA很难处理来自后期绑定调用的IUnknown
数组。解决方法是将返回类型更改为C#端的object[]
...
public object[] enumerateApples()
{
List<object> applesList = new List<object>();
//* Add some apples - well, one in fact for the time being
Apples app = new Apples();
app.variety = "Braeburn";
app.quantity = 4;
applesList.Add(app);
// * finished adding apples want to convert to SAFEARRAY
return applesList.ToArray();
}
...并将它们拉入VBA端的Variant
:
Sub TestEarlyBound()
'Tools -> References to type library LateBoundSafeArraysProblem.tlb
Dim fc As LateBoundSafeArraysProblem.FruitCounter
Set fc = New LateBoundSafeArraysProblem.FruitCounter
Dim apples As Variant
apples = fc.enumerateApples()
Debug.Print apples(0).variety 'prints "Braeburn"
End Sub
Sub TestFruitLateBound0()
Dim fc As Object
Set fc = CreateObject("LateBoundSafeArraysProblem.FruitCounter")
Dim apples As Variant
apples = fc.enumerateApples()
Debug.Print apples(0).variety 'prints "Braeburn"
End Sub