I have a c++ function like this:
myExport.h
extern "C" { __declspec(dllexport) const int Run(char *input, char *output, int *length); }
myExport.cpp
const int Run(char *input, char *output, int *length) {
std::ostringstream value;
value
<< "FOO" << "|"
<< "BAR" << "|";
auto str = value.str();
auto i = stdext::checked_array_iterator<char*>(output, str.length());
std::copy(str.begin(), str.end(), i);
output[str.length()] = '\0';
return 1;
}
And in C# I have:
myImport.cs
[DllImport("MyExport.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
private static extern int Run(
[MarshalAs(UnmanagedType.LPStr)]string input,
StringBuilder output,
ref int length);
public static string Execute(string input)
{
var length = 1024;
var output = new StringBuilder(1024);
var result = Run(input, output, ref length);
return output.ToString();
}
However, the output buffer is always empty. What am I doing wrong?
Since the type is char * for the second parameter, and the DLL function will fill in the buffer that's passed, the C# declaration should be as follows:
[MarshalAs(UnmanagedType.LPStr)]System.Text.StringBuilder output
Related
Am I doing it right? I have two projects in parallel, the first is code that was made in C ++ and the second project (Console made in AspNetCore v3.1) is the attempt to call the method that is in C ++ code.
I need to call the C ++ method "Decrypt" in the C # project. How do I do that?
C++ code
#include <stdlib.h>
#include <string.h>
#include<windows.h>
#include "bascript.hpp"
extern "C"
int FAR PASCAL _export
Decript( const LPSTR name, const LPSTR passwordWithCript,
LPSTR passwordWithoutCript, unsigned int sizeSpaceRetorn ) {
LPSTR result = lpDecript( name, passwordWithCript);
if ( sizeSpaceRetorn < strlen(result) )
return 0;
strcpy( passwordWithoutCript, result );
delete result;
return 1;
}
C#
class Program
{
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Auto, EntryPoint = "Decript")]
private static extern string Decript(string name, string passwordWithCript, string passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
string retorno = Decript("<user>", "<cript_password>", "", 0);
Console.WriteLine(retorno);
Console.ReadLine();
}
}
You can return a pointer from native world (C/C++, etc.) as long as you use a .NET compatible memory allocator. On Windows, that would be the COM Allocator.
So here are 3 ways to return a string: Ansi, Unicode and BSTR (unicode). Note: you should avoid using Ansi on Windows.
C++ side:
extern "C" __declspec(dllexport) void* DecryptA(const char* name, const char* password)
{
char str[] = "hello ansi world";
int size = (lstrlenA(str) + 1) * sizeof(char); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) void* DecryptW(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello unicode world";
int size = (lstrlenW(str) + 1) * sizeof(wchar_t); // terminating zero
// use .NET compatible allocator
void* buffer = CoTaskMemAlloc(size);
CopyMemory(buffer, str, size);
return buffer;
}
extern "C" __declspec(dllexport) BSTR DecryptBSTR(const wchar_t* name, const wchar_t* password)
{
wchar_t str[] = L"hello BSTR world";
// use .NET compatible allocator and COM coolness
return SysAllocString(str);
}
C# side:
[DllImport("mydll", CharSet = CharSet.Ansi)]
private static extern string DecryptA(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
private static extern string DecryptW(string name, string password);
[DllImport("mydll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string DecryptBSTR(string name, string password);
...
static void Main()
{
Console.WriteLine(DecryptA("name", "password"));
Console.WriteLine(DecryptW("name", "password"));
Console.WriteLine(DecryptBSTR("name", "password"));
}
Your C++ function does not return a string or equivalent. It returns an int result (success or failure), and the actual result goes in the passwordWithoutCript buffer.
So you need to create the buffer and pass it in.
Because you are using LPSTR on the C++ side, you need CharSet.Ansi:
[DllImport(#"C:\MS\VS\TesteDLLCentura\TesteDLLCentura\bin\Debug\netcoreapp3.1\Sises.DLL", CharSet = CharSet.Ansi, EntryPoint = "Decript")]
private static extern int Decript(string name, string passwordWithCript, StringBuilder passwordWithoutCript, uint sizeSpaceRetorn);
static void Main(string[] args)
{
var sb = new StringBuilder(1000); // or whatever size
if(Decript("<user>", "<cript_password>", sb, sb.Length) == 1)
Console.WriteLine(retorno);
Console.ReadLine();
}
I've already written this piece of code which works fine:
C++ code
extern "C"
{
const MYLIBRARY_EXPORT char* giefStrPlx(char* addon)
{
return addon;
}
}
C# code
[DllImport("ClassLibrary1")]
private static extern IntPtr giefStrPlx(string x);
void Start()
{
IntPtr stringPtr = giefStrPlx("Huntsman");
string huntsman = Marshal.PtrToStringAnsi(echoedStringPtr);
}
After this huntsman contains "Huntsman".
My problem is the step of doing something similar for an array of strings. I wrote the following function
extern "C"
{
const MYLIBRARY_EXPORT bool fillStrArray(char** lizt, int* length)
{
char* one = "one";
char* two = "two";
char* three = "three";
lizt[0] = one;
lizt[1] = two;
lizt[2] = three;
*length = 3;
}
}
I then tried to write the following piece of code in C#
[DllImport("ClassLibrary1")]
private static extern bool fillStrArray(ref IntPtr array, ref int length);
void Start()
{
IntPtr charArray = IntPtr.Zero;
int charArraySize = 0;
fillStrArray(ref charArray, ref charArraySize);
IntPtr[] results = new IntPtr[charArraySize];
Marshal.Copy(charArray, results, 0, charArraySize);
foreach (IntPtr ptr in results)
{
string str = Marshal.PtrToStringAnsi(ptr);
}
}
Which does not work. So now I'm a bit lost on how to accomplish this.
Here are the two helper functions I have from CLR to std::string and from std::string to string CLR
std::string CLROperations::ClrStringToStdString(String^ str)
{
if (String::IsNullOrEmpty(str))
return "";
std::string outStr;
IntPtr ansiStr = System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(str);
outStr = (const char*)ansiStr.ToPointer();
System::Runtime::InteropServices::Marshal::FreeHGlobal(ansiStr);
return outStr;
}
String ^ CLROperations::StdStringToClr(std::string str)
{
return gcnew String(str.c_str());
}
for using a List of strings you will need to use List<String^>^ mind the capital String. for a list of std::string use std::vector<std::string>
I'm using Robert Giesecke's Unmanaged Exports package to be able to call from C++ to C#.
This has to use the C interface from within C++. I have managed to get most things working, by scouring the web and picking up bits here and there....
extern "C"
{
// Simple
__declspec(dllimport) int IntTest(int input);
__declspec(dllimport) double DoubleTest(double input);
// Array of simple types in
__declspec(dllimport) int passArray(int t[], int i, int xx);
// String in and out
__declspec(dllimport) int PassStringIn(wchar_t* str);
__declspec(dllimport) int PassStringOut(wchar_t** str);
__declspec(dllimport) wchar_t* PassStringInOut(wchar_t* str);
// Array of strings types in
//__declspec(dllimport) int passArrayStrings(char** t, int i);
}
....
// Int in and out
int aa = IntTest(4);
// Double in and out
double bb = DoubleTest(4.3);
// Pass array in
int arr[4] = { 1,2,3,4 };
int cc = passArray(arr, 4, 0);
// String in
wchar_t* a_str = L"input string from C++";
int dd = PassStringIn(a_str);
// String out
wchar_t* b_str = L"not used";
int ee = PassStringOut(&b_str);
// String in & out
wchar_t* d_str = L"bob";
wchar_t* result = PassStringInOut(d_str);
corresponding C#
[DllExport( CallingConvention = CallingConvention.Cdecl)]
static int IntTest(int input)
{
return input + 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
static double DoubleTest(double input)
{
return input + 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int i, int x)
{
return tab[x];
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int PassStringIn( [MarshalAs(UnmanagedType.LPWStr)] string inputString)
{
Console.WriteLine("Hi, the string passed in was :" + inputString);
return 1;
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
static int PassStringOut([MarshalAs(UnmanagedType.BStr)] out string outputString)
{
Console.WriteLine("Hi, I will return the time from c#");
outputString = DateTime.Now.ToLongTimeString();
return 0; // indicates success
}
[DllExport(CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPTStr)]
public static string PassStringInOut([MarshalAs(UnmanagedType.LPTStr)]string name)
{
return string.Format("Hello from .NET assembly, {0}!", name);
}
Which was nice! Anyway would anybody be able to help with passing arrays of strings in and out. I am pretty sure the C# section should look like this:
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArrayStrings( [In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 1)] string[] tab, int i)
{
return 1;
}
I need some help on the C++(C) side on how to structure the array of strings in, such that they can be marshaled correctly. The mixed mode assembly created has both C# and and a C interface. As it is C and not C++ the arguments types of the exposed functions are not visible.
Thanks
You can use an IntPtr parameter.
You'll have to allocate unmanaged memory and copy the array into that blob anyway. Otherwise the GC will eat your array at some point.
Unmanaged Exports with Arrays
ok so after a lot of messing about I came to a solution:
// Array of strings types in
__declspec(dllimport) int passArrayStrings(BSTR* bstrArray, int i);
BSTR bstrArray[10] = { 0 };
for (int i = 0; i < 10; i++)
{
bstrArray[i] = ::SysAllocString(L"My String.");
}
int ff = passArrayStrings(bstrArray, 10);
for (int i = 0; i < 10; i++)
{
::SysFreeString(bstrArray[i]);
}
and on the c# side:
[DllExport(CallingConvention = CallingConvention.Cdecl)]
public static int passArrayStrings([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.BStr, SizeParamIndex = 1)] string[] tab, int iSize)
{
return 1;
}
I am calling a method from C# like this:
[DllImport(#"pHash.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ph_dct_videohash(string file, ref int length);
And here is the method I am calling from the library
ulong64* ph_dct_videohash(const char *filename, int &Length){
CImgList<uint8_t> *keyframes = ph_getKeyFramesFromVideo(filename);
if (keyframes == NULL)
return NULL;
Length = keyframes->size();
ulong64 *hash = (ulong64*)malloc(sizeof(ulong64)*Length);
//some code to fill the hash array
return hash;
}
How can I read the ulong array from the IntPtr?
While the Marshal class doesn't provide any methods for dealing with ulongs directly, it does offer you Marshal.Copy(IntPtr, long[], int, int) which you can use to get a long array and then cast the values to ulongs.
The following works for me:
[DllImport("F:/CPP_DLL.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern IntPtr uint64method(string file, ref int length);
static ulong[] GetUlongArray(IntPtr ptr, int length)
{
var buffer = new long[length];
Marshal.Copy(ptr, buffer, 0, length);
// If you're not a fan of LINQ, this can be
// replaced with a for loop or
// return Array.ConvertAll<long, ulong>(buffer, l => (ulong)l);
return buffer.Select(l => (ulong)l).ToArray();
}
void Main()
{
int length = 4;
IntPtr arrayPointer = uint64method("dummy", ref length);
ulong[] values = GetUlongArray(arrayPointer, length);
}
Consider just using unsafe code:
IntPtr pfoo = ph_dct_videohash(/* args */);
unsafe {
ulong* foo = (ulong*)pfoo;
ulong value = *foo;
Console.WriteLine(value);
}
I'm using a DLL written in C++ in my C# project by using DllImport and one of the functions I'm using looks like this:
[DllImport("dds.dll", CharSet = CharSet.Auto)]
private static extern int Par(
ddTableResults2 tableResult,
ref parResults ParResult,
int vul
);
The parResults struct is defined in C++ like this:
struct parResults {
/* index = 0 is NS view and index = 1
is EW view. By 'view' is here meant
which side that starts the bidding. */
char parScore[2][16];
char parContractsString[2][128];
};
The start of the C++ function
int STDCALL Par(struct ddTableResults * tablep, struct parResults *presp,
int vulnerable)
How should I define the above struct in C# to able to send that struct as en reference into the DLL function?
This is what I have tried but don't work at all and I just get a Access Violation Error
[StructLayout(LayoutKind.Sequential)]
public struct parResults
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[,] parScore;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[,] parContractsString;
public parResults(int x)
{
parScore = new char[2,16];
parContractsString = new char[2,128];
}
}
This is quite a tricky struct to marshal in C#. There are various ways to attempt it, but I think that it will be cleanest to represent the character arrays as byte arrays and marshal to and from strings by hand. Here is a demonstration of what I mean:
C++
#include <cstring>
struct parResults {
char parScore[2][16];
char parContractsString[2][128];
};
extern "C"
{
__declspec(dllexport) void foo(struct parResults *res)
{
strcpy(res->parScore[0], res->parContractsString[0]);
strcpy(res->parScore[1], res->parContractsString[1]);
}
}
C#
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[StructLayout(LayoutKind.Sequential)]
class parResults
{
private const int parScoreCount = 2;
private const int parScoreLen = 16;
private const int parContractsStringCount = 2;
private const int parContractsStringLen = 128;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parScoreCount * parScoreLen)]
private byte[] parScoreBuff;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = parContractsStringCount * parContractsStringLen)]
private byte[] parContractsStringBuff;
public string getParScore(int index)
{
string str = Encoding.Default.GetString(parScoreBuff,
index * parScoreLen, parScoreLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParScore(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parScoreLen);
Array.Copy(bytes, 0, parScoreBuff, index * parScoreLen, len);
Array.Clear(parScoreBuff, index * parScoreLen + len,
parScoreLen - len);
}
public string parContractsString(int index)
{
string str = Encoding.Default.GetString(parContractsStringBuff,
index * parContractsStringLen, parContractsStringLen);
int len = str.IndexOf('\0');
if (len != -1)
str = str.Substring(0, len);
return str;
}
public void setParContractsString(int index, string value)
{
byte[] bytes = Encoding.Default.GetBytes(value);
int len = Math.Min(bytes.Length, parContractsStringLen);
Array.Copy(bytes, 0, parContractsStringBuff,
index * parContractsStringLen, len);
Array.Clear(parContractsStringBuff,
index * parContractsStringLen + len,
parContractsStringLen - len);
}
public parResults()
{
parScoreBuff = new byte[parScoreCount * parScoreLen];
parContractsStringBuff =
new byte[parContractsStringCount * parContractsStringLen];
}
};
[DllImport(#"...", CallingConvention = CallingConvention.Cdecl)]
static extern void foo([In,Out] parResults res);
static void Main(string[] args)
{
parResults res = new parResults();
res.setParContractsString(0, "foo");
res.setParContractsString(1, "bar");
foo(res);
Console.WriteLine(res.getParScore(0));
Console.WriteLine(res.getParScore(1));
Console.ReadLine();
}
}
}
Here I've used a class to represent the struct. Since a class in C# is a reference, we don't declare the parameters of that type with ref. I've also used __cdecl for convenience to avoid having to work out what the decorated name of the function would be. But your library used __stdcall and so you need to stick to that.
The class demonstrates sending data in both directions. You could probably simplify the code if the data flow was more restricted.