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

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);

Related

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 a struct from a c++ function to c# then an array UPDATE

I have read through maybe 100+ posts on this over 2 weeks and have got myself totally confused. This is for a wrapper i'm writing over the BlueSoleil stack.
In c++
typedef char BTINT8;
typedef unsigned char BTUINT8;
typedef unsigned char BTUCHAR; /* extended ASII character, 0 - 255 */
typedef unsigned char BTBOOL;
typedef short BTINT16;
typedef unsigned short BTUINT16;
typedef long BTINT32;
typedef unsigned long BTUINT32;
typedef void * BTLPVOID;
typedef BTUINT32 BTDEVHDL;
typedef BTUINT32 BTSVCHDL;
typedef BTUINT32 BTCONNHDL;
typedef BTUINT32 BTSHCHDL;
typedef BTUINT32 BTSDKHANDLE;
typedef struct _BlueToothDevice
{
BTDEVHDL rmt_device_handle;
BTINT32 rmt_device_num;
BTUINT32 rmt_device_class;
BTUINT8 rmt_device_name[64];
} BLUETOOTHDEVICE;
typedef struct _BlueToothDevices
{
BTUINT32 num_rmt_devices;
BLUETOOTHDEVICE rmt_btd[100];
} BLUETOOTHDEVICES;
public ref class RemoteDeviceDiscovery
{
public:
int GetBTD(BLUETOOTHDEVICE btd);
};
In C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct BluetoothDevice
{
[MarshalAs(UnmanagedType.SysUInt)]
public UInt32 rmt_device_handle;
[MarshalAs(UnmanagedType.SysInt)]
public Int32 rmt_device_num;
[MarshalAs(UnmanagedType.SysUInt)]
public UInt32 rmt_device_class;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 248)]
public IntPtr rmt_device_name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct BluetoothDevices
{
[MarshalAs(UnmanagedType.SysUInt)]
public UInt32 num_rmt_devices;
[MarshalAs(UnmanagedType.ByValArray)]
public List<BluetoothDevice> BluetoothDeviceInfo;
}
static void Main()
{
var rd = new RemoteDeviceDiscovery();
BluetoothDevice bluetoothDevice = new BluetoothDevice();
var i = rd.GetBTD(ref bluetoothDevice);
}
Error 3 Argument 1: cannot convert from 'ConsoleWrapperTest.Program.BluetoothDevice' to 'BlueSoleilWrapper._BlueToothDevice'
So can someone point out where I have gone wrong please.
Jim
UPDATE
Have the c++/cli wrapper compiling with the following header code
public ref class RemoteDeviceDiscovery
{
/* Remote Device Discovery */
public:
int^ GetBTD(_BlueToothDevices % btds);
};
and cpp code
array<Byte>^ MakeManagedArray(unsigned char* input, int len)
{
array<Byte>^ result = gcnew array<Byte>(len);
for (int i = 0; i < len; i++)
{
result[i] = input[i];
}
return result;
}
int^ RemoteDeviceDiscovery::GetBTD(_BlueToothDevices % btds)
{
unsigned char name[] = "Test";
_BlueToothDevice entry;
btds.num_rmt_devices = 1;
entry.rmt_device_class = 1;
entry.rmt_device_handle = 2;
entry.rmt_device_num = 3;
entry.rmt_device_name = MakeManagedArray(name, sizeof(name));
btds.rmt_btd[0] = gcnew _BlueToothDevice();
btds.rmt_btd[0] = %entry;
return 99;
}
Problem is now in the C# code
var rd = new RemoteDeviceDiscovery();
_BlueToothDevices bluetoothDevices = new _BlueToothDevices();
var i = rd.GetBTD(bluetoothDevices);
var rmt_device_handle = bluetoothDevices.rmt_btd[0].rmt_device_handle;
When compiling I get 'GetBTD' is not supported by the language and everything I have seen so far as solutions hasn't worked. Appreciate any further assistance.
Jim
Seems like you're using C++\CLI, not plain C++. If you're using RemoteDeviceDiscovery directly then you have to instantiate BLUETOOTHDEVICE, not trying to create your own structure.
If you can't use BLUETOOTHDEVICE because it's not publicly visible, you can write some sort of adapter for RemoteDeviceDiscovery class that will be taking your structure and mapping it on BLUETOOTHDEVICE.
Ok, got a working answer ! Many thanks to rpeshkov you pointed me in the right direction and i'm glad I learnt something from it all.
In the header
public ref class RemoteDeviceDiscovery
{
/* Remote Device Discovery */
public:
int GetBTD(_BlueToothDevices ^% btds);
};
In the cpp code
int RemoteDeviceDiscovery::GetBTD(_BlueToothDevices ^% btds)
{
unsigned char name0[] = "Test0";
unsigned char name1[] = "Test1";
_BlueToothDevice^ entry = gcnew _BlueToothDevice();
entry->rmt_device_class = 1;
entry->rmt_device_handle = 2;
entry->rmt_device_num = 3;
entry->rmt_device_name = MakeManagedArray(name0, sizeof(name0));
btds->num_rmt_devices = 2;
btds->rmt_btd[0] = gcnew _BlueToothDevice();
btds->rmt_btd[0] = entry;
entry = gcnew _BlueToothDevice();
entry->rmt_device_class = 4;
entry->rmt_device_handle = 5;
entry->rmt_device_num = 6;
entry->rmt_device_name = MakeManagedArray(name1, sizeof(name1));
btds->rmt_btd[1] = gcnew _BlueToothDevice();
btds->rmt_btd[1] = entry;
return 99;
}
and finally in the C# code (this is of course just a bare bones test to development a framework)
var rd = new RemoteDeviceDiscovery();
_BlueToothDevices bluetoothDevices = new _BlueToothDevices();
var i = rd.GetBTD(ref bluetoothDevices);
var rmt_device_handle = bluetoothDevices.rmt_btd[0].rmt_device_handle;
I suggest looking in the assembly that defines the class RemoteDeviceDiscovery (F12 in Microsoft Visual Studio), and see if you can find that assembly's definition of BlueSoleilWrapper._BlueToothDevice. Then use that definition directly. ConsoleWrapperTest.Program.BluetoothDevice will never map to BlueSoleilWrapper._BlueToothDevice, regardless of how close your definition matches the one in the assembly.
Replacement .cpp code
int RemoteDeviceDiscovery::GetBTD(_BlueToothDevices ^% btds)
{
unsigned char name0[] = "Test0";
unsigned char name1[] = "Test1";
_BlueToothDevice^ entry = gcnew _BlueToothDevice();
entry->rmt_device_class = 1;
entry->rmt_device_handle = 2;
entry->rmt_device_num = 3;
entry->rmt_device_name = MakeManagedArray(name0, sizeof(name0));
btds->num_rmt_devices = 2;
btds->rmt_btd[0] = gcnew _BlueToothDevice();
btds->rmt_btd[0] = entry;
entry = gcnew _BlueToothDevice();
entry->rmt_device_class = 4;
entry->rmt_device_handle = 5;
entry->rmt_device_num = 6;
entry->rmt_device_name = MakeManagedArray(name1, sizeof(name1));
btds->rmt_btd[1] = gcnew _BlueToothDevice();
btds->rmt_btd[1] = entry;
return 99;
}

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.

