I have a c++ method which creates, fills and returns 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?
I have in c++ something along these lines:
extern "C" __declspec(dllexport) void GetResult(SAFEARRAY*& data, long& size)
{
size = 0;
data = handle->GetResult(size);
}
Is it correct, isn't it?
Thanks for help!
EDIT:
c# call:
public static extern void GetResult(IntPtr handle, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_USERDEFINED)] TestStruct[] data, ref int size);
Full example of use of a SAFEARRAY(int)
C#->C++->C# (so the array is initialized with some data in C#, passed to C++, modified there and returned to 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);
and
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");
}
Code partially taken from https://stackoverflow.com/a/12484259/613130 and https://stackoverflow.com/a/3735438/613130
SAFEARRAY(VT_RECORD)
It is doable... Very hard... but doable. Please don't do it. You can't hate enough the world to do it. I do hope you don't!
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);
and
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);
}
}
There is a single big caveat for GetResultSafeArray
: you must pass from C# at least an empty array (like a new ManagedUDT[0]
). This because to create a SAFEARRAY(ManagedUDT)
from nothing in C++ you would need a IRecordInfo
object. I don't know how to retrieve it from C++. If you already have a SAFEARRAY(ManagedUDT)
then clearly it has the IRecordInfo
already set, so there is no problem. In the example given, in C++ there are first some sanity checks, then the passed array is printed, then it is emptied, then it is re-filled. The GetResultSafeArrayOut
/GetResultSafeArrayRef
"cheat": they receive from C# a ITypeInfo
pointer (that is easy to retrieve in C#, with Marshal.GetITypeInfoForType()
), and from taht the C++ can retrieve the IRecordInfo
interface.
Some notes:
I wrote Ansi-charset-C++. Normally for myself I always write Unicode-ready C++ (or directy Unicode-C++, because all the Windows NT support Unicode), but I've noticed that I'm an exception... So in various parts of the code there are conversions BSTR->Ansi->BSTR.
I'm retrieving the HRESULT
of all the function calls. They should be checked, and the failure handled.
The most complex thing in C++/COM is knowing when to free something... In general always free/Release()
everything! (be it BSTR
/IUnknown
derived interfaces, ...)
Unless there is a bug, there is no support for this code. Consider it to be a proof of concept. I already lost various hours on it out of curiosity. You break it, you repair it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With