Passing string arrays from COM to C# - c#

I need to access C# methods in a COM dll via COM-like interface. One of the method requires an array of strings to be passed as input.
Am creating a SAFEARRAY and passing it to the COM Interop. However, this does not seem to be working, as I see an exception in the interop layer. (System.Runtime.InteropServices.SafeArrayTypeMismatchException).
Obviously, there seems to be a difference in type being expected.
Pasting the code here:
C# method to be invoked:
public long DoIt3(int nFiles, string[] fileNames);
C++ code invoking the same:
int _tmain()
{
TCHAR *fileNames[128] = { TEXT("C:\\Program Files\\IBM\\RTC.NET"),
TEXT("C:\\KIRAN\\Work\\RFT"), TEXT(".\\bin\\Debug") };
SAFEARRAY *pSA = CreateSafeStringArray(3, fileNames);
_tprintf(TEXT("%d"), pIManaged->DoIt3(3, pSA));
SafeArrayDestroy(pSA);
}
static SAFEARRAY *CreateSafeStringArray(long nElements, TCHAR *elements[])
{
SAFEARRAYBOUND saBound[1];
saBound[0].cElements = nElements;
saBound[0].lLbound = 0;
SAFEARRAY *pSA = SafeArrayCreate(VT_VARIANT, 1, saBound);
if (pSA == NULL)
{
return NULL;
}
for (int ix = 0; ix < nElements; ix++)
{
VARIANT v;
VariantInit(&v);
v.vt = VT_BSTR;
v.bstrVal = elements[ix];
long rgIndicies[1];
rgIndicies[0] = ix + saBound[0].lLbound;
HRESULT hr = SafeArrayPutElement(pSA, rgIndicies, &v);
_tprintf(TEXT("%d"), hr);
VariantClear(&v);
}
return pSA;
}
Any ideas/suggestions are welcome.

I figured it out! The following code works:
static SAFEARRAY *CreateSafeStringArray(long nElements, TCHAR *elements[])
{
SAFEARRAYBOUND saBound[1];
saBound[0].cElements = nElements;
saBound[0].lLbound = 0;
SAFEARRAY *pSA = SafeArrayCreate(VT_BSTR, 1, saBound);
if (pSA == NULL)
{
return NULL;
}
for (int ix = 0; ix < nElements; ix++)
{
BSTR pData = SysAllocString(elements[ix]);
long rgIndicies[1];
rgIndicies[0] = saBound[0].lLbound + ix;
HRESULT hr = SafeArrayPutElement(pSA, rgIndicies, pData);
_tprintf(TEXT("%d"), hr);
}
return pSA;
}
Thanks for all your suggestions!

In the case of an array of BSTR strings, you can set the BSTR values directly on your array, and also you need to allocate memory for your BSTR elements, you could use ATL/MFC CString for that:
...
psa = SafeArrayCreate( VT_BSTR, 1, saBound);
HRESULT hr = SafeArrayLock( psa );
//TODO: test for hr success
if (pSA == NULL)
{
return NULL;
}
for (int ix = 0; ix < nElements; ix++)
{
long rgIndicies[1];
rgIndicies[0] = ix + saBound[0].lLbound;
CString tempstr(elements[ix]);
((BSTR*)psa->pvData)[ix] = tempstr.AllocSysString();
_tprintf(TEXT("%d"), hr);
}
hr = SafeArrayUnlock( psa );
//TODO: test for hr success
...

Related

_AppDomainPtr Load_3 method example?

