How to get array of strings from DllImport - c#

Im trying to get the array of strings from the dll.
This is the documentation for this dll function.
Request_the_Value_of_ICT_multi_Currency_Bill_Validator()
(a)Input
1. strCurrecy: AnsiString
(b)Return:pData: AnsiString*
pData[0] Value1
pData[1] Value2
...
pData[19] Value3
(c)Example:
AnsiString *pData =
Request_the_Value_of_ICT_multi_Currency_Bill_Validator("AUD");
I've tried this code below and the return is some non-english language
IntPtr pData = Request_the_Value_of_ICT_multi_Currency_Bill_Validator("AUD");
string stringB = Marshal.PtrToStringAnsi(pData);
string stringA = Marshal.PtrToStringUni(pData);
This is the dll
[DllImport("PS3_DLL.dll", EntryPoint = "Request_the_Value_of_ICT_multi_Currency_Bill_Validator", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr Request_the_Value_of_ICT_multi_Currency_Bill_Validator(string strCurrency);
This is the function i got from disassembling the dll. I really got no idea what it means
UPDATE: FINALLY I GOT THE RIGHT CODE!
IntPtr pData = ictdll.Request_the_Country_code_of_ICT_multi_Currency_BA();
IntPtr[] pGetData = new IntPtr[9];
Marshal.Copy(pData, pGetData, 0, pGetData.Length);
string[] currency = new string[9];
for (int i = 0; i < 9; i++)
{
currency[i] = Marshal.PtrToStringAnsi(pGetData[i]);
}

The documentation you provided clearly says Ansi. Also, this looks to be an array of string pointers, so you need to marshal the whole array.
It's unclear who is supposed to free all this memory, you may want to investigate that.
[DllImport("PS3_DLL.dll", CharSet = CharSet.Ansi,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr[] Request_the_Value_of_ICT_multi_Currency_Bill_Validator
(string strCurrency);
var arrPtr = Request_the_Value_of_ICT_multi_Currency_Bill_Validator
(yourCurrency);
arrayOfStrings = arrPtr.Select(ptr =>
Marshal.PtrToStringAnsi(ptr)).ToArray()
I suggest you check the calling convention is correct. Windows API uses StdCall, but most C libraries use CDecl

After browsing for many days i finally got the right code!.
IntPtr pData = ictdll.Request_the_Country_code_of_ICT_multi_Currency_BA();
IntPtr[] pGetData = new IntPtr[9];
Marshal.Copy(pData, pGetData, 0, pGetData.Length);
string[] currency = new string[9];
for (int i = 0; i < 9; i++)
{
currency[i] = Marshal.PtrToStringAnsi(pGetData[i]);
}

Related

C# Sending String Array to C++ DLL as SafeArray. Gets only first character

I have C# project and I have to use a C++ dll using DllImport. (I have source codes of c++ dll)
I'm importing a function from c++ dll like this :
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] ref string[] fields);
I'm using this function in C# like this :
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
string[] fieldsArr = fields.ToArray();
int resultOfSendRequest = SendRequest(ref fieldsArr);
Problem is in c++ dll, it casts string to char* in somewhere and it reads the only first character, not entire string.
How can I solve this without touching c++ dll. (If I call this function from VB6 it works without any problem.)
C++ code is like this :
VARIANT vVar;
__declspec( dllexport ) int _stdcall SendRequest (SAFEARRAY**);
int _stdcall SendRequest ( SAFEARRAY** arrayFlds,
short NFlds)
{
// *********** prepare O.i.d, fields name, values
for (long iElem=0; iElem < NFlds; iElem++)
if (LoadElement (&vVar, iElem, &flds[iElem], &pFlds[iElem],*arrayFlds)==-1)
return -1;
//...
}
int LoadElement( VARIANT* vVar,
long iElem,
S_FLDS* flds,
char** pFld,
SAFEARRAY* arrayFlds)
{
hRes = SafeArrayGetElement(arrayFlds, &iElem, pFld);
strcpy(flds->FieldName, *pFld);
flds->bValLen = 0;
char *Name = flds->FieldName;
//....
}
The fieldName and char *Name at the end of the core only consists of first character of string. Not the full string.
In C Langues a string is a byte[] with each string terminated with a '\0'. An array of strings the last item has two '\0' at the end. So try following :
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest(IntPtr fields);
static void Main(string[] args)
{
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
string fieldsArr = string.Join("\0", fields);
IntPtr fieldsPtr = Marshal.StringToBSTR(fieldsArr);
int results = SendRequest(fieldsPtr);
}
If it is an array of pointers then use this
public struct Pointers
{
public IntPtr[] pointers;
}
[DllImport("Example.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int SendRequest(IntPtr fields);
static void Main(string[] args)
{
List<String> fields = new List<String>();
fields.Add("Test1");
fields.Add("Test2");
List<IntPtr> pointers = new List<IntPtr>();
foreach (string field in fields)
{
IntPtr intPtr = Marshal.StringToBSTR(field);
pointers.Add(intPtr);
}
Pointers sPointers = new Pointers();
sPointers.pointers = pointers.ToArray();
IntPtr fieldsPtr = IntPtr.Zero;
Marshal.StructureToPtr(sPointers, fieldsPtr, true);
int results = SendRequest(fieldsPtr);
Marshal.FreeHGlobal(fieldsPtr);
}

C# pinvoke C char array

Marshalling char array.
The C typedef of the function is:
typedef bool (*get_VariableInfoFunc)(int *, char * *, int *, int *);
It`s usage is like:
pnVariableIdArray = new (nothrow) int[numVars];
pcVariableName = new (nothrow) char*[numVars];
for (int i = 0; i < numVars; i++)
{
pcVariableName[i] = new char[100];
}
pnUnVariableIdArray = new (nothrow) int[numVars];
pnVariableType = new (nothrow) int[numVars];
res = get_VariableInfo(pnVariableIdArray, pcVariableName, pnUnVariableIdArray, pnVariableType);
My C# Signature is:
[DllImport(AUTODYNResultsApiDll, EntryPoint = "get_VariableInfo", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetVariableInfo(
int[] pnVariableIdArray,
IntPtr[] pcVariableName,
int[] pnUnVariableIdArray,
int[] pnVariableType);
I use it like:
var stringPointers= new IntPtr[numVars];
for (int i = 0; i < numVars; i++)
{
by[i] = Marshal.AllocHGlobal(100);
}
GetVariableInfo(pnVariableIdArray, stringPointers, pnUnVariableIdArray, pnVariableType);
and then back to string with:
var strings = new string[numVars];
for (int i = 0; i < numVars; i++)
{
strings[i] = Marshal.PtrToStringAnsi(by[i]);
}
Is there a nicer way doing this without freeing the Memory of the pointer. Just copy the char array as string array?
First of all, a few problems with your code as it stands:
The calling convention is cdecl, you need to include CallingConvention = CallingConvention.Cdecl in your attribute.
You set SetLastError = true, but the unmanaged function surely does not call SetLastError. If that is so, remove SetLastError = true.
You call AllocHGlobal but there is no matching call to FreeHGlobal, and so the memory is leaked.
Your assumption that no string will exceed a length of 100 (including null terminator) seems brittle, and at risk of buffer overflow.
As far as the main body of your question goes, the marshaller won't marshal an array of strings for you. Your current approach, subject to the provisos above, is the correct way to tackle the problem.

Getting byte array from C into C#

I have the following C function that I need to call from C#:
__declspec(dllexport) int receive_message(char* ret_buf, int buffer_size);
I've declared the following on the C# side:
[DllImport("MyCLibrary", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto, EntryPoint = "receive_message")]
public static extern int ReceiveMessage([MarshalAs(UnmanagedType.LPStr)]StringBuilder retBuf, int bufferSize);
I'm calling the function like so:
StringBuilder sb = new StringBuilder();
int len = ReceiveMessage(sb, 512);
This works fine with my initial tests where I was receiving "string" messages. But, now I want to receive packed messages (array of chars/bytes). The problem is that the array of chars/bytes will have 0s and will terminate the string so I don't get back the whole message. Any ideas how I can refactor to get array of bytes back?
With jdweng help, I've changed the declaration to:
[DllImport("MyCLibrary", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto, EntryPoint = "receive_message")]
public static extern int ReceiveMessage(IntPtr retBuf, int bufferSize);
And, I'm allocating and freeing the memory on the C# side along with marshalling the data.
IntPtr pnt = Marshall.AllocHGlobal(512);
try
{
int len = ReceiveMessage(pnt, 512);
...
byte[] bytes = new byte[len];
Marshal.Copy(pnt, bytes, 0, len);
...
}
finally
{
Marshal.FreeHGlobal(pnt);
}

Return array of pointers from c++ to c#

Below I have a code snippet from c++.
I need to return array of pointers (to TempStruct).
The problem is that on c# side I get only one element. On the other I get AV.
**C++**
extern "C" __declspec(dllexport) void GetResult(TempStruct** outPtr, long *size)
{
*outPtr = (TempStruct*)new TempStruct*[2];
outPtr[0] = new TempStruct("sdf", 123);
outPtr[1] = new TempStruct("abc", 456);
*size = 2;
}
**C#**
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void GetResult(out IntPtr outPtr, out int size);
IntPtr ptr = IntPtr.Zero;
int length;
GetResult(out ptr, out length);
int size = Marshal.SizeOf(typeof(TempStruct));
TempStruct[] someData2 = new TempStruct[length];
for (int i = 0; i < length; i++)
{
IntPtr wskptr = (IntPtr)(ptr.ToInt64() + (size * i));
someData2[i] = (TempStruct)Marshal.PtrToStructure(wskptr, typeof(TempStruct));
}
You are doing it wrong.
You are mixing pointer types.
By using the new TempStruct() you are creating an array of pointers to TempStruct. The example I gave you created an array of TempStruct. See the difference?
Now... TempStruct** outPtr should be TempStruct*** outPtr (because you want to return (*) an array (*) of pointers (*)... Or TempStruct**& if you prefer :-)
Change this line
someData2[i] = (TempStruct)Marshal.PtrToStructure(Marshal.ReadIntPtr(wskptr), typeof(TempStruct));
Because you must read the single pointers.
I do hope you are deleting the various TempStruct with delete and using the
delete[] ptr;
operator to delete the array of structures.
Full example:
C++:
struct TempStruct
{
char* str;
int num;
// Note the strdup. You don't know the source of str.
// For example if the source is "Foo", then you can't free it.
// Using strdup solves this problem.
TempStruct(const char *str, int num)
: str(strdup(str)), num(num)
{
}
~TempStruct()
{
free(str);
}
};
extern "C"
{
__declspec(dllexport) void GetResult(TempStruct ***outPtr, int *size)
{
*outPtr = new TempStruct*[2];
(*outPtr)[0] = new TempStruct("sdf", 123);
(*outPtr)[1] = new TempStruct("abc", 456);
*size = 2;
}
__declspec(dllexport) void FreeSomeData(TempStruct **ptr, int size)
{
for (int i = 0; i < size; i++)
{
delete ptr[i];
}
delete[] ptr;
}
}
C#:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1), Serializable]
internal struct TempStruct
{
public string str;
public int num;
}
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void GetResult(out IntPtr outPtr, out int numPtr);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeSomeData(IntPtr ptr, int num);
// C++ will return its TempStruct array in ptr
IntPtr ptr;
int size;
GetResult(out ptr, out size);
TempStruct[] someData2 = new TempStruct[size];
for (int i = 0; i < size; i++)
{
IntPtr ptr2 = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
someData2[i] = (TempStruct)Marshal.PtrToStructure(ptr2, typeof(TempStruct));
}
// Important! We free the TempStruct allocated by C++. We let the
// C++ do it, because it knows how to do it.
FreeSomeData(ptr, size);
Note that you don't need [Serializable] and Pack=1 on the C# struct
More correct for the C++:
__declspec(dllexport) void GetResult(TempStruct **&outPtr, int &size)
{
outPtr = new TempStruct*[2];
outPtr[0] = new TempStruct("sdf", 123);
outPtr[1] = new TempStruct("abc", 456);
size = 2;
}
It is more correct because both outPtr and size can't be NULL. See https://stackoverflow.com/a/620634/613130 . The C# signature is the same.
The C++ code is wrong. It's returning an array of pointers to struct. The fact that you cast the value returned by new should have alerted you to the fact that you made a mistake. You want to return an array of struct.
It should be:
*outPtr = new TempStruct[2];
(*outPtr)[0].str = "sdf";
(*outPtr)[0].i = 123;
(*outPtr)[1].str = "abc";
(*outPtr)[1].i = 456;
*size = 2;

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.

Categories