I'm writing a DLL in C++ to search a subpicture in a Bitmap.
When the C++ Part is executed, the HBITMAP is not valid.
Here is my code:
C#:
[System.Runtime.InteropServices.DllImport("FindSubPicture.dll", EntryPoint = "FindSubPictures", CallingConvention = CallingConvention.Cdecl)]
private static extern System.IntPtr FindSubPicturesFct(System.IntPtr mImg, System.IntPtr sImg, int* nMatches);
public static System.Collections.Generic.List<TPoint> FindSubPictures(System.Drawing.Bitmap mImg, System.Drawing.Bitmap sImg)
{
TPoint* PStack = null;
int nMatches = 0;
System.Collections.Generic.List<TPoint> MyList = new System.Collections.Generic.List<TPoint>();
MyList.Clear();
PStack = (TPoint*)FindSubPicturesFct(mImg.GetHbitmap(), sImg.GetHbitmap(), &nMatches);
if(PStack == null) { return MyList;}
for (int i = 0; i < nMatches; i++) { MyList.Add(new TPoint(PStack[i].x[0], PStack[i].x[1])); }
try
{
System.Runtime.InteropServices.Marshal.FreeHGlobal((System.IntPtr)PStack);
}catch(System.Exception ex) { System.Console.WriteLine(ex.Message); }
return MyList;
}
C++:
#include "FindSubPictures.h"
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <omp.h>
struct TPoint
{
int x[2];
TPoint(int fx, int fy) {
x[0] = fx;
x[1] = fy;
}
void Reset() { x[0] = 0; x[1] = 0; }
void Set(int fx, int fy) { x[0] = fx; x[1] = fy; }
};
extern "C" __declspec(dllexport) TPoint* FindSubPictures(HBITMAP mImg, HBITMAP sImg, int* nMatches) {
int mImgWidth = -1, mImgHeight = -1, sImgWidth = -1, sImgHeight = -1;
TPoint* MyList = nullptr;
if (mImg == nullptr || sImg == nullptr || nMatches == nullptr) { return nullptr; }
return MyList;
}
Well, the C++ Library function is not doing anything until now. That's because the HBITMAP is not valid. In C#, the System.Drawing.Bitmap is valid.
When I enter "mIng.", no auto-completion is available.
Am I missing anything?
Obtaining a bitmap does not mean you can access it like a data field. You'll have to lock the bitmap data in memory first.
See also the answers in this thread here.
Bitmap.LockBits
Code would look as follows:
Rectangle rect = new Rectangle(0, 0, mImg.Width, mImg.Height);
System.Drawing.Imaging.BitmapData bmpData =
mImg.LockBits(rect, System.Drawing.Imaging.ImageLockMode.Read,
mImg.PixelFormat);
// Get the address of the first line.
IntPtr ptr2mImg = bmpData.Scan0;
Ok, I have the solution.
HBITMAP is only a handle "void *"
You first have to get the complete object by the function GetObject
Code:
extern "C" __declspec(dllexport) TPoint* FindSubPictures(HBITMAP mImg, HBITMAP sImg, int* nMatches) {
int mImgWidth = -1, mImgHeight = -1, sImgWidth = -1, sImgHeight = -1;
TPoint* MyList = nullptr;
if (mImg== nullptr || sImg== nullptr || nMatches == nullptr) { return nullptr; }
BITMAP mBMP, sBMP;
GetObject(mImg, sizeof(BITMAP), &mBMP);
GetObject(sImg, sizeof(BITMAP), &sBMP);
/*Now, BITMAP mBMP and sBMP are valid*/
return MyList;
}
Related
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);
}
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.
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.
Below I have a code snippet from c++.
I need to return array of pointers (to TempStruct).
The problem is that on c# side I get only one element. On the other I get AV.
**C++**
extern "C" __declspec(dllexport) void GetResult(TempStruct** outPtr, long *size)
{
*outPtr = (TempStruct*)new TempStruct*[2];
outPtr[0] = new TempStruct("sdf", 123);
outPtr[1] = new TempStruct("abc", 456);
*size = 2;
}
**C#**
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetResult(out IntPtr outPtr, out int size);
IntPtr ptr = IntPtr.Zero;
int length;
GetResult(out ptr, out length);
int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];
for (int i = 0; i < length; i++)
{
IntPtr wskptr = (IntPtr)(ptr.ToInt64() + (size * i));
someData2[i] = (TempStruct)Marshal.PtrToStructure(wskptr, typeof(TempStruct));
}
You are doing it wrong.
You are mixing pointer types.
By using the new TempStruct() you are creating an array of pointers to TempStruct. The example I gave you created an array of TempStruct. See the difference?
Now... TempStruct** outPtr should be TempStruct*** outPtr (because you want to return (*) an array (*) of pointers (*)... Or TempStruct**& if you prefer :-)
Change this line
someData2[i] = (TempStruct)Marshal.PtrToStructure(Marshal.ReadIntPtr(wskptr), typeof(TempStruct));
Because you must read the single pointers.
I do hope you are deleting the various TempStruct with delete and using the
delete[] ptr;
operator to delete the array of structures.
Full example:
C++:
struct TempStruct
{
char* str;
int num;
// Note the strdup. You don't know the source of str.
// For example if the source is "Foo", then you can't free it.
// Using strdup solves this problem.
TempStruct(const char *str, int num)
: str(strdup(str)), num(num)
{
}
~TempStruct()
{
free(str);
}
};
extern "C"
{
__declspec(dllexport) void GetResult(TempStruct ***outPtr, int *size)
{
*outPtr = new TempStruct*[2];
(*outPtr)[0] = new TempStruct("sdf", 123);
(*outPtr)[1] = new TempStruct("abc", 456);
*size = 2;
}
__declspec(dllexport) void FreeSomeData(TempStruct **ptr, int size)
{
for (int i = 0; i < size; i++)
{
delete ptr[i];
}
delete[] ptr;
}
}
C#:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
public string str;
public int num;
}
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResult(out IntPtr outPtr, out int numPtr);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeSomeData(IntPtr ptr, int num);
// C++ will return its TempStruct array in ptr
IntPtr ptr;
int size;
GetResult(out ptr, out size);
TempStruct[] someData2 = new TempStruct[size];
for (int i = 0; i < size; i++)
{
IntPtr ptr2 = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
someData2[i] = (TempStruct)Marshal.PtrToStructure(ptr2, typeof(TempStruct));
}
// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, size);
Note that you don't need [Serializable] and Pack=1 on the C# struct
More correct for the C++:
__declspec(dllexport) void GetResult(TempStruct **&outPtr, int &size)
{
outPtr = new TempStruct*[2];
outPtr[0] = new TempStruct("sdf", 123);
outPtr[1] = new TempStruct("abc", 456);
size = 2;
}
It is more correct because both outPtr and size can't be NULL. See https://stackoverflow.com/a/620634/613130 . The C# signature is the same.
The C++ code is wrong. It's returning an array of pointers to struct. The fact that you cast the value returned by new should have alerted you to the fact that you made a mistake. You want to return an array of struct.
It should be:
*outPtr = new TempStruct[2];
(*outPtr)[0].str = "sdf";
(*outPtr)[0].i = 123;
(*outPtr)[1].str = "abc";
(*outPtr)[1].i = 456;
*size = 2;
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.