Calling C++ DLib from C# results in bad allocation exception - c#

If I run my code directly in C++ it runs just fine. However when I invoke it from C# I get a bad allocation message. My C++ experience is really low but I feel based on everything I have read and modified, that this should work.
My Situation
Pass image path through facial recognition, it will then save/serialize the results to disk and return back the image's index in the array (function: int AddImageToCollection)
If I run my code with the main function I get perfect results (Figure 1) but if I run it through C# (Figure 2) I get the following:
I'm getting log 4-1 but not log 4-2, and the only error in e.what() is "bad allocation". I created a parameterless test function that is hard coded to return 5 and that works so it is isolated to this more complex function, and I believe it has to be related to passing in data to a const char*.
Figure 1
frontal_face_detector detector = get_frontal_face_detector();
shape_predictor sp;
anet_type net;
bool fileExists(const std::string& name) {
ifstream f(name.c_str());
return f.good();
}
void log(std::string name) {
}
int main(int argc, char** argv) try
{
string str = "C:\\images\\Me.jpg";
const char* c = str.c_str();
int whatIsMyIdx = AddImageToCollection(c);
cout << whatIsMyIdx << endl;
cin.get();
}
catch (std::exception& e)
{
cout << e.what() << endl;
}
int AddImageToCollection(const char* imagePath)
{
deserialize("shape_predictor_5_face_landmarks.dat") >> sp;
deserialize("dlib_face_recognition_resnet_model_v1.dat") >> net;
matrix<rgb_pixel> image;
string imagePathStr(imagePath);
load_image(image, imagePathStr);
std::vector<matrix<rgb_pixel>> faces;
if (fileExists("faces_in_collection.dat")) {
deserialize("faces_in_collection.dat") >> faces;
}
auto facesDetected = detector(image);
if (facesDetected.size() == 0) { return -1; }
if (facesDetected.size() > 1) { return -2; }
auto shape = sp(image, facesDetected[0]);
log("4-1");
matrix<rgb_pixel> face_chip;
extract_image_chip(image, get_face_chip_details(shape, 150, 0.25), face_chip);
log("4-2");
faces.push_back(move(face_chip));
serialize("faces_in_collection.dat") << faces;
std::vector<matrix<float, 0, 1>> face_descriptors;
if (fileExists("face_descriptors_in_collection.dat")) {
deserialize("face_descriptors_in_collection.dat") >> face_descriptors;
}
face_descriptors.push_back(net(faces[faces.size() - 1]));
serialize("face_descriptors_in_collection.dat") << face_descriptors;
return faces.size() - 1; //Return Image's Index in Array
}
Figure 2
[DllImport("FacialRecognition.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestImageToCollection();
[DllImport("FacialRecognition.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int AddImageToCollection([MarshalAs(UnmanagedType.LPStr)]string imagePath);
private void Form1_Load(object sender, EventArgs e)
{
int whatIsMyTestIdx = TestImageToCollection();
int whatIsMyIdx = AddImageToCollection(#"C:\images\Me.jpg");
MessageBox.Show(whatIsMyIdx.ToString());
}

You need to adapt your code as following
in the DLL
extern "C" __declspec(dllexport) int AddImageToCollection(LPCWSTR imagePath)
{
....
// you may need to convert from Unicode string to ANSI if you are calling function that accept ANSI strings
}
and in the c# application
[DllImport("FacialRecognition.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public static extern int AddImageToCollection(string imagePath);
and call your function like
int whatIsMyIdx = AddImageToCollection(#"C:\\images\\Me.jpg");
For the above to work correctly, Do not forget to compile both application and Dll as Unicode. if they are differ you should adapt the correct export and import declarations.

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

AccessViolationException when Call C++ dll on C#

I am implementing a C++ Dll for C#. But when I run my program, an AccessViolationException will occur. Here is the code.
in C++:
extern "C" DLLIMPORT double __cdecl ErrorCalculate(double* pBufferA, double* pBufferB,__int32 length)
{
__m128d xfdLoadA;
__m128d xfdLoadB;
const double* pA = pBufferA;
const double* pB = pBufferB;
//do somthing
for(int i=0;i<length/2;i++)
{
xfdLoadA = _mm_load_pd(pA);//error occur at this line
xfdLoadB = _mm_load_pd(pB);
pA+=2;
pB+=2;
// do somthing
}
// do somthing
}
in C#:
[DllImport("test.dll", EntryPoint = "ErrorCalculate", CallingConvention = CallingConvention.Cdecl)]
static extern double ErrorCalculate(double[] pBufferA, double[] pBufferB, int length);
public void frameProcessing(Image<Gray, byte> frame){
//do something
SSE4 sse4 = new SSE4();
unsafe
{
//length of samplePoint is the same as tempPoint
fixed (double* sample = samplePoint)
{
fixed (double* test = tempPoint)
{
errorSum = sse4.ErrorCalc(sample, test, length);
}
}
}
if(errorSum<=threshold)
{//do something}
}
frameProcessing will be called several times. Sometimes the error will occur at first time I call frameProcessing, sometimes second time. And I found that error occur at _mm_load_pd(pA). I'm sure that pA and pB are not null(Value of the two array can be printed out). Any help would be appreciated.

C# call C++ DLL passing pointer-to-pointer argument

Could you guys please help me solve the following issue?
I have a C++ function dll, and it will be called by another C# application.
One of the functions I needed is as follow:
struct DataStruct
{
unsigned char* data;
int len;
};
DLLAPI int API_ReadFile(const wchar_t* filename, DataStruct** outData);
I wrote the following code in C#:
class CS_DataStruct
{
public byte[] data;
public int len;
}
[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int API_ReadFile([MarshalAs(UnmanagedType.LPWStr)]string filename, ref CS_DataStruct data);
Unfortunately, the above code is not working... I guess that is due to the C++ func takes a pointer-to-pointer of DataStruct, while I just passed a reference of CS_DataStruct in.
May I know how can I pass a pointer-to-pointer to the C++ func? If it is not possible, is there any workaround? (the C++ API is fixed, so changing API to pointer is not possible)
Edit:
Memory of DataStruct will be allocated by c++ function. Before that, I have no idea how large the data array should be.
(Thanks for the comments below)
I used the following test implementation:
int API_ReadFile(const wchar_t* filename, DataStruct** outData)
{
*outData = new DataStruct();
(*outData)->data = (unsigned char*)_strdup("hello");
(*outData)->len = 5;
return 0;
}
void API_Free(DataStruct** pp)
{
free((*pp)->data);
delete *pp;
*pp = NULL;
}
The C# code to access those functions are as follows:
[StructLayout(LayoutKind.Sequential)]
struct DataStruct
{
public IntPtr data;
public int len;
};
[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
unsafe private static extern int API_ReadFile([MarshalAs(UnmanagedType.LPWStr)]string filename, DataStruct** outData);
[DllImport("ReadFile.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe private static extern void API_Free(DataStruct** handle);
unsafe static int ReadFile(string filename, out byte[] buffer)
{
DataStruct* outData;
int result = API_ReadFile(filename, &outData);
buffer = new byte[outData->len];
Marshal.Copy((IntPtr)outData->data, buffer, 0, outData->len);
API_Free(&outData);
return result;
}
static void Main(string[] args)
{
byte[] buffer;
ReadFile("test.txt", out buffer);
foreach (byte ch in buffer)
{
Console.Write("{0} ", ch);
}
Console.Write("\n");
}
The data is now transferred to buffer safely, and there should be no memory leaks. I wish it would help.
It isn't necessary to use unsafe to pass a pointer to an array from a DLL. Here is an example (see the 'results' parameter). The key is to use the ref attribute. It also shows how to pass several other types of data.
As defined in C++/C:
#ifdef __cplusplus
extern "C" {
#endif
#ifdef BUILDING_DLL
#define DLLCALL __declspec(dllexport)
#else
#define DLLCALL __declspec(dllimport)
#endif
static const int DataLength = 10;
static const int StrLen = 16;
static const int MaxResults = 30;
enum Status { on = 0, off = 1 };
struct Result {
char name[StrLen]; //!< Up to StrLen-1 char null-terminated name
float location;
Status status;
};
/**
* Analyze Data
* #param data [in] array of doubles
* #param dataLength [in] number of floats in data
* #param weight [in]
* #param status [in] enum with data status
* #param results [out] array of MaxResults (pre-allocated) DLLResult structs.
* Up to MaxResults results will be returned.
* #param nResults [out] the actual number of results being returned.
*/
void DLLCALL __stdcall analyzeData(
const double *data, int dataLength, float weight, Status status, Result **results, int *nResults);
#ifdef __cplusplus
}
#endif
As used in C#:
private const int DataLength = 10;
private const int StrLen = 16;
private const int MaxThreatPeaks = 30;
public enum Status { on = 0, off = 1 };
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Result
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = StrLen)] public string name; //!< Up to StrLen-1 char null-terminated name
public float location;
public Status status;
}
[DllImport("dllname.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "analyzeData#32")] // "#32" is only used in the 32-bit version.
public static extern void analyzeData(
double[] data,
int dataLength,
float weight,
Status status,
[MarshalAs(UnmanagedType.LPArray, SizeConst = MaxResults)] ref Result[] results,
out int nResults
);
Without the extern "C" part, the C++ compiler would mangle the export name in a compiler dependent way. I noticed that the EntryPoint / Exported function name matches the function name exactly in a 64-bit DLL, but has an appended '#32' (the number may vary) when compiled into a 32-bit DLL. Run dumpbin /exports dllname.dll to find the exported name for sure. In some cases you may also need to use the DLLImport parameter ExactSpelling = true. Note that this function is declared __stdcall. If it were not specified, it would be __cdecl and you'd need CallingConvention.Cdecl.
Here is how it might be used in C#:
Status status = Status.on;
double[] data = { -0.034, -0.05, -0.039, -0.034, -0.057, -0.084, -0.105, -0.146, -0.174, -0.167};
Result[] results = new Result[MaxResults];
int nResults = -1; // just to see that it changes (input value is ignored)
analyzeData(data, DataLength, 1.0f, status, ref results, out nResults);
If you do call native code, make sure your structs are alligned in the memory. CLR does not guarantee alignment unless you push it.
Try
[StructLayout(LayoutKind.Explicit)]
struct DataStruct
{
string data;
int len;
};
More info:
http://www.developerfusion.com/article/84519/mastering-structs-in-c/

Handling Array returned from c++ dll to C#

I have this in my dll created in c++
extern "C" __declspec(dllexport)
char* __stdcall hh()
{
char a[2];
a[0]='a';
a[1]='b';
return(a);
}
And this is how I am trying to handle code in c#
[DllImport(#"mydll.dll",CharSet = CharSet.Ansi,CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr hh();
static void Main(string[] args)
{
IntPtr a = hh();
//How to proceed here???
}
}
Help in proceeding further.
There is no way to handle such arrays. char a[2] is allocated on the stack in your C++ function and is destroyed as soon as you return from it. You should either pass an array from C# and fill it in the C++ code or allocate array in the heap and provide some means for freeing it.
When you have it correct the handling will depend on how you return the data from C++ code. If it's still IntPtr you could use Marshal.ReadByte methods to read characters from memory and use Encoding methods to convert those bytes into string if necessary.
const int bufferSize = 2; // suppose it's some well-known value.
IntPtr p = ...; // get this pointer somehow.
for (var i = 0; i != bufferSize; ++i)
{
var b = Marshal.ReadByte(p, i);
Console.WriteLine(b);
}
I got a solution as follows::
OUR C++ code goes as follows
extern "C" __declspec(dllexport)
char** __stdcall hh()
{
static char* myArray[3] = {"A1", "BB2", "CC3",};
return myArray;
}
And C# goes as follows
[DllImport(#"ourdll.dll",CharSet = CharSet.Ansi,CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr hh();
static void Main(string[] args)
{
IntPtr a = hh();
int j = 0;
string[] s=new string[100];
do
{
s[j] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(a,4*j));
j++;
}
while(s[j-1] != null);
}
The only problem now faced is that how can we know size of the array
so that in this statement
string[] s=new string[100];
we neednot waste our memory.
The answer would be
string stra = Marshal.PtrToStringAnsi(a);
But you also have the problem that the dll returns garbage per your code as char* is a local c style string.
Would be ok if you would return something like:
const char* str = "Hello from DLL.";
Try to use not empty StringBuilder as the return value.

Why this Explicit P/Invoke does not work?

The following .net to native C code does not work, any ideas
extern "C" {
TRADITIONALDLL_API int TestStrRef( __inout char* c) {
int rc = strlen(c);
std::cout << "the input to TestStrRef is: >>" << c << "<<" ;
c = "This is from the C code ";
return rc;
}
}
[DllImport("MyDll.dll", SetLastError = true)]
static extern int TestStrRef([MarshalAs(UnmanagedType.LPStr)] ref string s);
String abc = "InOut string";
TestStrRef(ref abc);
At this point Console.WriteLine(abc) should print "This is from the C code " but doesn't, Any ideas on what's wrong ?
FYI - i have another test function not using ref type string, it works just fine
Your code wrong at C side also. __inout annotation just tell compiler you can change buffer to which "c" argument pointed. But pointer itself located in stack and does not return to caller if you modified "c" argument.
Your declaration may look like:
extern "C" {
TRADITIONALDLL_API int TestStrRef( __inout char** c) {
int rc = strlen(*c);
std::cout << "the input to TestStrRef is: >>" << *c << "<<" ;
*c = "This is from the C code ";
return rc;
}
}
And C# side:
[DllImport("MyDll.dll", SetLastError = true)]
static extern int TestStrRef(ref IntPtr c);
{
String abc = "InOut string";
IntPtr ptrOrig = Marshal.StringToHGlobalAnsi(abc)
IntPtr ptr = ptrOrig; // Because IntPtr is structure, ptr contains copy of ptrOrig
int len = TestStrRef(ref ptr);
Marshal.FreeHGlobal(ptrOrig); // You need to free memory located to abc' native copy
string newAbc = Marshal.PtrToStringAnsi(ptr);
// You cannot free memory pointed by ptr, because it pointed to literal string located in dll code.
}
Does this work for you? Basically just add CallingConvention = CallingConvention.Cdecl to the DllImport statement. You might also want to specify the CharSet (for example: CharSet:=CharSet.Unicode)
[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
static extern int TestStrRef([MarshalAs(UnmanagedType.LPStr)] ref string s);

Categories