Integration between C++ DLL and C# Unity - c#

I'm having issues with C++ Dll Integration with Unity. I found some code in the web (link at the end of article) but it doesn't work.
This is my C++ DLL code (I want to send unity a structure with some points):
struct Pontos {
int i;
float f;
};
Pontos **pontos;
DllExport bool SetPoints(Pontos *** a, int *i)
{
pontos = new Pontos*[4];
for (int j = 0; j < 4; j++) {
pontos[j] = new Pontos; // Actually create each object.
pontos[j]->i = j;
pontos[j]->f = (float)j;
}
*a = pontos;
*i = 4;
return true;
}
From the unity code, I get the following error:
No MonoBehaviour scripts in the file, or their names do not match the file name
I don't know what this means.
Here I try to get those points and save them in c#:
[DllImport("dll")]
private static extern bool SetPoints(out IntPtr ptrResultVerts, out int resultVertLength);
public struct Pontos
{
int i;
float f;
};
void Start()
{
IntPtr ptrNativeData = IntPtr.Zero;
int itemsLength = 0;
bool success = SetPoints(out ptrNativeData, out itemsLength);
if (!success)
{
return;
}
Pontos[] SArray = new Pontos[itemsLength]; // Where the final data will be stored.
IntPtr[] SPointers = new IntPtr[itemsLength];
Debug.Log("Length: " + itemsLength); // Works!
Marshal.Copy(ptrNativeData, SPointers, 0, itemsLength); // Seems not to work.
for (int i = 0; i < itemsLength; i++)
{
Debug.Log("Pointer: " + SPointers[i]);
SArray[i] = (Pontos)Marshal.PtrToStructure(SPointers[i], typeof(Pontos));
}
I got this code from Can't marshal array of stucts from C++ to C# in Unity.

You must import the dll specifying the calling convention
[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
I don't think the error
No MonoBehaviour scripts in the file, or their names do not match the
file name
has anything to do with your code. Check your class name and your script name. Do they correspond?
Finally, I'm not sure about the out modifier in the method signature as I never have to deal with triple pointers on the C++ side. If your goal is to fill an array of Pontos structures than you shouldn't need it.
See also Pass structure (or class) from C++ dll to C# (Unity 3D)

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

my c++ and c# interop crashes in 64 bit, why? pointer size?

i got a native 32 bit dll (no source) which runs as a plugin in an application i use. I've done another native dll myself which will communicate with that plugin in order to create and update the plugin's controls.
From that dll i've exported the functions I need in order to control the plugin from my c# application (with p/invoke).
here's the code:
h file:
#pragma once
#include "include\SpoutControls.h"
extern "C" { __declspec(dllexport) void InitializeControls(char *sendername, int *numControls, char** names, int *types, float* floats, float* toggles, float* press, char** text); }
extern "C" { __declspec(dllexport) bool UpdateControls(const char** text, float *floats, float *toggles, float *press, int *numControls); }
extern "C" { __declspec(dllexport) void CloseControls(); }
//
extern "C" __declspec(dllexport) int ReleaseMemory(float *pArray)
{
delete[] pArray;
//delete[] Usize;
return 0;
};
the cpp:
#include "SpoutControls4vvvv.h"
//SpoutControls and the functions
//CreateControl, OpenControls, CheckControls, CloseControls
//are declared in SpoutControls.h, which comes with the 32 bit plugin dll
SpoutControls spoutcontrols;
void InitializeControls(char *sendername, int *numControls, char** names, int *types, float* floats, float* toggles, float* press, char** text) {
int Vcontrols = numControls[0];
int Tcontrols = numControls[1];
int Pcontrols = numControls[2];
int Scontrols = numControls[3];
int all = Vcontrols + Tcontrols + Pcontrols + Scontrols;
int v=0, t=0, p=0, s = 0;
for (int controlID = 0; controlID < all; controlID++) {
if (types[controlID] == 0) {
spoutcontrols.CreateControl(names[controlID], "float",0.0,1.0, floats[v]);
v++;
}
if (types[controlID] == 1) {
spoutcontrols.CreateControl(names[controlID], "bool", toggles[t]);
t++;
}
if (types[controlID] == 2) {
spoutcontrols.CreateControl(names[controlID], "event", press[p]);
p++;
}
if (types[controlID] == 3) {
spoutcontrols.CreateControl(names[controlID], "text", text[s]);
s++;
}
}
spoutcontrols.OpenControls(sendername);
}
bool UpdateControls(const char** text, float *floats, float *toggles, float *press, int *numControls) {
int Vcontrols = numControls[0];
int Tcontrols = numControls[1];
int Pcontrols = numControls[2];
int Scontrols = numControls[3];
int all = Vcontrols + Tcontrols + Pcontrols + Scontrols;
int v = 0, t = 0, p = 0, s = 0;
if (spoutcontrols.CheckControls(myControls)) {
for (int controlID = 0; controlID < all; controlID++) {
if (myControls[controlID].type == 10) {
floats[v] = myControls[controlID].value;
v++;
}
if (myControls[controlID].type == 0) {
toggles[t] = myControls[controlID].value;
t++;
}
if (myControls[controlID].type == 1) {
press[p] = myControls[controlID].value;
p++;
}
if (myControls[controlID].type == 100) {
text[s] = myControls[controlID].text.data();
s++;
}
}
return true;
}
return false;
}
void CloseControls() {
spoutcontrols.CloseControls();
}
and here's the c# code:
public unsafe class SystemSpoutSenderNode: IDisposable
{
[System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
private static extern void InitializeControls(IntPtr sendername, IntPtr numControls,String[] names, IntPtr types, IntPtr floats, IntPtr toggles, IntPtr press, String[] text);
[System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
private static extern int CloseControls();
[System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool UpdateControls([In, Out] String[] text, [In, Out] float[] floats, [In, Out] float[] toggles, [In, Out] float[] press, IntPtr numControls);
[System.Runtime.InteropServices.DllImport("SpoutControls4vvvv.dll")]
private static extern int ReleaseMemory(IntPtr ptr);
public void Evaluate(int SpreadMax)
{
//countControls determines number of controls per type (string,float,toggle,click)
int[] controls = countControls(FType);
//sumControls will just add up all elements in controls
int all = sumControls(controls);
//in my code these arrays will get filled with values, deleted here for readability
String[] names = new String[all];
int[] types = new int[all];
float[] floats = new float[controls[0]];
float[] toggles = new float[controls[1]];
float[] press = new float[controls[2]];
String[] text = new String[controls[3]];
//initialze return arrays
String[] Rtext = new String[controls[3]];
float[] Rfloats = new float[controls[0]];
float[] Rtoggles = new float[controls[1]];
float[] Rpress = new float[controls[2]];
//allocate pointers
IntPtr SndrNamePtr = NativeUtf8FromString(FSenderName);
IntPtr BinPtr = Marshal.AllocHGlobal(4*sizeof(int));
IntPtr TypePtr = Marshal.AllocHGlobal(all*sizeof(int));
IntPtr FloatPtr = Marshal.AllocHGlobal(controls[0]*sizeof(float));
IntPtr TogglePtr = Marshal.AllocHGlobal(controls[1]*sizeof(float));
IntPtr PressPtr = Marshal.AllocHGlobal(controls[2]*sizeof(float));
try
{
//copy control info + defaults to pointer
Marshal.Copy(controls, 0, BinPtr, 4);
Marshal.Copy(types, 0, TypePtr, all);
Marshal.Copy(floats, 0, FloatPtr, controls[0]);
Marshal.Copy(toggles, 0, TogglePtr, controls[1]);
Marshal.Copy(press, 0, PressPtr, controls[2]);
//initialize controls
if (FWrite) InitializeControls(SndrNamePtr,BinPtr,names,TypePtr,FloatPtr,TogglePtr,PressPtr,text);
//update controls
bool changed = UpdateControls(Rtext,Rfloats,Rtoggles,Rpress,BinPtr);
//FF, FT, FS and FP are the outputs in my c# host
if (changed){
for(int j=0; j<controls[0];j++){
FF[j]=Rfloats[j];
}
for(int j=0; j<controls[1];j++){
FT[j]=FloatToBool(Rtoggles[j]);
}
for(int j=0; j<controls[3];j++){
FS[j]=Rtext[j];
}
}
for(int j=0; j<controls[2];j++){
FP[j]=FloatToBool(Rpress[j]);
}
}
finally
{
Marshal.FreeHGlobal(SndrNamePtr);
Marshal.FreeHGlobal(BinPtr);
Marshal.FreeHGlobal(FloatPtr);
Marshal.FreeHGlobal(TogglePtr);
Marshal.FreeHGlobal(PressPtr);
}
}
}
}
public void Dispose()
{
CleanUp();
CloseControls();
}
}
NOTE: the c# code runs without precompiling in a frame-based, c# host environment for graphical programming (vvvv), therefore i've deleted host specific decalarations of inputs (FType,FSenderName) and outputs (FF,FS,FP,FT) to avoid confusion. These will be used to "connect" this code with other functionality. Evaluate will be called every frame by the host.
Now to the actual question(s):
it's working fine so far in 32 bit, but in 64 bit my c# host crashes without any message. after some reading i believe this is due to pointer sizes being different in 32/64bit systems, but i'm not exactly sure what to do/if this actually applies here. I would be very thankful if you could
explain me how (and why) to get this code to run in 64 bit
point out any other mistakes you might spot along the way- i'm completely new to c++ and still a beginner in c#, so i'm pretty confident there's a lot to improve here; especially: memory leaks and passing the values from c++ to c# and vice versa...uiuiui.
I've understood that I shouldn't cast a pointer to an int in 64 bit, so the last thing I've tried is to change from
int Vcontrols = numControls[0];
int Tcontrols = numControls[1];
int Pcontrols = numControls[2];
int Scontrols = numControls[3];
to
int Vcontrols = (INT_PTR)numControls[0];
int Tcontrols = (INT_PTR)numControls[1];
int Pcontrols = (INT_PTR)numControls[2];
int Scontrols = (INT_PTR)numControls[3];
but with no luck, therefore I'm posting my original problem, even if this is a correct improvement(?).
EDIT: thanks to #dkackman for pointing out one unclear point: my cpp code calls functions which come as source code (SpoutControls.h) with the native 32 bit dll. It's not the source for the 32 bit dll itself but declares the functions used to (as far as i can tell) access the same shared memory as the 32 bit dll.
I can also copy paste the code here if this might be the problem?
Also can be found here
thank you.
I am afraid you are out of luck. If your process is 64bit, you won't be able to load that 32bit dll, no matter how much you try.
Can I load a 32 bit DLL into a 64 bit process on Windows?
from https://msdn.microsoft.com/en-us/library/windows/desktop/aa384231(v=vs.85).aspx
On 64-bit Windows, a 64-bit process cannot load a 32-bit dynamic-link
library (DLL).
Without access to its source, your only option would be to convert your host to 32bit or otherwise figure out how to host the 32bit plugin in a 32bit process and use some sort of IPC to communicate with it from a 64bit host process.
So my guess is that this has nothing to do with your wrapper, array passing or interop code.

Passing 2D string array (non-blittable) from C# to C++

The issue is passing a 2D string array (non-blitable) from managed C# to unmanaged C++.
I not sure if the DllImport and MarshalAs conventions are fully correct for this type of string array. Maybe, the pointer / memory allocation definition has a missing attribute. Many thanks for your comments.
public struct TestStruct
{
public string[,] stringArray;
}
[DllImport("C:\\Users\\Win32Project2.dll",
EntryPoint = "DDentry",
CallingConvention = CallingConvention.StdCall)]
public static extern void DDentry
(
[In][MarshalAs(UnmanagedType.LPArray,
ArraySubType = UnmanagedType.LPStr)] string[,] arrayReadDat, int iDim1, int iDim2
);
private void button6_Click_1(object sender, EventArgs e)
{
TestStruct arrayReadDat = new TestStruct();
arrayReadDat.stringArray = new string[lastRow+1, lastCol+1];
for (int i = 2; i <= lastRow; i++)
{
for (int j = 1; j <= lastCol; j++)
{
arrayReadDat.stringArray[i, j] = i;
}
}
int size = Marshal.SizeOf(typeof(TestStruct));
IntPtr strPointer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(arrayReadDat, strPointer, false);
DDentry(arrayReadDat.stringArray, lastRow+1, lastCol+1);
Marshal.FreeHGlobal(strPointer);
}
Here the unmanaged C++ code, which don not show the data from the C# code:
_declspec(dllexport) void DDentry(string *p2DIntArray, int iDim1, int iDim2)
{
int iIndex = 0;
for (int i = 2; i <= iDim1; i++)
{
for (int j = 1; j <= iDim2; j++)
{
arrayREAD[i][j] = p2DIntArray[iIndex++];
}
}
}
It looks like, that instead of importing the DLL in C++ code and exporting it in C# code, You are doing it exactly vice versa.
An example how to call a managed DLL from native Visual C++ code can be found here:
https://support.microsoft.com/en-us/kb/828736
It is written for VS2005, but the overall logic should be same in newer VS versions also.

Updating an C dll struct array and its elements values from C# code using dllimport

I have C code which will be build as a dynamic library (DLL) , which i would like
to call C function from C# using dLL created from the C code
C code :
struct data
{
char data_val1[100];
float data_val2;
float data_val3[50];
};
typedef struct data data;
#ifdef __cplusplus
extern "C" __declspec(dllexport) void cfun_call(data *pdata,long count);
#endif
#ifdef __cplusplus
extern "C"
{
#endif
__declspec(dllexport) void cfun_call(data *pdata,long count)
{
int x = 0;
for(x=0;x<count;x++)
{
data[x].data_val2 = (pdata->data_val3[49] + pdata->data_val3[48]) / 2.0;
}
}
#ifdef __cplusplus
}
#endif
Here i wanted to import the function "cfun_call" in C# code, and pass values to the fucntion call
and manipulate the passed values in C function from the dll and wanted to display the updated values
back to the C# code and display it, Since my expertise in C# is limited i need some help to solve this issue
C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
public class data
{
public char[] data_val1 = new char[100];
public float data_val2;
public float[] data_val3 = new float[50];
};
[DllImport("mycdll.dll", EntryPoint = "cfun_call", CallingConvention = CallingConvention.Cdecl, ExactSpelling = false)]
// void cfun_call(data *pdata,long count); //C function for reference
public static extern void cfun_call([In, Out] data[] ouputdata, long count);
static void Main(string[] args)
{
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
//Fill the data in objitemData
objData[i] = new objData();
for (int j = 0; j < 100; j++)
{
objData[i].data_val1[j] = '\0';
}
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.00;
}
objData[i].data_val2 = 0.00;
}
cfun_call(objData,10); //Making call to C dll function
for (int i = 0; i < 10; i++)
Console.WriteLine("{0} ", objData[i].data_val2);
Console.WriteLine("");//new line
Console.ReadLine();
}
}
Here the values (objData) passed from C# function is not updated by using the C dll fucntion , I am not sure why.
Can anyone point me to right direction ?
Edit 1:
I have updated code as suggested ,
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public char[] data_val1;
public float data_val2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public float[] data_val3;
};
Initialized struct elements like below ,
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
//Fill the data in objitemData
objData[i] = new objData();
for (int j = 0; j < 100; j++)
{
objData[i].data_val1[j] = '\0'; //I am getting exception here
}
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.00;
}
objData[i].data_val2 = 0.00;
}
Runtime i am getting null ptr exception , like
An unhandled exception of type 'System.NullReferenceException' occurred in mybinary.exe
Additional information: Object reference not set to an instance of an object.
How to initialize the struct array elements properly in manged code ?
Edit 2:
Hi one more question , when i add , objData[i].data_val3[k] = randomData; //randomvalues, It is not updated when making cfun_call while using contnt value it is updated why ?
Your translation of the struct is incorrect. You need it to be like so:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public char[] data_val1;
public float data_val2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public float[] data_val3;
};
You have to make this a struct since you need to pass an array of values. Your declaration using class leads to you passing an array of references.
You will need to initialize the arrays explicitly now. That might look like so:
data[] objData = new data[10];
for (int i = 0; i < 10; i++)
{
objData[i].data_val1 = new char[100];
objData[i].data_val2 = 0.00;
objData[i].data_val3 = new float[50];
for (int k = 0; k < 50; k++)
{
objData[i].data_val3[k] = 20.0f;
}
}
Further, C++ long is 32 bits wide, but C# long is 64 bits wide. You therefore have a mismatch. Your p/invoke should be:
[DllImport("mycdll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void cfun_call(
[In, Out] data[] ouputItem_data,
int count
);

c# Wrapper to native c++ code, wrapping a parameter which is a pointer to an array

I have the following simple DLL in c++ un-managed code;
extern "C" __declspec(dllexport) void ArrayMultiplier(float (*pointerArray)[3], int scalar, int length);
void ArrayMultiplier(float (*pointerArray)[3], int scalar, int length)
{
for (int i = 0 ; i < length ; length++)
{
for (int j = 0; j < 3; j++)
{
pointerArray[i][j] = pointerArray[i][j] * scalar;
}
}
}
I have tried writing the following wrapper function for the above in c#:
[DllImport("sample.dll")]
public static extern void ArrayMultiplier(ref float elements, int scalar, int length);
where elements is a 2 dimentional 3x3 array:
public float[][] elements =
{
new float[] {2,5,3},
new float [] {4,8,6},
new float [] {5,28,3}
};
The code given above compiles, but the program crashes when the wrapper function is called:
Wrapper.ArrayMultiplier(ref elements, scalar, length);
Please help me here, and tell me whats wrong with the code above, or how a wrapper can be written for a simple c++ function:
void SimpleFunction(float (*pointerToArray)[3]);
Thank you all in advance
There are a few ways to do this.
The unsafe route, which works well with 2D arrays (that you have):
[DllImport("fastprocessing.dll", EntryPoint = "MyFunc")]
public static extern void MyFuncViaDLL(int inPtr, int outPtr, int inSize1, int size2, int param);
called via
private unsafe float[] MyFunc(float[] inData, int inSize1, int inSize2, int param1, int param2) {
float[] theOutData = new float[inChannelData.Length];
fixed (float* inBufferPtr = &inChannelData[0]) {
fixed (float* outBufferPtr = &theOutData[0]) {
MyFuncViaDLL((int)inBufferPtr, (int)outBufferPtr, inSize1, inSize2, param);
}
}
return theOutData;
}
That will work in an unsafe way, but you'd need to change your input arrays into 1D arrays. I think that's a better idea anyway, but that's just the way that I think.
If you want to be safe about it, add another parameter that is the size of the array itself, and then do some marshalling. Again, though, you'll need to go into 1D arrays:
Instead, you want to do some marshalling, like so:
[DllImport("fastprocessing.dll", EntryPoint = "MyFunc")]
public static extern void MyFuncViaDLL([MarshalAs(UnmanagedType.LPArray)]float[] inPtr, int size1, int size2, int totalSize, int param2);
Then just call the function directly:
MyFuncViaDLL(array, size1, size2, size1*size2, param1, param2);
Your C++ would then change to:
void ArrayMultiplier(float *pointerArray, int inSize1, int inSize2, int inTotalSize, int scalar)
{
int i, j, index;
for (i = 0 ; i < size1; i++)//note that length++ would be very very wrong here
{
for (j = 0; j < size2; j++)
{
index = i*size2 + j;
if(index >= inTotalSize) { return; } //avoid walking off the end
pointerArray[i*size2 + j] *= scalar;
}
}
}
If you want, you can add in the check against total length to ensure that you don't walk off the end, but that'll be a pretty big speed hit (enough to want to not use C++), as if statements aren't free.
Having done all of that, however, I have to ask-- why not just do this directly in C#, and save yourself the hassle of interop services like marshalling? C++ tends to be faster for complicated things, but for a quick array walk, I've seen C# behave pretty well. It can be pretty quick in C# too, once it's a 1D array:
int i;
for (i = 0; i < array.length; i++){
array[i] *= scalar;
}

Categories