I am trying to load a C# assembly with _AppDomainPtr Load_3 method. Do you have some example since I get error:
hr = 0x80131533 : A mismatch has occurred between the runtime type of the array and the sub type recorded in the metadata.
Here is a snippet how do I load the assembly:
static void binarray(SAFEARRAY** output, const char* data, size_t size)
{
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = size;
*output = SafeArrayCreate(VT_R8, 1, &Bound);
double HUGEP *pdFreq;
HRESULT hr = SafeArrayAccessData(*output, (void HUGEP* FAR*)&pdFreq);
if (SUCCEEDED(hr))
{
// copy sample values from data[] to this safearray
for (DWORD i = 0; i < size; i++)
{
*pdFreq++ = data[i];
}
SafeArrayUnaccessData(*output);
}
}
And the call:
hr = spDefaultAppDomain->Load_3(output, &spAssembly);
Anyone used that?
Apparently I've found the problem. I was creating a SafeArray of Real numbers. The correct fix for anyone who needs that is:
static void binarray(SAFEARRAY** output, const unsigned char* data, size_t size)
{
SAFEARRAYBOUND Bound;
Bound.lLbound = 0;
Bound.cElements = size;
// VT_I1
*output = SafeArrayCreate(VT_UI1, 1, &Bound);
//VT_R8
unsigned char *pdFreq;
HRESULT hr = SafeArrayAccessData(*output, (void* FAR*)&pdFreq);
if (SUCCEEDED(hr))
{
// copy sample values from data[] to this safearray
for (DWORD i = 0; i < size; i++)
{
*pdFreq++ = data[i];
}
SafeArrayUnaccessData(*output);
}
}

IntPtr C# to C++ void*

I am a beginner in C# and I have been asked to create a C# code which uses a C++ DLL.
Functions from C++ DLL need a void* parameter so I send a IntPtr parameter from the C# code.
But at the end of this function my pointer seems to be NULL. Am I missing something?
Here my C# code:
int somme = 0;
IntPtr deviceHandle = new IntPtr();
uint[] SpiConf = new uint[7];
somme = Opening(deviceHandle, SpiConf);
if (deviceHandle == IntPtr.Zero)
{
Console.WriteLine("deviceHandle is NULL"); //
}
And here is my function from the C++ DLL:
int Opening(void* deviceHandle, unsigned char SpiConf[])
{
wchar_t devPath;
unsigned long devPathsize = 0;
unsigned short VID = 0x4d8;
unsigned short PID = 0xde;
int res;
deviceHandle = Mcp2210_OpenByIndex(VID, PID, 0, &devPath, &devPathsize);
res = Mcp2210_GetLastError();
if (res != E_SUCCESS)
{
//Console.WriteLine("Failed to open connection");
return -1;
}
if (deviceHandle == NULL)
{
return -2;
}
return 0; // This function returns 0
}
Any help would be very appreciate.

Return SAFEARRAY from c++ to c#

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.

Free IntPtr allocated memory after promoting the pointer

