Pass array to C# from C++ via mono - c#

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

Related

Error in function input in cv::Mat format in C# and C++ use marshaling

I have a DLL in C++ whose function input is cv::Mat. It gives an error when I try to call this function with the frame input that I receive from opencvsharp as Mat in C#.
How do I fix this problem?
How can I match Mat in C++ and Mat in C# to prevent errors?
Do I need to change the C++ function or do I need to do something else in C# to access the data inside the Mat as input to the C++ function?
C++ function:
extern "C" __declspec(dllexport) vector<std::string> __cdecl ProcessFrame(cv::Mat image);
vector<std::string> ProcessFrame(cv::Mat image)
{
int k = 0;
cv::Mat croppedimage;
cv::Mat finalcropped;
string filename;
Mat result_image;
vector<string> listName;
Module module = torch::jit::load("D:/Project/libfacedetection/example/converted.pt");
int* pResults = NULL;
unsigned char* pBuffer = (unsigned char*)malloc(DETECT_BUFFER_SIZE);
if (!pBuffer)
{
fprintf(stderr, "Can not alloc buffer.\n");
return listName;
}
TickMeter cvtm;
cvtm.start();
pResults = facedetect_cnn(pBuffer, (unsigned char*)(image.ptr(0)), image.cols, image.rows, (int)image.step);
int face_num = (pResults ? *pResults : 0);
if (*pResults != 0)
{
result_image = image.clone();
for (int i = 0; i < face_num; i++)
{
try
{
short* p = ((short*)(pResults + 1)) + 142 * i;
int confidence = p[0];
int x = p[1];
int y = p[2];
int w = p[3];
int h = p[4];
char sScore[256];
if (confidence >= 95)
{
//////////////////////////////////////////////////////////////////////////////
////////////// Rotate and Crop
//////////////////////////////////////////////////////////////////////////////
short angle = Face_rotate(p);
cv::Rect rc = AlignCordinates(x, y, w, h, result_image.cols, result_image.rows);
cv::Rect myroi(x, y, w, h);
cv::Rect newroi((x - rc.x) / 2, (y - rc.y) / 2, w, h);
croppedimage = result_image(rc);
//imshow("1", croppedimage);
croppedimage = croppedimage.clone();
croppedimage = rotate(croppedimage, (angle));
//imshow("Rotate", croppedimage);
croppedimage = croppedimage(newroi).clone();
finalcropped = Mat(112, 112, croppedimage.type());
//imshow("dst", croppedimage);
cv::resize(croppedimage, finalcropped, finalcropped.size());
//imshow("resize", finalcropped);
Mat flipimage;
flip(finalcropped, flipimage, 1);
torch::Tensor img_tensor = torch::from_blob(finalcropped.data, { finalcropped.rows,finalcropped.cols ,3 }, torch::kByte);
torch::Tensor img_tensor_flip = torch::from_blob(flipimage.data, { flipimage.rows, flipimage.cols, 3 }, torch::kByte);
//torch::Tensor img_tensor_final = img_tensor + img_tensor_flip;
img_tensor = img_tensor.to(at::kFloat).div(255).unsqueeze(0);
img_tensor = img_tensor.sub_(0.5);
img_tensor = img_tensor.permute({ 0,3,1,2 });
img_tensor_flip = img_tensor_flip.to(at::kFloat).div(255).unsqueeze(0);
img_tensor_flip = img_tensor_flip.sub_(0.5);
img_tensor_flip = img_tensor_flip.permute({ 0,3,1,2 });
at::Tensor output_org = module.forward({ img_tensor }).toTensor();
at::Tensor output_flip = module.forward({ img_tensor_flip }).toTensor();
std::vector<double> out;
for (int i = 0; i < 512; i++)
{
out.push_back(output_org[0][i].item().to<double>() + output_flip[0][i].item().to<double>());
}
out = l2_norm(out);
std::ifstream file("D:/Project/libfacedetection/example/facebank.json");
json object = json::parse(file);
double min_dis = 1000;
std::string min_name;
for (auto& x : object.items()) {
auto dataSize = std::size(x.value());
std::vector<double> vec1 = x.value();
double res = cosine_similarity_vectors(vec1, out);
res = (res * -1) + 1;
//double res = distance(vec1, out);
if (res <= min_dis) {
min_dis = res;
min_name = x.key();
}
}
std::cout << "One Frame " << min_name << " " << min_dis << std::endl;
if (min_dis < 0.8) {
listName.push_back(min_name);
}
else
{
listName.push_back("Unknown");
}
}
else
{
listName.push_back("conf_low");
}
}
catch (const std::exception& ex)
{
cout << "NASHOD" << endl;
//std::cout << ex.what();
}
}
}
else
{
listName.push_back("No_Body");
}
cvtm.stop();
//printf("time = %gms\n", cvtm.getTimeMilli());
//printf("%d faces detected.\n", (pResults ? *pResults : 0));
free(pBuffer);
return listName;
}
C#:
[DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern List<string> ProcessFrame(Mat image);
private void button1_Click(object sender, EventArgs e)
{
Mat image = Cv2.ImRead("D:/c++/ImportCallFunction/ImportCallFunction/123.jpg");
List<string> facelist = ProcessFrame(image);
foreach (var item in facelist)
{
listBox1.Items.Add(item);
}
Error:
System.Runtime.InteropServices.MarshalDirectiveException: 'Cannot marshal 'return value': Generic types cannot be marshaled.'
The error that you've encountered is not related to the parameter type cv::Mat but the return type of the function which is declared as vector<std::string>.
First, a note about the parameter type: you might want to make it const cv::Mat& to avoid copying the whole matrix into the function on every frame. So it will be like:
std::vector<std::string> ProcessFrame(const cv::Mat& image)
You will also need a wrapper function that is written in C++/CLI and serves as the interface between the C# code and the C++ code. It performs the custom marshalling required by the function both for the input argument and for the return value. Note that you should place the wrapper function in a compile unit that is compiled with /clr (to enable C++/CLI). Your original (native) function doesn't need to be compiled with the /clr option. The wrapper function declaration might look like this:
System::Collections::Generic<System::String>^ ProcessFrameWrapper(
OpenCvSharp::Mat^ mat);
In the C# code, you will call the wrapper function now:
[DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern List<string> ProcessFrameWrapper(Mat image);
// ...
List<string> facelist = ProcessFrameWrapper(image);
To summarize, you will need these files:
DetectCamera.h:
// ...
std::vector<std::string> ProcessFrame(const cv::Mat& image);
// ... other native declarations
DetectCamera.cpp:
// ...
std::vector<std::string> ProcessFrame(const cv::Mat& image)
{
// actual function implementation
}
// ... other function implementations
DetectCameraWrapper.h:
// ...
System::String^ ProcessFrameWrapper(OpenCvSharp::Mat^ mat);
// ... other wrapper functions ...
DetectCameraWrapper.cpp:
// ...
System::Collections::Generic<System::String>^ ProcessFrameWrapper(
OpenCvSharp::Mat^ mat)
{
var names = gcnew System::Collections::Generic<System::String>();
auto matNativePtr =
reinterpret_cast<cv::Mat*>(marshal_as<void*>(mat->CvPtr));
auto namesNative = ProcessFrame(*matNativePtr);
for (const auto& nameNative : namesNative)
{
names->Add(marshal_as<System::String^>(nameNative));
}
return names;
}
// ... other wrapper function implementations
DetectCamera.cs:
// ...
[DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern List<string> ProcessFrameWrapper(Mat image);
private void button1_Click(object sender, EventArgs e)
{
Mat image = Cv2.ImRead("D:/c++/ImportCallFunction/ImportCallFunction/123.jpg");
List<string> facelist = ProcessFrameWrapper(image);
foreach (var item in facelist)
{
listBox1.Items.Add(item);
}
// ...
These files can be organized in two or three separate projects:
DetectCamera.cs is placed in a C# project - call it ProjCSharp.
DetectCameraWrapper.cpp is placed in a C++/CLI DLL project (with /clr) - call it ProjWrapper.
DetectCamera.cpp could be placed either within the same project ProjWrapper, or in a separate native library project (.lib) - call it ProjNative. I recommend the latter. If it is placed in a separate library (ProjNative), the DLL project ProjWrapper must be linked to the library ProjNative.
The reason I recommend placing the native code inside a separate library is modularity and code reusability.
I do not quite remember the Interop and was never an expert although I used to do pretty advanced stuff.
The advice from #misoboute is to use CLI which I have never used so I wouldn't know. It is surely possible to do that that and as he has explained, such C++ will understand the generic C# list and be able to marshal successfully.
But it appears to me, this is NOT your problem. You simply want to return a bunch of strings. It really does not have to be defined as List<string>. It can be defined as string[], an array of string.
So I would try
[DllImport("detect-camera.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern string[] ProcessFrame(Mat image);
And then, in my C++ code, return an array of string by representing the vector of string as an array:
How to convert vector to array
Sorry but that is all I have for you. It is possible that after this, you will hit other problems.
You have two "standard" ways to get from C++ to C#.
The first is C++/CLI. In this case you will build a C++/CLI library that takes the std::vectorstd::string and converting that into a System::vectorSystem::string. Then you can use it freely as a System.String[] in C#.
The other is COM. There you create a COM interface that returns a SAFEARRAY containing BSTR string. This COM interface is then instantiated though the System.Runtime.InteropServices in C#. The SAFEARRAY is then a Object[] which can be cased to single string objects.
The facility to load C interfaces into C# is basically restricted to C. Any C++ will fail and Pete provides that "non standard" approach. (It works very well, just not what MS wants you to do.)
c++ :
extern "C" __declspec(dllexport) LPSAFEARRAY ListDevices();
LPSAFEARRAY ProcessFrame(int width, int height, unsigned char* data)
{
CComSafeArray<BSTR> a(listName.size()); // cool ATL helper that requires atlsafe.h
std::vector<std::string>::const_iterator it;
int i = 0;
for (it = listName.begin(); it != listName.end(); ++it, ++i)
{
// note: you could also use std::wstring instead and avoid A2W conversion
a.SetAt(i, A2BSTR_EX((*it).c_str()), FALSE);
}
return a.Detach();
}
c#:
[DllImport("detectcameratest.dll")]
[return: MarshalAs(UnmanagedType.SafeArray)]
private extern static string[] ProcessFrame2(int width, int height, IntPtr data);
private void button2_Click(object sender, EventArgs e)
{
Mat img = Cv2.ImRead(#"C:\Users\Subtek\source\repos\WindowsFormsApp17\WindowsFormsApp17\bin\x64\Debug\1.jpg");
var facelist = ProcessFrame2(img.Width, img.Height, img.Data);
foreach (var s in facelist)
{
listBox1.Items.Add(s);
}

Race condition concurrent queue - Reference to a pointer

I am analyzing an implementation of a queue (implemented using a circular buffer) which is supposed to be used by 1 consumer and n producers. This code is a porting of a C# implementation you can find here.
The code below shows the implementation of such a queue, and a main. On my system, this program crashes. I narrowed down the problem to the fact that the pop() function is returning a reference. Such reference is an alias for the array cell and not for the pointer the cell of the array contains. This reference is then used to execute the task, but crucially, some producers might write in the same array location and the whole program goes UB. Am I completely going in the wrong direction here?
Of course, if I remove the & form the signature the program works fine with no crashes.
I have also used helgrind to check for race conditions and indeed when I run the reference version it shows a warning.
#include <cmath>
#include <functional>
#include <iostream>
#include <mutex>
#include <stdexcept>
#include <thread>
template<typename T, uint64_t SIZE = 4096, uint64_t MAX_SPIN_ON_BUSY = 40000000>
class ConcurrentQueue {
private:
static constexpr unsigned Log2(unsigned n, unsigned p = 0) {
return (n <= 1) ? p : Log2(n / 2, p + 1);
}
static constexpr uint64_t closestExponentOf2(uint64_t x) {
return (1UL << ((uint64_t) (Log2(SIZE - 1)) + 1));
}
static constexpr uint64_t mRingModMask = closestExponentOf2(SIZE) - 1;
static constexpr uint64_t mSize = closestExponentOf2(SIZE);
static const T mEmpty;
T mMem[mSize];
std::mutex mLock;
uint64_t mReadPtr = 0;
uint64_t mWritePtr = 0;
public:
const T& pop() { //piece of code I believe is dangerous
if (!peek()) {
return mEmpty;
}
std::lock_guard<std::mutex> lock(mLock);
if (!peek()) {
return mEmpty;
}
T& ret = mMem[mReadPtr & mRingModMask];
mReadPtr++;
return ret;
}
bool peek() const {
return (mWritePtr != mReadPtr);
}
uint64_t getCount() const {
return mWritePtr > mReadPtr ? mWritePtr - mReadPtr : mReadPtr - mWritePtr;
}
bool busyWaitForPush() {
uint64_t start = 0;
while (getCount() == mSize) {
if (start++ > MAX_SPIN_ON_BUSY) {
return false;
}
}
return true;
}
void push(const T& pItem) {
if (!busyWaitForPush()) {
throw std::runtime_error("Concurrent queue full cannot write to it!");
}
std::lock_guard<std::mutex> lock(mLock);
mMem[mWritePtr & mRingModMask] = pItem;
mWritePtr++;
}
void push(T&& pItem) {
if (!busyWaitForPush()) {
throw std::runtime_error("Concurrent queue full cannot write to it!");
}
std::lock_guard<std::mutex> lock(mLock);
mMem[mWritePtr & mRingModMask] = std::move(pItem);
mWritePtr++;
}
};
template<typename T, uint64_t SIZE, uint64_t MAX_SPIN_ON_BUSY>
const T ConcurrentQueue<T, SIZE, MAX_SPIN_ON_BUSY>::mEmpty = T{ };
int main(int, char**) {
using Functor = std::function<void()>;
ConcurrentQueue<Functor*> queue;
std::thread consumer([ & ] {
while (true) {
if (queue.peek()) {
auto task = queue.pop();
(*task)();
delete task;
}
}
});
std::thread producer([ & ] {
uint64_t counter = 0;
while (true) {
auto taskId = counter++;
auto newTask = new Functor([ = ] {
std::cout << "Running task " << taskId << std::endl << std::flush;
});
queue.push(newTask);
}
});
consumer.join();
producer.join();
return 0;
}

AMD Gpu Memory dll

I'm writing an C++ dll for using in a C# application.
The dll will check the total GPU memory and the usage of the GPU memory.
Now I have created three methods. The first one does initialize GLew and other OpeGl stuff. The second will read the total memory of the GPU. And the last one will read the GPU usage.
The inialize and the total memory methods does work but with the last one I get some problems. When I call the methode it stops and when I debug it I can set a breakpoint on the delete[] ids; line without any problem. But it does not return anythin on the return available line (it does not get there). When I remove the delte[] ids line I get an error:
'Run-Time Check Failure #2 - Stack around the variable 'nCurAvailMemoryInKB' was corrupted.'. Do I something wrong to read the usage of the GPU memory?
__declspec(dllexport) float getAvailableMemory()
{
int available = -1;
if (wglGetGPUIDsAMD && wglGetGPUInfoAMD)
{
UINT n = wglGetGPUIDsAMD(0, 0);
UINT * ids = new UINT[n];
wglGetGPUIDsAMD(n, ids);
GLint nCurAvailMemoryInKB = 0;
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI,
&nCurAvailMemoryInKB);
available = nCurAvailMemoryInKB;
delete[] ids;
}
return available;
}
I created a test caller for the Dll in C#:
class Program {
[DllImport("AmdLib.dll")]
public static extern bool init();
[DllImport("AmdLib.dll")]
public static extern int getTotalMemory();
[DllImport("AmdLib.dll")]
public static extern float getAvailableMemory();
static void Main(string[] args) {
init();
Console.WriteLine("Total");
Console.WriteLine(getTotalMemory());
Console.WriteLine("Available");
Console.WriteLine(getAvailableMemory());
}
}
And the full C++ DLL source does looks like:
#include <stdio.h>
#include <windows.h>
#include <GL/glew.h>
#include <GL/wglew.h>
#include <assert.h>
#include <vector>
#include <string>
using namespace std;
extern "C"
{
static HGLRC ctx = NULL;
__declspec(dllexport) bool init()
{
HWND hwnd = NULL;
HINSTANCE hinstance = (HINSTANCE)GetModuleHandle(NULL);
WNDCLASSA window_class;
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_GLOBALCLASS;
window_class.lpfnWndProc = DefWindowProc;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = hinstance;
window_class.hIcon = NULL;
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
window_class.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
window_class.lpszMenuName = NULL;
window_class.lpszClassName = "test_class";
ATOM atom = RegisterClassA(&window_class);
hwnd = CreateWindowA("test_class", "htest", WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 1, 1, 1, 1, NULL, NULL, hinstance, NULL);
if (hwnd == NULL) {
DWORD err = GetLastError();
return false;
}
HDC hDC = GetDC(hwnd);
if (hDC == NULL) {
return false;
}
PIXELFORMATDESCRIPTOR const pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_TYPE_RGBA,
0,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
0,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
int pixel_format = ChoosePixelFormat(hDC, &pfd);
SetPixelFormat(hDC, pixel_format, &pfd);
ctx = wglCreateContext(hDC);
if (ctx) {
if (!wglMakeCurrent(hDC, ctx)) {
return false;
}
}
ReleaseDC(hwnd, hDC);
GLenum glew = glewInit();
return true;
}
static void check_gl_error()
{
GLenum error = glGetError();
assert(error == GL_NO_ERROR);
}
__declspec(dllexport) int getTotalMemory()
{
if (wglGetGPUIDsAMD && wglGetGPUInfoAMD)
{
UINT n = wglGetGPUIDsAMD(0, 0);
UINT * ids = new UINT[n];
UINT total_mem_mb = 0;
wglGetGPUIDsAMD(n, ids);
wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(UINT), &total_mem_mb);
delete[] ids;
return total_mem_mb;
}
return -1;
}
__declspec(dllexport) float getAvailableMemory()
{
int available = -1;
if (wglGetGPUIDsAMD && wglGetGPUInfoAMD)
{
UINT n = wglGetGPUIDsAMD(0, 0);
UINT * ids = new UINT[n];
wglGetGPUIDsAMD(n, ids);
GLint nCurAvailMemoryInKB = 0;
glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI,
&nCurAvailMemoryInKB);
available = nCurAvailMemoryInKB;
//delete[] ids;
}
return available;
}
}
Since I don't have an ATI card to test with, off the top of my head I'd guess the first wglGetGPUIDsAMD call returns 0, you allocate a 0-length array (which works) and at the end you try to delete it (which throws). Somewhere in-between you overwrite the memory around that pointer with data (thus corrupting the guards and making VS throw).
Now looking at what you're actually doing with that array, or the knowledge of how many GPUs you have, you never actually use either of them. You can literally delete both calls to wglGetGPUIDsAMD and the array allocation/deallocation, and just call glGetIntegerv.

array data not marshaled correctly for structures from c# to c within Linux embedded mono program

I've built on the Mono Embed sample to try and invoke a method in a C# assembly that updates a structure. The structure has 1 int array. This is on a Linux system.
Accessing the int array field in c# results in a segmentation fault. Just checking if the field is null is enough to cause the fault.
When I do internal marshaling simulation within C#, converting the struct to bytes and then back to a struct this works fine.
Mono Version: 3.2.3
I have included the c# and c code below and can furnish more information upon request if need be.
Here's the c code...
#include <mono/jit/jit.h>
#include <mono/metadata/object.h>
#include <mono/metadata/environment.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <string.h>
#include <stdlib.h>
#ifndef FALSE
#define FALSE 0
#endif
struct STRUCT_Test
{
int IntValue1[2];
};
int
main (int argc, char* argv[]) {
MonoDomain *domain;
MonoAssembly *assembly;
MonoClass *klass;
MonoObject *obj;
MonoImage *image;
const char *file;
int retval;
if (argc < 2){
fprintf (stderr, "Please provide an assembly to load\n");
return 1;
}
file = argv [1];
domain = mono_jit_init (file);
assembly = mono_domain_assembly_open(domain, file);
if (!assembly)
exit(2);
image = mono_assembly_get_image(assembly);
klass = mono_class_from_name(image, "StructTestLib", "StructReader");
if (!klass) {
fprintf(stderr, "Can't find StructTestLib in assembly %s\n", mono_image_get_filename(image));
exit(1);
}
obj = mono_object_new(domain, klass);
mono_runtime_object_init(obj);
{
struct STRUCT_Test structRecord; memset(&structRecord, 0, sizeof(struct STRUCT_Test));
void* args[2];
int val = 277001;
MonoMethodDesc* mdesc = mono_method_desc_new(":ReadData", FALSE);
MonoMethod *method = mono_method_desc_search_in_class(mdesc, klass);
args[0] = &val;
args[1] = &structRecord;
structRecord.IntValue1[0] = 1111;
structRecord.IntValue1[1] = 2222;
mono_runtime_invoke(method, obj, args, NULL);
printf("IntValue1: %d, %d\r\n", structRecord.IntValue1[0], structRecord.IntValue1[1]);
}
retval = mono_environment_exitcode_get ();
mono_jit_cleanup (domain);
return retval;
}
Here's the c# code...
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StructTestLib
{
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct STRUCT_Test
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public Int32[] IntValue1;
}
public class StructReader
{
public void ReadData(int uniqueId, ref STRUCT_Test tripRecord)
{
if (tripRecord.IntValue1 != null)
Console.WriteLine("IntValue1: " + tripRecord.IntValue1[0] + ", " + tripRecord.IntValue1[1]);
else
Console.WriteLine("IntValue1 is NULL");
tripRecord.IntValue1[0] = 3333;
tripRecord.IntValue1[1] = 4444;
}
}
}
Oops! My Ignorance!
Seems that my understanding of the marshaling was incorrect. Raw array based data types (string, long[]) cannot be marshaled directly. The c structure has to have the Monoxxx* type as the member for the runtime to marshal correctly.
Using MonoString* StringValue1 instead of char StringValue1[31] and MonoArray* IntArray instead of int IntArray[2] allows the marshaling to work correctly.
Here's what I ultimately ended up with
I really needed to pass in the raw structure from c without all the "mono" baggage within the structure, I'm trying to use existing c structures without changing them. I was able to do this by using "unsafe" c# code and allowing the address of the structure itself to be passed into the c# method. This allows the raw memory to be manipulated within c# and gives full freedom for the c# marshaler to convert bytes to struct and vice versa.
c# code
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using EmpireCLS.Comm;
namespace StructTestLib
{
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct STRUCT_Test
{
public Int32 IntValue1;
public Int32 IntValue2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string StringValue1;
}
public class StructReader
{
unsafe public void ReadDataRaw(int uniqueId, void* tripRecordPtr)
{
STRUCT_Test tripRecord = (STRUCT_Test)Marshal.PtrToStructure((IntPtr)tripRecordPtr, typeof(STRUCT_Test));
tripRecord.IntValue1 = 3333;
tripRecord.IntValue2 = 4444;
Console.WriteLine("c# StringValue1: " + tripRecord.StringValue1);
tripRecord.StringValue1 = "fghij";
GCHandle pinnedPacket = new GCHandle();
try
{
int structSizeInBytes = Marshal.SizeOf(typeof(STRUCT_Test));
byte[] bytes = new byte[structSizeInBytes];
pinnedPacket = GCHandle.Alloc(bytes, GCHandleType.Pinned);
Marshal.StructureToPtr(tripRecord, pinnedPacket.AddrOfPinnedObject(), true);
Marshal.Copy(bytes, 0, (IntPtr)tripRecordPtr, bytes.Length);
}
finally
{
if (pinnedPacket.IsAllocated)
pinnedPacket.Free();
}
}
}
}
c code
#include <mono/jit/jit.h>
#include <mono/metadata/object.h>
#include <mono/metadata/environment.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <string.h>
#include <stdlib.h>
#ifndef FALSE
#define FALSE 0
#endif
struct STRUCT_Test
{
int IntValue1;
int IntValue2;
char StringValue1[20];
};
int
main (int argc, char* argv[]) {
MonoDomain *domain;
MonoAssembly *assembly;
MonoClass *klass;
MonoObject *obj;
MonoImage *image;
const char *file;
int retval;
if (argc < 2){
fprintf (stderr, "Please provide an assembly to load\n");
return 1;
}
file = argv [1];
domain = mono_jit_init (file);
assembly = mono_domain_assembly_open(domain, file);
if (!assembly)
exit(2);
image = mono_assembly_get_image(assembly);
klass = mono_class_from_name(image, "StructTestLib", "StructReader");
if (!klass) {
fprintf(stderr, "Can't find StructTestLib in assembly %s\n", mono_image_get_filename(image));
exit(1);
}
obj = mono_object_new(domain, klass);
mono_runtime_object_init(obj);
{
struct STRUCT_Test structRecord; memset(&structRecord, 0, sizeof(struct STRUCT_Test));
void* args[2];
int val = 277001;
char* p = NULL;
MonoMethodDesc* mdesc = mono_method_desc_new(":ReadDataRaw", FALSE);
MonoMethod *method = mono_method_desc_search_in_class(mdesc, klass);
args[0] = &val;
args[1] = &structRecord;
structRecord.IntValue1 = 1111;
structRecord.IntValue2 = 2222;
strcpy(structRecord.StringValue1, "abcde");
mono_runtime_invoke(method, obj, args, NULL);
printf("C IntValue1: %d, %d\r\n", structRecord.IntValue1, structRecord.IntValue2);
printf("C StringValue: %s\r\n", structRecord.StringValue1);
}
retval = mono_environment_exitcode_get ();
mono_jit_cleanup (domain);
return retval;
}
Try passing your StringValue1 as an array of characters, since that's what you've actually defined it to be in the C program.
mono_runtime_invoke() doesn't do any type marshalling (same if you go the other way around and use internal calls).
Only P/Invoke methods perform data marshalling.

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