I'm tyring to pass a C# string to C dll function which supposes to encrypt it. Unfortunately, it does simply nothing. After calling the function, the string is still the same.
C function:
#include <stdio.h>
#include <stdlib.h>
extern "C"{
__declspec(dllexport) void Encrypt( char *plainText,long height,long inputLength)
{
unsigned char *encryptedText=(unsigned char*)malloc(sizeof(plainText));
unsigned char **cipherArray;
cipherArray=(unsigned char**)malloc(height*sizeof(unsigned char *));
for(long i=0; i<height; i++)
{
cipherArray[i]=(unsigned char*)malloc(inputLength*sizeof(char));
for (long j=0; j<inputLength ; j++)
cipherArray[i][j]='#';
}
bool addRow=true;
long row=0;
long column = 0;
long arrayIterator = 0;
while(arrayIterator<inputLength){
cipherArray[row][column] = plainText[arrayIterator];
column++;
if(addRow)row++;
else row--;
if (row >= height)
{
row--;
row--;
addRow=false;
}
else if (row < 0)
{
row++;
row++;
addRow = true;
}
arrayIterator++;
}
long iterator=0;
for (long i=0; i< height; i++)
for(long j=0; j<inputLength;j++){
if(cipherArray[i][j]!='#'){
encryptedText[iterator]=cipherArray[i][j];
iterator++;
}
}
long j=0;
while(j<inputLength){
plainText[j]=encryptedText[j];
printf("%c",encryptedText[j]);
j++;
}
for(long i=0; i<height; i++)
free(cipherArray[i]);
free(cipherArray);
cipherArray = NULL;
}
}
Containing C# class:
namespace RailFenceCipher
{
public class CCipher
{
[DllImport("Win32Project3.dll", EntryPoint = "Encrypt", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void Encrypt([MarshalAs(UnmanagedType.LPStr)] string plainText, long height, long inputLength);
}
}
and calling for the function:
private void cipherC()
{
string plainText = this.fileInput;
Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
CCipher.dllprint();
CCipher.Encrypt(plainText, this.height, this.fileInput.Length);
this.fileOutputC = plainText;
Console.WriteLine("=== END ===");
}
after the calling, plainText is not changed.
This is to be expected. You are marshalling the data in, but not out. That is the behaviour for a parameter of type string. You need to use StringBuilder in order to marshal text back from the native code.
In addition, C# long is 64 bit, but C++ long in 32 bit. Your pinvoke should be:
[DllImport("Win32Project3.dll", CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi)]
public static extern void Encrypt(StringBuilder plainText,
int height, int inputLength);
And you need to make sure that the capacity of the StringBuilder instance that you pass is sufficient for the text to be returned.
Perhaps a bigger problem is that your C++ code is broken. At the least, you need to fix the malloc call which receives sizeof(plainText). That's the size of a pointer. You need to pass the length of the buffer, inputLength. You should debug that code first before attempting interop.
Related
To return arrays in C pointers needed. To clarify my question, I created a simple .c function and compiled as DLL and accessing it by C#. The C function is:
#include <stdio.h>
__declspec(dllexport) int* ReturnArray(int size) {
int* arr = malloc(size * sizeof(int));
arr[0] = 19;
arr[1] = 18;
arr[2] = 17;
arr[3] = 16;
arr[4] = 15;
return arr;
}
On the C# side:
namespace ConsoleC2CSExample
{
class Program
{
[DllImport("array.dll", EntryPoint = "ReturnArray", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern short* ReturnArray(int size);
static unsafe void Main(string[] args)
{
int arraySize = 5;
short *ptr = ReturnArray(arraySize);
for(int i = 0; i < arraySize; i++)
Console.WriteLine(*(ptr + i * sizeof(short)));
Console.Read();
}
}
}
And the output is as expected as follows:
I have two questions here.
How can this be done by not using unsafe mode?
And what are the dangers if any of using unsafe?
C#/.NET is so-called managed code because it runs safely on the common language runtime (CLR). Whereas, C code is so-called unmanaged code.
The CLR ensures, that we will not suffer the typical problems known from C code (like buffer underflow, buffer overflow, ...) resulting in vulnerable code, crashing applications, unknown behavior, etc.
So the danger of using unsafe code is, that errors in your C library will probably terminate your entire C#/.NET application unexpectedly, causing you a lot of headache.
To get at least rid of the unsafe context within your C# code, you can try the following:
using System.Runtime.InteropServices;
namespace ConsoleC2CSExample
{
class Program
{
[DllImport("array.dll", EntryPoint = "ReturnArray", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ReturnArray(int size);
static void Main(string[] args)
{
int arraySize = 5;
IntPtr ptr = ReturnArray(arraySize);
for(int i = 0; i < arraySize; i++)
Console.WriteLine(Marshal.ReadInt32(ptr + i * sizeof(int)));
Console.Read();
}
}
}
Hello I'm wrapping C++ library with C#. Next function in C++:
SCREENCAPTUREDLL_API wchar_t** getAudioDeviceList(int* listSize) {
static std::vector<wchar_t*> descriptionList;
AudioCaptureList::getInstance().Update();
AudioCaptureList::getInstance().getList(&descriptionList);
*listSize = descriptionList.size();
return &descriptionList[0];
}
Wrapping with next C# code:
[DllImport(screenCaptureDLLPath, CallingConvention = callConversion)]
private static extern IntPtr getAudioDeviceList(ref int arrayCount);
public static string[] GetAudioDeviceList()
{
IntPtr outputStr;
int length = 0;
outputStr = getAudioDeviceList(ref length);
string[] resultArray = new string[length];
for (int j = 0; j < length; j++)
{
resultArray[j] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(outputStr, 4 * j));
}
return resultArray;
}
That works perfect, exactly as I expected, but I was about to change the way I returning value from function itself to variable by reference, so I changing my code to:
C++
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** list, int* listSize) {
static std::vector<wchar_t*> descriptionList;
AudioCaptureList::getInstance().Update();
AudioCaptureList::getInstance().getList(&descriptionList);
*listSize = descriptionList.size();
list = &descriptionList[0];
}
C#
[DllImport(screenCaptureDLLPath, CallingConvention = callConversion)]
private static extern void getAudioDeviceList(out IntPtr listRef, ref int arrayCount);
public static string[] GetAudioDeviceList()
{
IntPtr outputStr;
int length = 0;
getAudioDeviceList(out outputStr, ref length);
string[] resultArray = new string[length];
for (int j = 0; j < length; j++)
{
resultArray[j] = Marshal.PtrToStringUni(Marshal.ReadIntPtr(outputStr, 4 * j));
}
return resultArray;
}
But I got error, returned memory address is zero. What is the problem here? Please help me understood what cause the problem and how to fix that, thanks!
Why doesn't Pinvoke work? Because you are trying to interpret a pointer to a string as a pointer to a set of strings. But there is nothing wrong with PInvoke - it happens because there is actually a problem with new function signature and its internal code.
See:
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** listRef, int* listSize);
can't provide the same data like
DLL_API wchar_t** getAudioDeviceList(int* listSize)
Because original definition basically returned pointer to a set of pointers to strings(C style strings, I mean), while wchar_t** listRef can only allow to return a single pointer to a string.
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t** listRef, int* listSize)
{
...
*listRef = "string";
I don't know what is going inside new version of the function(you didn't show the code), but listRef = &descriptionList[0]; will compile though won't do anything, and even if *listRef = &descriptionList[0]; somehow compiles it won't contain what you want.
So function signature should containt triple pointer to allow return of a set of strings.
SCREENCAPTUREDLL_API void getAudioDeviceList(wchar_t*** listRef, int* listSize)
{
...
*listRef = &descriptionList[0];
}
Then your PInvoke would work correctly because it will have the same pointer to a set of string pointers.
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 trying to create a Win32 DLL exposes some functions which are called in C# as follows
__declspec(dllexport) int GetData(unsigned char* *data, int* size)
{
try
{
int tlen = 3;
unsigned char* tchr = new unsigned char[5];
tchr[0] = 'a';
tchr[1] = 'b';
tchr[2] = 'c';
*size = tlen;
*data = tchr;
return 1;
}
catch (char *p)
{
return 0;
}
}
And on C# side
[DllImport("MyDll.dll")]
static extern int GetData(ref byte[] data, ref int size);
static void Main()
{
try
{
int hr = 0;
byte[] gData = null;
int gSize = 0;
hr = GetData(ref gData, ref gSize);
Console.WriteLine(gSize);
for (int i = 0; i < gSize; i++)
Console.WriteLine((char)gData[i]);
}
catch (Exception p)
{
Console.WriteLine(p.ToString());
}
}
When I run C# code, AccessViolationException happens on GetData function which is a sign of exception in C++ code however, following C++ code snippet works fine without any error.
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char* data = NULL;
int size = NULL;
GetData(&data, &size);
printf("%d", size);
for (int i = 0; i < size; i++)
printf("%c,", data[i]);
return 0;
}
If you compare C# main function and C++ _tmain, they are almost analoguous so where I may make a mistake?
You are returning an array allocated by a call to C++ new and hoping that the marshaler will turn it into a C# byte[]. That won't happen.
You'll need to pass a pointer by reference and then marshal it by hand. Your p/invoke should look like this:
[DllImport("MyDll.dll")]
static extern int GetData(out IntPtr data, out int size);
When the function returns data will point to the array and you can read the contents using the Marshal class. I guess you would copy it to a new byte array.
var arr = new byte[size];
Marshal.Copy(data, arr, 0, size);
Some other points:
The calling conventions do not match. The native side is cdecl and the managed is stdcall.
You'll need to export a deallocator to delete the memory returned by the native function. Consider a re-design where the caller allocates the buffer.
C++ Function header in DLL this two function to get some information about the wifi stations around me using win mobile 6.5 device and i need to invoke them to use them in C# code
// (adapter names , pointer to destination buffer ,and the size , returned structs)
bool __declspec(dllexport) GetBBSIDs(LPWSTR pAdapter, struct BSSIDInfo *pDest, DWORD &dwBufSizeBytes, DWORD &dwReturnedItems);
bool __declspec(dllexport) RefreshBSSIDs(LPWSTR pAdapter);
bool __declspec(dllexport) GetAdapters(LPWSTR pDest, DWORD &dwBufSizeBytes);
C# sample
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetAdapters", SetLastError = true)]
public static extern bool getAdapters([MarshalAs(UnmanagedType.LPWStr)] String buf, ref UInt32 dwBufSizeBytes);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "RefreshBSSIDs", SetLastError = true)]
public static extern bool refreshBSSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf);
[DllImport(#"\Storage Card\Work\Beaad.dll", EntryPoint = "GetBBSIDs", SetLastError = true)]
public static extern bool getBBSIDs([MarshalAs(UnmanagedType.LPWStr)]String buf,BSSIDInfo [] nfo, ref UInt32 dwBufSizeBytes, ref UInt32 dwReturnedItems);
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto)]
public struct BSSIDInfo
{
public byte[] BSSID; //mac
public char[] SSID;
public BSSIDInfo(byte[]bs,char[] ss)
{
this.RSSI = 0;
this.Infastructure = 0;
this.Channel = 0;
this.Auth = 0;
bs = new byte[6];
ss = new char[32];
BSSID = bs;
SSID = ss;
}
public int RSSI;
public int Channel;
public int Infastructure;
public int Auth;
}
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
public static char[] c = new char[1024];
string buf = new string(c);
public void button1_Click(object sender, EventArgs e)
{
BSSIDInfo[] nfo = new BSSIDInfo[128];
byte[] bytee=StrToByteArray(buf);
UInt32 dwsize= new UInt32();
UInt32 dwTmp = new UInt32();
UInt32 dwCount = new UInt32();
dwTmp = Convert.ToUInt32(Marshal.SizeOf(typeof(BSSIDInfo)) * nfo.Length);
dwCount =0;
dwsize=Convert.ToUInt32(bytee.Length);
if (false == getAdapters(buf,ref dwsize) || dwsize == 0)
{
label1.Text = "no adabters";
}
else
{
String [] strList=new String[15];
if (buf.Contains(',') == false)// one adapter
{
textBox1.Text = buf;
}
else
{
strList = buf.Split(',');
for (int i = 0; i < strList.Length; i++)
{
textBox1.Text+= strList[i]+Environment.NewLine;
}
}
if (refreshBSSIDs(buf) && getBBSIDs(buf, nfo, ref dwTmp, ref dwCount) && dwCount > 0)
{
//refreshBSSIDs(buf) &&
for (int i = 0; i < dwCount; i++)
{
textBox2.Text += nfo.GetValue(i).ToString() + Environment.NewLine;
}
}
else
{
//make another thing
}
}
}
and when i put this dll on the mobile and the C# app.exe the first function that named as Getadapters(..) return to me the name of the adapter in the first textbox1 then the app stopped and give me not supported exception when the mobile tries to execute the other two function that named as refreshBSSID() and getBSSIDs() so what is the problem ? or is there another solution to get this information (BSSID ,SS ..etc) ?
C++ by default unless changed uses a caller( Cdecl ) calling convention. Your C++ code does not change the calling convention. Your C# code by default ( unless you change it ) will use a callee convention ( StdCall ).
While this might not be exactly the problem your having it still is technically incorrect. Even if you were to fix your current problem you likely will end up having a problem because of the calling convention.
I am going to guess your C# BSSIDInfo structure does not match the C++ structure. Why do the method StrToByteArray when all it does is GetBytes on the given string...
when the mobile tries to execute the
other two function that named as
refreshBSSID() and getBSSIDs() so what
is the problem ? or is there another
solution to get this information
I thought I knew the reason took another look and I was wrong.