将 SAFEARRAY 从 c++ 返回到 c#

Posted

技术标签:

【中文标题】将 SAFEARRAY 从 c++ 返回到 c#【英文标题】:Return SAFEARRAY from c++ to c# 【发布时间】:2015-06-23 19:18:40 【问题描述】:

我有一个创建、填充和返回 SAFEARRAY 的 c++ 方法:

SAFEARRAY* TestClass::GetResult(long& size) 

    return GetSafeArrayList(size);


How should I export that function in a DLL so that c# could take it
How should I write c# method signature?

我在 c++ 中有一些类似的东西:

extern "C" __declspec(dllexport) void GetResult(SAFEARRAY*& data, long& size)

    size = 0;
    data = handle->GetResult(size);

对不对?

感谢您的帮助!

编辑:

c#调用:

public static extern void GetResult(IntPtr handle, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_USERDEFINED)] TestStruct[] data, ref int size);

【问题讨论】:

顺便说一句:返回 'SAFEARRAY*' 是最不安全的方式 您为什么使用 SafeArray?如果要在 C++/C# 之间传递数组,可以使用 PInvoke @Matt 我被建议在.NET 端已知的 SAFEARRAY 中从 c++ 返回我的数据。也许我做错了什么。请指出正确的方向。 见:***.com/questions/30670551/… 我只是不知道c#部分怎么写 【参考方案1】:

使用SAFEARRAY(int) C#->C++->C# 的完整示例(因此该数组使用 C# 中的一些数据进行初始化,传递给 C++,在那里修改并返回给 C#。

C++:

// For the various _t classes for handling BSTR and IUnknown
#include <comdef.h>

struct ManagedUDT

    BSTR m_str01;
    int m_int01;

    ~ManagedUDT()
    
        ::SysFreeString(m_str01);
        m_str01 = NULL;
    
;

extern "C" __declspec(dllexport) void GetResult(SAFEARRAY*& data)

    if (data != NULL)
    
        // Begin print content of SAFEARRAY
        VARTYPE vt;
        HRESULT hr = SafeArrayGetVartype(data, &vt);

        if (SUCCEEDED(hr))
        
            // To make this code simple, we print only
            // SAFEARRAY(VT_I4)
            if (vt == VT_I4)
            
                int *pVals;
                hr = SafeArrayAccessData(data, (void**)&pVals); // direct access to SA memory

                if (SUCCEEDED(hr))
                
                    long lowerBound, upperBound;  // get array bounds
                    SafeArrayGetLBound(data, 1, &lowerBound);
                    SafeArrayGetUBound(data, 1, &upperBound);

                    long cnt_elements = upperBound - lowerBound + 1;

                    for (int i = 0; i < cnt_elements; i++)  // iterate through returned values
                    
                        int val = pVals[i];
                        printf("C++: %d\n", val);
                    

                    SafeArrayUnaccessData(data);
                
                else
                
                    // Error
                
            
        
        else
        
            // Error
        
        // End print content of SAFEARRAY

        // Delete the SAFEARRAY if already present
        SafeArrayDestroy(data);
        data = NULL;
    

    
        // Creation of a new SAFEARRAY
        SAFEARRAYBOUND bounds;
        bounds.lLbound = 0;
        bounds.cElements = 10;

        data = SafeArrayCreate(VT_I4, 1, &bounds);
        int *pVals;

        HRESULT hr = SafeArrayAccessData(data, (void**)&pVals); // direct access to SA memory

        if (SUCCEEDED(hr))
        
            for (ULONG i = 0; i < bounds.cElements; i++)
            
                pVals[i] = i + 100;
            
        
        else
        
            // Error
        
    

C#

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void GetResult([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] ref int[] ar);

var data = new int[]  1, 2, 3, 4, 5 ;
GetResult(ref data);

if (data != null)

    for (int i = 0; i < data.Length; i++)
    
        Console.WriteLine("C#: 0", data[i]);
    

else

    Console.WriteLine("C#: data is null");

部分代码取自 https://***.com/a/12484259/613130 和 https://***.com/a/3735438/613130


SAFEARRAY(VT_RECORD)

这是可行的……非常困难……但可行。请不要这样做。你不能憎恨这个世界来做这件事。我希望你不要!

C++:

// For the _com_util
#include <comdef.h>

