将SAFEARRAY从c ++返回到c#

我有一个c ++方法创建,填充并返回SAFEARRAY:

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(int)完整示例C# – > C ++ – > C#(因此,数组在C#中用一些数据初始化,传递给C ++,在那里修改并返回到C#)。

C ++:

 // For the various _t classes for handling BSTR and IUnknown #include  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://stackoverflow.com/a/12484259/613130和https://stackoverflow.com/a/3735438/613130


SAFEARRAY(VT_RECORD)

这是可行的......很难......但可行。 请不要这样做。 你不能讨厌世界做这件事。 我希望你不要!

C ++:

 // For the _com_util #include  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 > 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 , and then //std::unique_ptr 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 > 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)创建SAFEARRAY(ManagedUDT) ,您需要一个IRecordInfo对象。 我不知道如何从C ++中检索它。 如果你已经有了SAFEARRAY(ManagedUDT)那么显然它已经设置了IRecordInfo ,所以没有问题。 在给出的示例中,在C ++中首先进行一些健全性检查,然后打印传递的数组,然后将其清空,然后重新填充。 GetResultSafeArrayOut / GetResultSafeArrayRef “作弊”:他们从C#接收一个ITypeInfo指针(很容易在C#中检索,使用Marshal.GetITypeInfoForType() ),并且从Taht开始,C ++可以检索IRecordInfo接口。

一些说明:

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

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

  • C ++ / COM中最复杂的事情是知道何时释放某些东西......一般来说, 总是免费/ Release()一切! (无论是BSTR / IUnknown派生接口,......)

  • 除非有错误,否则不支持此代码。 认为它是一个概念certificate。 出于好奇,我已经失去了不同的时间。 你打破它,你修复它。