I have created a dll, with a function like this
CFunc.h
extern __declspec(dllexport) void fillStr(char* retStr, int strlen);
CFunc.cpp
void fillStr(char* retStr, int strlen)
{
//Option 1
const char* retTemp = "abc";
//Option 2
string retString = "abc";
const char* retTemp = retString.c_str();
cout << "ret in dlb:" << retTemp << endl;
strcpy_s(retStr, strlen, retTemp);
}
Then in C# I have
[DllImport("myDll.dll")]
static extern string fillStr(StringBuilder str, int strLen);
StringBuilder sb = new StringBuilder(2^31);
fillStr(sb, sb.Capacity);
string myString = sb.ToString();
When I use option 1, which is to directly create the const char*, the program works fine.
However, when I use a string, it crashes unexpectedly, with the exception "A heap has been corrupted". I have tried using memcpy and creating the string on the heap, to then delete later, but same error.
Found my mistake.
The C++ function is void while the C# code looks for a returned string.
Changing it to this
[DllImport("myDll.dll")]
static extern void fillStr(StringBuilder str, int strLen);
StringBuilder sb = new StringBuilder(2^31);
fillStr(sb, sb.Capacity);
string myString = sb.ToString();
worked.
Related
I want to create a function in c++ that takes two parameter(char[], int) and modifies the parameters(Similar to out parameter in c#) and then create a dll that can be used in c#.
C++ Sample Code:
static void funct(char * name, int size)
{
char[] testName="John";
name=&testName;
size=5;
}
C# Sample Code:
const string dllLocation = "D:\\Test.dll";
[DllImport(dllLocation, CallingConvention = CallingConvention.Cdecl)]
private static extern void funct(StringBuilder name, int size);
This is not correct code. It is just to give an idea of what I want. I want to use c# to access the c++ function through dll and obtain the name and size(no of characters).
Use pointers in the C++ side and marshal these by ref on the C# side.
static void funct(char** name, int* size)
{
char[] testName="John";
*name=&testName;
*size=5;
}
const string dllLocation = "D:\\Test.dll";
[DllImport(dllLocation, CallingConvention = CallingConvention.Cdecl)]
private static extern void funct(ref StringBuilder name, ref int size);
Beware with the char array, it may create memory leaks or worse crashes, it would be better to first test the size of the buffer and the size of the data you want to pass to C#, if there is enough room copy it, else inform to C# that it needs more size so it's allocated by C#.
Something like this:
static int funct(char** name, int* size, int bufferSize)
{
if(bufferSize < 4)
return 4;
char[] testName="John";
memcpy(name*, &char[0], 4);
*size=5;
return -1;
}
const string dllLocation = "D:\\Test.dll";
[DllImport(dllLocation, CallingConvention = CallingConvention.Cdecl)]
private static extern void funct(ref StringBuilder name, ref int size, int bufferSize);
StringBuilder sb = null;
int size = 0;
//Request size
int neededSize = funct(ref sb, ref size, 0);
//Create buffer with appropiate size
sb = new StringBuilder(neededSize);
//Call with the correct size, test result.
if(funct(ref sb, ref size, neededSize) != -1)
throw new Exception();
I call a C/C++ DLL function from C# and have a problem when the function has a wchar_t* parameter.
I modified the parameter from wchar_t* to char*, then everything is ok.
Why? how to make it work well with wchar_t*?
Here is my code:
C#
[DllImport("kernel32")]
private static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint loadFlag);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr lib, string funcName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr lib);
delegate void DllTestFunc(string str, byte[] data, int length);
static void Main(string[] args)
{
var dllInstance = LoadLibraryEx(#"D:\Development\NativeDll\x64\Debug\NativeDll.dll", IntPtr.Zero, 0x0008);
var ptr = GetProcAddress(dllInstance, "DllTestFunc");
var func = Marshal.GetDelegateForFunctionPointer<DllTestFunc>(ptr);
var inputStr = #"E:\test.dat";
var data = new byte[100];
data[0] = 0x55;
func(inputStr, data, 2020);
FreeLibrary(dllInstance);
}
C/C++ Code:
void __stdcall DllTestFunc(wchar_t* str1, unsigned char* data, const int length)
{
cout << str1 << endl;
cout << int(data[0]) << endl;
cout << length << endl;**strong text**
}
Because I need to use the wchar_t*, how to resolve it?
You probably need to tell the marshaller how to transfer the str argument of your delegate:
delegate void DllTestFunc([MarshalAs(UnmanagedType.LPWStr)] string str, byte[] data, int length);
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.
I have dll with function:
extern "C"
int
doJob(char** buffer);
Its usage with C++ looks like this:
char* buf;
int status = doJob(&buf);
What definition should I have for this function in C#?
How can I use this function in C#?
One of the possible patterns is:
[DllImport("containsdojob.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 doJob(out IntPtr buffer);
[DllImport("containsdojob.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void freeMemory(IntPtr buffer);
and
IntPtr buffer = IntPtr.Zero;
string str = null;
try
{
doJob(out buffer);
if (buffer != IntPtr.Zero)
{
str = Marshal.PtrToStringAnsi(buffer);
}
}
finally
{
if (buffer != IntPtr.Zero)
{
freeMemory(buffer);
}
}
Note that you'll need a freeMemory method to free the memory allocated by doJob.
There are other possible patterns, for example based on BSTR and SysAllocString that are easier to implement C#-side (but more difficult to implement C-side)
The "pattern" for using BSTR:
C-side:
char *str = "Foo"; // your string
int len = strlen(str);
int wslen = MultiByteToWideChar(CP_ACP, 0, str, len, 0, 0);
BSTR bstr = SysAllocStringLen(NULL, wslen);
MultiByteToWideChar(CP_ACP, 0, str, len, bstr, wslen);
// bstr is the returned string
C#-side:
[DllImport("containsdojob.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 doJob([MarshalAs(UnmanagedType.BStr)] out string buffer);
string str;
doJob(out str);
The memory is automatically handled (freed) by the CLR.
If you are using Visual C++ you can even
char *str = "Foo"; // your string
_bstr_t bstrt(str);
BSTR bstr = bstrt.Detach();
// bstr is the returned string
Or C-side you could use one of the two allocators that can be freed C#-side: LocalAlloc or CoTaskMemAlloc:
char *str = "Foo"; // your string
char *buf = (char*)LocalAlloc(LMEM_FIXED, strlen(str) + 1);
// or char *buf = (char*)CoTaskMemAlloc(strlen(str) + 1);
strcpy(buf, str);
// buf is the returned string
Then you use the first example, but instead of calling
freeMemory(buffer);
you call:
Marshal.FreeHGlobal(buffer); // for LocalAlloc
or
Marshal.FreeCoTaskMem(buffer); // for CoTaskMemAlloc
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.