extern "C"

    __declspec(dllexport) void GetResultSafeArray(SAFEARRAY *&psa)
    
        // All the various hr results should be checked!
        HRESULT hr;

        // Begin sanity checks
        if (psa == NULL)
        
            // Error
        

        VARTYPE pvt;
        hr = ::SafeArrayGetVartype(psa, &pvt);

        if (pvt != VT_RECORD)
        
            // Error
        

        UINT size;
        size = ::SafeArrayGetElemsize(psa);

        if (size != sizeof(ManagedUDT))
        
            // Error
        

        // From tests done, it seems SafeArrayGetRecordInfo does a AddRef
        _com_ptr_t<_com_IIID<IRecordInfo, NULL> > prinfo;
        // The_com_ptr_t<>::operator& is overloaded
        hr = ::SafeArrayGetRecordInfo(psa, &prinfo);

        // From tests done, it seems GetName returns a new instance of the
        // BSTR
        // It is ok to use _bstr_t.GetAddress() here, see its description
        _bstr_t name1;
        hr = prinfo->GetName(name1.GetAddress());

        const _bstr_t name2 = _bstr_t(L"ManagedUDT");

        if (name1 != name2)
        
            // Error
        

        // End sanity checks

        long lowerBound, upperBound;  // get array bounds
        hr = ::SafeArrayGetLBound(psa, 1, &lowerBound);
        hr = ::SafeArrayGetUBound(psa, 1, &upperBound);
        long cnt_elements = upperBound - lowerBound + 1;

        // Begin print
        ManagedUDT *pVals;
        hr = ::SafeArrayAccessData(psa, (void**)&pVals);

        printf("C++:\n");

        for (int i = 0; i < cnt_elements; ++i)
        
            ManagedUDT *pVal = pVals + i;

            // If you are using a recent VisualC++, you can
            // #include <memory>, and then
            //std::unique_ptr<char[]> pstr(_com_util::ConvertBSTRToString(pVal->m_str01));
            // and you don't need the char *pstr line and the delete[]
            // line
            char *pstr = _com_util::ConvertBSTRToString(pVal->m_str01);
            printf("%s, %d\n", pstr, pVal->m_int01);
            delete[] pstr;
        

        hr = ::SafeArrayUnaccessData(psa);
        // End print

        // Begin free
        SAFEARRAYBOUND sab;
        sab.lLbound = 0;
        sab.cElements = 0;

        // SafeArrayRedim will call IRecordInfo::RecordClear
        hr = ::SafeArrayRedim(psa, &sab);
        // End Free

        // Begin create
        int numElements = 10;
        sab.cElements = numElements;
        hr = ::SafeArrayRedim(psa, &sab);

        hr = ::SafeArrayAccessData(psa, (void**)&pVals);

        for (int i = 0; i < numElements; i++)
        
            ManagedUDT *pVal = pVals + i;

            char pstr[100];
            sprintf(pstr, "Element #%d", i);
            pVal->m_str01 = _com_util::ConvertStringToBSTR(pstr);

            pVal->m_int01 = 100 + i;
        

        hr = ::SafeArrayUnaccessData(psa);
        // End create
    

    __declspec(dllexport) void GetResultSafeArrayOut(SAFEARRAY *&psa, ITypeInfo *itypeinfo)
    
        // All the various hr results should be checked!
        HRESULT hr;

        // Begin sanity checks
        if (psa != NULL)
        
            // Begin free
            // SafeArrayDestroy will call IRecordInfo::RecordClear
            // if necessary
            hr = ::SafeArrayDestroy(psa);
            // End Free
        

        // Begin create
        int numElements = 10;

        SAFEARRAYBOUND sab;
        sab.lLbound = 0;
        sab.cElements = numElements;

        // The_com_ptr_t<>::operator& is overloaded
        _com_ptr_t<_com_IIID<IRecordInfo, NULL> > prinfo;
        hr = ::GetRecordInfoFromTypeInfo(itypeinfo, &prinfo);

        psa = ::SafeArrayCreateVectorEx(VT_RECORD, 0, numElements, prinfo);

        ManagedUDT *pVals;
        hr = ::SafeArrayAccessData(psa, (void**)&pVals);

        for (int i = 0; i < numElements; i++)
        
            ManagedUDT *pVal = pVals + i;

            char pstr[100];
            sprintf(pstr, "Element #%d", i);
            pVal->m_str01 = _com_util::ConvertStringToBSTR(pstr);

            pVal->m_int01 = 100 + i;
        

        hr = ::SafeArrayUnaccessData(psa);
        // End create
    

C#:

[ComVisible(true)]
[Guid("BBFE1092-A90C-4b6d-B279-CBA28B9EDDFA")]
[StructLayout(LayoutKind.Sequential)]
public struct ManagedUDT

    [MarshalAs(UnmanagedType.BStr)]
    public string m_str01;
    public Int32 m_int01;