I'm using IntPtr in C#, to get array of object from C++ project.
To do that, i allocated the memory needed with Marshal.AllocHGlobal method, and i'm copying the full array in C++ into the ptr.
The thing is I am promoting the pointer, to move to the next object in the array.
My question is:
Do i have to restore the first value of the IntPtr, before using the Marshal.FreeHGlobal method?
Here is the code
public List<DirEntry> getDirEntries()
{
int dirEntrySize = Marshal.SizeOf(typeof(DirEntry));
int bufferSize = 28 * dirEntrySize;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
CppToCsharpAdapter.getDirEntries(this.myDiskPointer, buffer);
DirEntry dirEntry = new DirEntry();
List<DirEntry> dirEntries = new List<DirEntry>();
for (int i = 0; i < 28; i++)
{
Marshal.PtrToStructure(buffer, dirEntry);
buffer += dirEntrySize;
}
Marshal.FreeHGlobal(buffer);
return dirEntries;
}
You have to create a copy of the original pointer which can be used later use. In this case, it is called current:
public List<DirEntry> getDirEntries()
{
int dirEntrySize = Marshal.SizeOf(typeof(DirEntry));
int bufferSize = 28 * dirEntrySize;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
CppToCsharpAdapter.getDirEntries(this.myDiskPointer, buffer);
IntPtr current = buffer;
DirEntry dirEntry = new DirEntry();
List<DirEntry> dirEntries = new List<DirEntry>();
for (int i = 0; i < 28; i++)
{
Marshal.PtrToStructure(current, dirEntry);
current += dirEntrySize;
}
Marshal.FreeHGlobal(buffer);
return dirEntries;
}
However, there is another possibility without using an explicit copy of your pointer by using IntPtr.Add():
public List<DirEntry> getDirEntries()
{
int dirEntrySize = Marshal.SizeOf(typeof(DirEntry));
int bufferSize = 28 * dirEntrySize;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
CppToCsharpAdapter.getDirEntries(this.myDiskPointer, buffer);
DirEntry dirEntry = new DirEntry();
List<DirEntry> dirEntries = new List<DirEntry>();
for (int i = 0; i < 28; i++)
{
Marshal.PtrToStructure(IntPtr.Add(buffer, i * dirEntrySize), dirEntry);
}
Marshal.FreeHGlobal(buffer);
return dirEntries;
}
Please keep in mind, that your code contains a potential memory leek. You should wrap your code in a try/finally block. In the finally block you free the memory block again if the pointer is not zero:
public List<DirEntry> getDirEntries()
{
int dirEntrySize = Marshal.SizeOf(typeof(DirEntry));
int bufferSize = 28 * dirEntrySize;
IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
try
{
CppToCsharpAdapter.getDirEntries(this.myDiskPointer, buffer);
DirEntry dirEntry = new DirEntry();
List<DirEntry> dirEntries = new List<DirEntry>();
for (int i = 0; i < 28; i++)
{
Marshal.PtrToStructure(IntPtr.Add(buffer, i * dirEntrySize), dirEntry);
}
}
finally
{
if (buffer != IntPtr.Zero)
Marshal.FreeHGlobal(buffer);
}
return dirEntries;
}

How pass function pointer by call C# method via COM interfaces

I have this CLI c++ code for call method WpfApplication1.NetLauncher.Launch(IntPtr cb)
via reflection:
#include "stdafx.h"
using namespace System;
using namespace System::Reflection;
typedef int (__stdcall *PMyBeep)();
int __stdcall MyBeep()
{
return 123;
}
[STAThreadAttribute]
int main(array<System::String ^> ^args)
{
Assembly^ asmbl = Assembly::Load("WpfDemo");
Type^ type = asmbl->GetType("WpfApplication1.NetLauncher");
Object^ obj = asmbl->CreateInstance("WpfApplication1.NetLauncher");
MethodInfo^ method = type->GetMethod("Launch");
IntPtr pp=(IntPtr)MyBeep;
Object^ magicValue = method->Invoke(obj, gcnew array<Object^>(1){pp});
return 0;
}
And c# code:
namespace WpfApplication1
{
public class NetLauncher
{
delegate int Mydelegate();
[System.STAThreadAttribute()]
public int Launch( IntPtr dwData)
//public int Launch(string path)
{
Mydelegate codeToRun = null;
codeToRun = (Mydelegate)Marshal.GetDelegateForFunctionPointer(dwData, typeof(Mydelegate));
int res = codeToRun.Invoke();
// ....
}
}
}
Now I try to call this method from Win32 C++ via COM interfaces:
// ....
CComPtr<IDispatch> disp = launcher.pdispVal;
DISPID dispid;
OLECHAR FAR* methodName = L"Launch";
hr = disp->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
// ????? DWORD nData=(DWORD)MyBeep;
// ????? CComVariant *func = new CComVariant(nData);
CComVariant FAR args[] = {*func};
DISPPARAMS noArgs = {args, NULL, 1, 0};
hr = disp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &noArgs, NULL, NULL, NULL);
I think I have to save function pointer (MyBeep) as CComVariant in DISPPARAMS, but I don't know how???
I'm not in front of my Windows machine... Can you try it for me?
DISPPARAMS dispparams;
memset(&dispparams, 0, sizeof dispparams);
dispparams.cNamedArgs = 0;
dispparams.cArgs = 1;
dispparams.rgvarg = new VARIANTARG[dispparams.cArgs];
dispparams.rgvarg[0].vt = VT_I4;
dispparams.rgvarg[0].iVal = reinterpret_cast<int>(MyBeep);

Categories