How to import C++ dll from c# using PInvoke?
The definition of the exported function in c++ DLL is
int func1(const char* input,char** output)
{
int returnCode = 0;
std::stringstream xmlInputStream;
xmlInputStream<< std::string(input);
std::stringstream xmlOutputStream;
returnCode = doRequest(xmlInputStream,xmlOutputStream);
std::string xmlOutputString =xmlOutputStream.str();
*output=const_cast<char *> (xmlOutputString.c_str());
cout<<xmlOutputString;
return returnCode ;
}
I tried to import the function from c# like this...
==================================================================
[DllImport("sample.dll")]
public static extern int func1(String str1,out String str2);
string str1="hello";
string str2=String.Empty;
MyClas开发者_如何学Gos.func1(str1,out str2);
Console.writeln(str2);
====================================================================
Output is garbage value...
Why is it so and how to import this function from c#?
Thats simple, try to use custom marshaling (especially if you want to use char** pointers).
I found that you are no getting "Memory ass dumb" string, because of some improper realization of doRequest(char*,char**), as well as assigning to the result is not properly handled;
Firstly, if you are allocating memory in unmanaged process and passes it to managed process, you will need declare some mechanism to free unmanaged memory. Managed GC does not know anything about this memory, which will be lost overwize.
Secondy, you need to allocate memory for the results, and pass them to the unmanaged process, because the original memory locations can be rewritten in any time.
Lastly, you are getting only the first characted of the input just because you are not allocated memory for the results, effectively passing only a memory pointer to the memory location of the first output character, say &array[0]
Here is the compete code (MS VC++/MS C#), which fixes all the issues:
lib.cpp:
#include "stdafx.h"
#include "lib.h"
using namespace std;
int func1(char* input, char** output)
{
stringstream xmlInputStream, xmlOutputStream;
xmlInputStream << string(input);
int returnCode = doRequest(&xmlInputStream, &xmlOutputStream);
string xmlOutputString = xmlOutputStream.str();
//*output=const_cast<char *> (xmlOutputString.c_str());
long length = sizeof(char) * xmlOutputString.length();
char* src = const_cast<char *>(xmlOutputString.c_str());
char* dst = (char*)malloc(length+1);
memcpy_s(dst, length, src, length);
dst[length]=0; // 0 byte always ends up given ANSI string
*output = dst;
//cout << xmlOutputString;
return returnCode;
}
int func1_cleanup(char* memptr)
{
free(memptr);
return 0;
}
int doRequest(stringstream* xmlInputStream, stringstream* xmlOutputStream)
{
*xmlOutputStream << "Memory ass dumb";
return 0;
}
Program.cs:
using System;
using System.Runtime.InteropServices;
namespace test
{
class Program
{
[DllImport("lib.dll", EntryPoint = "func1", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern unsafe int func1(char* input, char** data);
[DllImport("lib.dll", EntryPoint = "func1_cleanup", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern unsafe int func1_cleanup(char* data);
static void Main(string[] args)
{
string input = "Hello, World!";
string output;
int result = func1(input, out output);
}
private const int S_OK = 0;
public static int func1(string input, out string output)
{
unsafe
{
output = null;
int result = -1;
fixed (char* parray1 = &input.ToCharArray()[0])
{
//
// if you allocating memory in a managed process, you can use this
//
//char[] array = new char[0xffffff];
//fixed(char* parray = &array[0])
{
//
// if you allocating memory in unmanaged process do not forget to cleanup the prevously allocated resources
//
char* array = (char*)0;
char** parray2 = &array;
result = func1(parray1, parray2);
if (result == S_OK)
{
//
// if your C++ code returns the ANSI string, you can skip this extraction code block (it can be useful in Unicode, UTF-8, UTF-7, UTF-32, all C# supported encodings)
//
//byte* self = (byte*)*((int*)parray2);
//byte* ptr = self;
//List<byte> bytes = new List<byte>();
//do
//{
// bytes.Add(*ptr++);
//}
//while (*ptr != (byte)0);
//output = Encoding.ASCII.GetString(bytes.ToArray());
output = Marshal.PtrToStringAnsi(new IntPtr(*parray2));
}
func1_cleanup(array);
}
}
return result;
}
}
}
}
I believe this should work
[DllImport("sample.dll")]
public static extern int func1(
[MarhsalAs(UnmanagedType.LPStr)] String str1,
[MarshalAs(UnmanagedType.LPStr)] out String str2);
The MarshalAs attribute is awesomely powerful for controlling interop.
EDIT (based on updated post with C++ code):
Maybe something more like this should work (I don't have VC++ on this computer to build a fake C++ DLL to check)
[DllImport("sample.dll")]
public static extern int func1(
[MarshalAs(UnmanagedType.LPStr)] String str1,
[MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeConst=1), Out] out String[] str2);
Note I added [Out] to the str2, I'm not sure this is good or necessary, so try with and without that (again, sorry, I can't try here). Also, you may need to use SizeParamIndex instead of SizeConst, I can't remember - if it needs to be SizeParamIndex, you'd need to change the C++ code to pass back 1 as the number of array elements.
This passes back str2 as a String[], but you can then just use the first element of that array to get the string.
There may be cleaner ways of doing this, too - a __deref_out parameter in C++ doesn't translate really well to C# that I remember, but I may be forgetting some of the arcana here.
精彩评论