[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResultSafeArray([MarshalAs(UnmanagedType.SafeArray)] ref ManagedUDT[] array);

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResultSafeArrayOut([MarshalAs(UnmanagedType.SafeArray)] out ManagedUDT[] array, IntPtr itypeinfo);

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "GetResultSafeArrayOut")]
static extern void GetResultSafeArrayRef([MarshalAs(UnmanagedType.SafeArray)] ref ManagedUDT[] array, IntPtr itypeinfo);

var arr = new[]

    new ManagedUDT  m_str01 = "Foo", m_int01 = 1,
    new ManagedUDT  m_str01 = "Bar", m_int01 = 2,
;


    Console.WriteLine("C#:");

    for (int i = 0; i < arr.Length; i++)
    
        Console.WriteLine("0, 1", arr[i].m_str01, arr[i].m_int01);
    



    Console.WriteLine();

    var arr2 = (ManagedUDT[])arr.Clone();

    GetResultSafeArray(ref arr2);

    Console.WriteLine();

    Console.WriteLine("C#:");

    for (int i = 0; i < arr2.Length; i++)
    
        Console.WriteLine("0, 1", arr2[i].m_str01, arr2[i].m_int01);
    



    Console.WriteLine();

    ManagedUDT[] arr2;

    IntPtr itypeinfo = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
    GetResultSafeArrayOut(out arr2, itypeinfo);

    Console.WriteLine();

    Console.WriteLine("C#:");

    for (int i = 0; i < arr2.Length; i++)
    
        Console.WriteLine("0, 1", arr2[i].m_str01, arr2[i].m_int01);
    



    Console.WriteLine();

    var arr2 = (ManagedUDT[])arr.Clone();

    IntPtr itypeinfo = Marshal.GetITypeInfoForType(typeof(ManagedUDT));
    GetResultSafeArrayRef(ref arr2, itypeinfo);

    Console.WriteLine();

    Console.WriteLine("C#:");

    for (int i = 0; i < arr2.Length; i++)
    
        Console.WriteLine("0, 1", arr2[i].m_str01, arr2[i].m_int01);
    

GetResultSafeArray 有一个重要的警告:您必须从 C# 传递至少一个空数组(如 new ManagedUDT[0])。这是因为要在 C++ 中从零创建一个 SAFEARRAY(ManagedUDT),您需要一个 IRecordInfo 对象。我不知道如何从 C++ 中检索它。如果您已经有一个SAFEARRAY(ManagedUDT),那么显然它已经设置了IRecordInfo,所以没有问题。在给出的示例中,在 C++ 中首先进行一些完整性检查,然后打印传递的数组,然后将其清空,然后重新填充。 GetResultSafeArrayOut/GetResultSafeArrayRef“作弊”:他们从 C# 接收到一个 ITypeInfo 指针(在 C# 中很容易用 Marshal.GetITypeInfoForType() 检索),并且 C++ 可以从中检索 IRecordInfo 接口。

一些注意事项:

我编写了 Ansi-charset-C++。通常对我自己来说,我总是编写 Unicode-ready C++(或直接 Unicode-C++,因为所有 Windows NT 都支持 Unicode),但我注意到我是一个例外......所以在代码的各个部分都有转换BSTR->Ansi->BSTR.

我正在检索所有函数调用的HRESULT。应该检查它们并处理故障。

C++/COM 中最复杂的事情是知道何时释放某些东西...通常总是释放/Release() 一切!(可能是BSTR/IUnknown派生接口,...)

除非存在错误,否则不支持此代码。将其视为概念证明。出于好奇,我已经失去了几个小时。你打破它,你修复它。

【讨论】:

嘿嘿,我想知道这里应该使用什么 vartype:data = SafeArrayCreate(VT_I4, 1, &bounds);用于指向结构的指针。例如 VT_BYREF 给我错误... @John 如果你想要结构,它变得非常困难......我什至不认为 C# 封送处理程序接受它......你必须使用 VT_RECORD 但它不起作用(有至少另一个人尝试过但对他不起作用here

以上是关于将 SAFEARRAY 从 c++ 返回到 c#的主要内容,如果未能解决你的问题,请参考以下文章

如何读取 C++ SAFEARRAY**,它是 C# 返回值为 byte[] 的 COM 互操作的结果?

C++ 托管 clr 调用库不在 safearray 参数中返回字符串

如何在 C++ 中创建和初始化双精度的 SAFEARRAY 以传递给 C#

遍历 BSTR 的 VARIANT/SAFEARRAY 以在 C++ 中分配值并打印到控制台

无法将 ByRef VARIANT 数组转换为 SAFEARRAY

SafeArray 的 COM SafeArray