Pass array to C# from C++ via mono

I need to pass an array from C++ to C# using mono. But I can't get mono_array_set() to compile. So how can I pass an array from C++ to C#?
I've tried mono_runtime_invoke() which compiles but gives a runtime error.
mcs /nologo /warn:4 /debug:pdbonly /o /nowarn:3003 /platform:x64 /out:array.dll /target:library array.cs
g++ array.cpp -g3 `pkg-config --cflags --libs mono-2` -o array
<snip>
array.cpp:32:5: note: in expansion of macro ‘mono_array_set’
// array.cpp
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
int main(int argc, char* argv[]) {
MonoDomain* domain = mono_jit_init("./array.dll");;
MonoAssembly* assembly = mono_domain_assembly_open(domain, "./array.dll");
MonoImage* image = mono_assembly_get_image(assembly);
MonoClass* containsClass = mono_class_from_name(image, "IntArray", "ContainsAnInt");
MonoMethodDesc* containsDesc = mono_method_desc_new("IntArray.ContainsAnInt:.ctor(int)", false);
MonoMethod* containsCtor = mono_method_desc_search_in_class(containsDesc, containsClass);
MonoObject* containsObject = mono_object_new(domain, containsClass);
void* args[1];
int value = 7;
args[0] = &value;
MonoObject* exception = NULL;
mono_runtime_invoke(containsCtor, containsObject, args, &exception);
MonoClass* unpackageClass = mono_class_from_name(image, "IntArray", "Unpackage");
args[0] = containsObject;
MonoMethodDesc* returnIntDesc = mono_method_desc_new("IntArray.Unpackage:ReturnInt(IntArray.ContainsAnInt)", true);
MonoMethod* returnIntMethod = mono_method_desc_search_in_class(returnIntDesc, unpackageClass);
mono_runtime_invoke(returnIntMethod, NULL, args, &exception); // <--- as expected, outputs "In ReturnInt: 7"
MonoArray* theArray = mono_array_new(domain, containsClass, 1);
//// Following will not compile
mono_array_set(theArray, MonoClass*, 0, containsObject);
////
MonoMethodDesc* returnElementDesc = mono_method_desc_new("IntArray.Unpackage:ReturnElement(IntArray.ContainsAnInt[])", true);
MonoMethod* returnElementMethod = mono_method_desc_search_in_class(returnElementDesc, unpackageClass);
mono_runtime_invoke_array(returnElementMethod, NULL, theArray, &exception); // <--- should output "In ReturnElement: 7"
mono_jit_cleanup(domain);
}
// array.cs
using System;
using System.IO;
namespace IntArray {
public class ContainsAnInt {
public ContainsAnInt(int i) { IntValue = i; }
public int IntValue { get; set; }
}
public class Unpackage {
public static int ReturnInt(ContainsAnInt n) {
Console.WriteLine("In ReturnInt: " + n.IntValue);
return n.IntValue;
}
public static int ReturnElement(ContainsAnInt[] n) {
Console.WriteLine("In ReturnElement: " + n[0].IntValue);
return n[0].IntValue;
}
}
}
This works:
MonoArray* theArray = mono_array_new(domain, containsClass, 1);
mono_array_set(theArray, MonoObject*, 0, containsObject);
args[0] = theArray;
mono_runtime_invoke(returnElementMethod, NULL, args, &exception); // <--- as expected, outputs "In ReturnElement: 7"
This also works because I was able to define a C struct with the exact same layout as System.Drawing.PointF . Still unanswered is what if I needed to populate an array of C# classes from C++?
MonoArray* arrayPtF = mono_array_new(domain, ptFClass, 2);
struct cpoint {
float x, y;
} mycpoint[] = { { 42, 1701 }, { 1701, 42 } };
mono_array_set(arrayPtF, cpoint, 0, mycpoint[0]);
mono_array_set(arrayPtF, cpoint, 1, mycpoint[1]);

Passing string arrays from COM to 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
...

Categories