从托管代码到本地代码对字符串数组进行编组

6

我有一个带有以下声明的托管函数(包括接口和实现):

[return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
String[] ManagedFunction()
{
    String[] foo = new String[1];
    foo[0] = "bar";
    return foo;
}

还有一个本地的C++接口,具有与托管接口相同的方法。在该接口内部,此方法具有以下声明:

void ManagedFunction(SAFEARRAY* foo); 

这个函数是由本地代码按以下方式调用的:
void NativeFunction(ManagedBinding binding)
{
    CComSafeArray<BSTR> cComSafeArray;
    cComSafeArray.Create(); 
    LPSAFEARRAY safeArray = cComSafeArray.Detach();
    binding.comObject->ManagedFunction(safeArray); 
}

我不确定自己做错了什么,但在我的托管函数被调用后,safeArray似乎有着垃圾值。返回值从托管代码转换回本地代码时,出现了问题。请有更多.Net互操作经验的人给我们指点迷津。另外,可能要提到的是,我从我的托管函数返回ValueType(如果您好奇,是boolean),但从托管函数返回String数组时出了问题。谢谢!
2个回答

1

1) 你的函数返回一个SAFEARRAY,那么为什么在调用函数之前要分配它?
2) ManagedFunction应该返回一个SAFEARRAY,所以它应该得到一个SAFEARRAY*才能够返回它!因此你应该这样说:

LPSAFEARRAY lpsa;
binding.comObject->ManagedFunction(&lpsa);
CComSafeArray<BSTR> cComSafeArray;
cComSafeArray.Attach(lpsa);

那部分代码我并不特别想去修改。我想我应该提一下这点。我不知道为什么要以这种方式在 binding.comObject 上调用该方法,但我相信肯定有一个很好的理由。ManagedFunction 在获取数组方面没有任何问题。问题在于将其转换回本地代码时出现了问题。 - Tejas Sharma
问题在于 ManagedFunction 不应该获取 SAFEARRAY,因为您是通过 COM 对象调用托管代码,在 COM 中返回值标记为 [out, retval] 属性,这会导致编组程序从调用方而不是从被调用方进行编组。 - BigBoss
好的,但这正是我想要的,数组一开始就是空的。只要在调用者内部所做的更改能够传播回来,我就会很高兴。我尝试了许多其他技术(例如将SAFEARRAY作为“out”参数传递给托管函数,而不是将其保留为返回值),但它们都没有起作用。我认为,在编组字符串或引用类型的数组方面存在某种复杂性,这并没有被充分记录。 - Tejas Sharma

0

好了,我终于让它工作了。我创建了一个名为ManagedSafeArraySAFEARRAY托管表示(从这里偷来的:http://social.msdn.microsoft.com/Forums/en-US/clr/thread/6641abfc-3a9c-4976-a523-43890b2b79a2/):

[StructLayout(LayoutKind.Sequential)]
struct ManagedSafeArray
{
    public ushort   dimensions;     // Count of dimensions in the SAFEARRAY
    public ushort   features;       // Flags to describe SAFEARRAY usage
    public uint     elementSize;    // Size of an array element
    public uint     locks;          // Number of times locked without unlocking
    public IntPtr   dataPtr;        // Pointer to the array data
    public uint     elementCount;   // Element count for first (only) dimension
    public int      lowerBound;     // Lower bound for first (only) dimension
}

我将方法的签名更改为:

void ManagedMethod(ref ManagedSafeArray foo);

在我的方法中,我通过调用Marshal.AllocCoTaskMem(...)手动更新了dataPtr字段,然后复制了我想要SAFEARRAY包含的字符串。
我不知道为什么CLR无法自动将参数封送到本机代码并从本机代码返回,如果有人能够解释一下,我仍然会很感激。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接