Pass char array value from C++ to C# app - c#

I have a huge C++ structure with a lot of validation code in C++, that I want to import in a C# project. I'm able to transfer all value except the CHAR* and CHAR[].
With a CHAR*, my string is full of chiness caracter BUT, if i go look in memory, my string is there, i can see "This is a test #1".
With a CHAR[x], I can see only the 1rst char, same in memory.
In the following test, i can extract the integer : value1 = data.Value1;
and value 1 is 123, but the the CHAR.
Question: What i miss, why i can't get the value with a char array.
Thank you
The C++ DLL
//This is the main DLL file.
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
extern "C"
{
public struct Data
{
int Value1;
char* pValue2;
char Value3[1024];
};
typedef int( *FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER) (struct Data* data);
FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER _pInvokeCallback;
int __declspec(dllexport) DLLTestCPlusPlus_Initialize()
{
return 0;
}
int __declspec(dllexport) DLLTestCPlusPlus_RegisterDllInvokeProcessCallback(void* fnInvokeCaller)
{
_pInvokeCallback = (FN_CCP_INVOKE_NEW_BOARD_OPTIMIZER)fnInvokeCaller;
struct Data data;
// INT
data.Value1 = 123;
// CHAR*
data.pValue2 = new char[1024];
sprintf(data.pValue2, "This is a test #1");
// CHAR [1024]
sprintf(data.Value3, "This is a test #2");
if (_pInvokeCallback)
{
_pInvokeCallback(&data);
}
return 0;
}
}
And here's the C# program that import the DLL.
using System;
using System.Runtime.InteropServices;
public unsafe struct Data
{
public int Value1;
public char* pValue2;
public fixed char Value3[1024];
}
public static class Interop
{
public delegate Int32 Callback([MarshalAs(UnmanagedType.Struct)] Data data);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetDllDirectory(string lpPathName);
[DllImport("C:\\DATA\\CODE\\ApplicationTestCSharp\\x64\\Debug\\DLLTestCPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 DLLTestCPlusPlus_Initialize();
[DllImport("C:\\DATA\\CODE\\ApplicationTestCSharp\\x64\\Debug\\DLLTestCPlusPlus.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 DLLTestCPlusPlus_RegisterDllInvokeProcessCallback([MarshalAs(UnmanagedType.FunctionPtr)] Callback handler);
}
public class MyTest
{
private Interop.Callback _callback = null;
public MyTest()
{
int returnCode = 0;
returnCode = Interop.DLLTestCPlusPlus_Initialize();
_callback = new Interop.Callback(CallbackHandler);
returnCode = Interop.DLLTestCPlusPlus_RegisterDllInvokeProcessCallback(_callback);
}
private Int32 CallbackHandler(Data data)
{
int value1 = 0;
string value2 = "";
string value3 = "";
unsafe
{
// INT
value1 = data.Value1;
// CHAR* - MUST BE "This is a test #1"
value2 = new string(data.pValue2);
// CHAR [1024] - "This is a test #2"
value3 = new string(data.Value3);
}
return 1;
}
}
class Program
{
static void Main(string[] args)
{
MyTest myTest = new MyTest();
}
}

Ok i found it, i made that change in the structure declaration
/*
public char* pValue2;
public fixed char Value3[1024];
*/
[MarshalAs(UnmanagedType.LPStr)] public String pValue2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] public String pValue3;
And pull the data like that!
// CHAR* - "This is a test #1";
// value2 = new string(data.pValue2);
value2 = data.pValue2
// CHAR [1024] - "This is a test #2"
//value3 = new string(data.Value3);
value3 = data.pValue3;

Related

Pass C# string to C++ pointer Char in typedef

I need pass a string of IP "192.168.1.1" from C# code to the typedef char pointer in DLL which written by c++. and I declare the char *pcAddrs like
char *pcAddrs; //c++
[MarshalAs(UnmanagedType.LPStr)] public string ip //C#
and declare open function
//c++
int Open( COMMIF_INFO *pInfo )
//c#
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
Now i get an error of wrong parameter when press button1 to trigger the Open function. It is the string and char pointer wrong?
Thanks in advance.
C++ DLL info
typedef struct CommIfInfo
{
char *pcAddrs;
long lPortNo;
long lRetry;
long lSendTimeOut;
long lCommSide;
long lMode;
long lKind;
} COMMIF_INFO;
//Function need to call.
int Open( COMMIF_INFO *pInfo )
Code in C#
// DLL import
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
// Structure
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct COMMIFINFO
{
[MarshalAs(UnmanagedType.LPStr)] public string ip;
public int PortNo;
public int Retry;
public int SendTimeOut;
public int CommSide;
public int Mode;
public int Kind;
}
private void button1_Click(object sender, EventArgs e)
{
string _ip = "192.168.1.1";
COMMIFINFO info = new COMMIFINF();
info.ip = _ip;
info.Kind = 1;
int ErrCode = Open(info);
}
Program will get wrong parameter error if passing the COMMIFINFO using method below
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(COMMIFINFO info);
Found is because the parameter pass to open function need by reference. So program working if use "in" to pass parameter with reference.
[DllImport("Open-IF.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Open(in COMMIFINFO info);

C struct and use C dll in C#

everyone, I'm newbie in C#, and I have a problem with using "C" dll in C#, below is the C side code which work fine.
// myCode.h
typedef struct _SubABC
{
unsigned short WordCount;
unsigned char *WordData;
unsigned char SpeedUp;
} SubABC;
typedef struct _ABC
{
unsigned char SubFontNum;
SubABC subABC[5];
} ABC
extern __declspec(dllimport) int __stdcall MY_SetSth(unsigned char SerialNum, ABC *pABC);
//myCode.c
ABC myFun = { '\0' };
unsigned char text_c[] =
{
0x00,0x27,0xff,0xA5,0xC3, 0x00,0x27,0xff,0xA6,0x26, 0x00,0x23,0xff,0xA7,0xAE, 0x00,0x27,0xff,0xBA,0x7E,
0x00,0x27,0xff,0xC1,0x52, 0x00,0x27,0xff,0xAC,0xF9, 0x00,0x27,0xff,0x20,0x00,0x27,0xff,0x31, 0x00,0x27,0xff,0x20, 0x00,0x27,0xff,0xA4,0xC0, 0x00,0x27,0xff,0xC4,0xC1,
};
myFun.SubFontNum = 1;
myFun.subMABC[0].WordCount = sizeof(text_c);
myFun.subMABC[0].WordData = text_c;
myFun.subMABC[0].SpeedUp = 0;
int retvalue = MY_SetSth(1, &myFun);
and my C# code is below
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SubABC
{
public ushort WordCount;
[MarshalAs(UnmanagedType.LPArray)]
public byte[] WordData;
public byte SpeedUp;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ABC
{
public byte SubFontNum;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public SubABC[] subABC;
}
[DllImport("myDLL.dll", CharSet = CharSet.Ansi)]
public extern static int MY_SetSth(byte SerialNum, ref ABC pABC);
ABC myFun = new ABC();
ABC.subABC = new SubABC[5];
unsigned char text_c[] =
{
0x00,0x27,0xff,0xA5,0xC3, 0x00,0x27,0xff,0xA6,0x26, 0x00,0x23,0xff,0xA7,0xAE, 0x00,0x27,0xff,0xBA,0x7E,
0x00,0x27,0xff,0xC1,0x52, 0x00,0x27,0xff,0xAC,0xF9, 0x00,0x27,0xff,0x20,0x00,0x27,0xff,0x31, 0x00,0x27,0xff,0x20, 0x00,0x27,0xff,0xA4,0xC0, 0x00,0x27,0xff,0xC4,0xC1,
};
myFun.SubFontNum = 1;
myFun.subMABC[0].WordCount = (ushort)text_c.Length;
myFun.subMABC[0].WordData = text_c;
myFun.subMABC[0].SpeedUp = 0;
int retvalue = MY_SetSth(1, ref myFun);
I always get TypeLoadException: Cannot marshal field 'WordData' of type 'subABC': Invalid managed/unmanaged type combination (Array fields must be paired with ByValArray or SafeArray), because WordData is unknown size, I can't use ByValArray, and SafeArray get AccessViolationException: Attempted to read or write protected memory
I try to use IntPtr like below
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SubABC
{
public ushort WordCount;
public IntPtr WordData;
public byte SpeedUp;
}
ABC.subABC[0].WordData = Marshal.AllocHGlobal(text_c.Length); ;
Marshal.Copy(text_c, 0, ABC.subABC[0].WordData, text_c.Length);
int retvalue = MY_SetSth(1, ref myFun);
I get error message AccessViolationException: Attempted to read or write protected memory
Can anyone help me? thank a lot.

How to assign value to a char array (C-String) within C++ DLL through C# code

I have a DLL developed in C++ that performs some computations. I am trying to link this DLL into my C# application.
I have a struct within a struct which contains a char array (C string). I have a pointer to the object created by the C++ DLL within my C# application.
namespace Test
[StructLayout(LayoutKind.Sequential)]
public unsafe struct Child
{
public float number1;
public float number2;
[MarshalAs(UnmanagedType.LPArray)]
public char[] name;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct DataStructure
{
public Child child;
...
}
.
.
.
public unsafe partial class Form1:Form {
[DllImport("Calculation.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern IntPtr createInstance();
[DllImport("Calculation.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern DataStructure* processData(IntPtr source); // Setup of data
[DllImport("Calculation.dll", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int calculate(IntPtr calc); // Perform calculation
.
.
public Form2()
{
private void button1_Click_1(object sender, EventArgs e)
{
// test functions
unsafe
{
IntPtr ptr1 = createInstance(); // Returns pointer to the instance
DataStructure* data = processData(ptr1); // Returns pointer to DataStructure object created through DllImport
data->child.number1 = 1.234F; // Works
data->child.number1 = 9.876F; // Works
data->child.name = "......" // This doesn't work!!
int result = calculate(ptr1); // Returns value when name commented
}
}
}
}
If I don't comment name, I get the Error - Cannot take the address of managed object.
If I comment name, I am able to run the calculation without issue.
I wish to update the value of the char array name but can't seem to figure out the solution. Below is my usual C solution. Thanks!
#include "DataStructure.h"
#define DLLNAME "Calculation.dll"
int main(int argc, char** argv) {
HINSTANCE dllHandle = LoadLibraryA(fileNameDll);
const void* ptr1 = 0;
int result = 0;
DataStructure *data;
ptr1 = createInstance();
data = processData(ptr1);
data->child.number1 = 1.234;
data->child.number2 = 9.876;
sprintf(data->child.name, "3104-03");
result = calculate(ptr1);
}

invoking unmanaged DLL and returning string

I am attempting to invoke an unmanaged DLL and use what it returns for authentication. Could someone help me correct my code to return the correct authentication strings?
I am confused about the struct's, any assistance would be appreciated.
this is my C# code invoking the library
class Wrapper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
public struct ARSSOUserCredentialsStruct
{
/// int
public int m_nARTCPNum;
public int m_nARRPCNum;
/// string*
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public static string m_szARUserName;
public string m_szARPassword;
public string m_szARAuthenticationstring;
public int m_nARNumberOfServers;
//bool
public bool m_bARUsingPreferenceServer;
}
public struct ARSSOServerInformation
{
/// int
public int m_nARTCPNum;
public int m_nARRPCNum;
/// string*
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr)]
public string m_szARServerName;
}
[System.Runtime.InteropServices.DllImportAttribute("ARSSOInfo.dll", EntryPoint = "ARGetSSOLoginCrendentials")]
public static extern System.IntPtr ARGetSSOLoginCrendentials(string m_szARUserName);
public static IntPtr getInfo(string m_szARUserName)
{
IntPtr ptr = ARGetSSOLoginCrendentials(m_szARUserName); // should this be IntPtr?
//return (ARSSOUserCredentialsStruct)(Marshal.PtrToStructure(ptr, typeof(ARSSOUserCredentialsStruct))); // what should type >
// var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(m_szARUserName));
if(ptr != null)
{
Marshal.StructureToPtr(m_szARUserName, ptr, false);
Console.WriteLine("Not null");
Console.WriteLine();
}
else
{
Console.WriteLine("null");
Console.ReadLine();
}
return ptr;
}
class Program
{
static void Main(string[] args)
{
getInfo("jp");
}
}
}
unmanaged dll code which is being called by C# app:
#include <string.h>
#include <stdafx.h>
struct ARSSOServerInformation
{
char * m_szARServerName;
int m_nARTCPNum;
int m_nARRPCNum;
};
struct ARSSOUserCredentialsStruct
{
char* m_szARUserName;
char* m_szARPassword;
char* m_szARAuthenticationString;
bool m_bARUsingPreferenceServer;
int m_nARNumberOfServers;
};
extern "C"
{
__declspec(dllexport) void
ARGetSSOLoginCrendentials(ARSSOUserCredentialsStruct
*pUserCredentialStruct)
{
// The required memory for struct ARSSOUserCredentialsStruct is allocated by user tool,
// This dll just needs to assign values.
// eg:
strcpy(pUserCredentialStruct->m_szARUserName, "Demo");
pUserCredentialStruct->m_nARNumberOfServers = 2;
}
}//End 'extern "C"
extern "C"
{
__declspec(dllexport) void
ARGetSSOServerInformation(ARSSOServerInformation *pServerInfo)
{
// The required memory for struct ARSSOServerInformation is allocated by user tool,
// This dll just needs to assign values.
// eg:
strcpy(pServerInfo[0].m_szARServerName, "ServerName1");
pServerInfo->m_nARTCPNum = 3040; pServerInfo->m_nARRPCNum
= 390622;
strcpy(pServerInfo[1].m_szARServerName, "ServerName2");
}
}//End 'extern "C"

Passing C# structure with string array to c++ function which accepts void * for c# structure and char** for c# string array

I want to send a C# structure with string array to a C++ function which accepts void * for the c# structure and char** for the c# structure string array member.
I was able to send the structure to c++ function,but the issue is , not able to access the string array data member of c# structure from c++ function. When sending the string array separately,i was able to access the array elements.
Sample code is-
C# Code:
[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
public int TestId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public String[] Parameters;
}
[DllImport("TestAPI.dll", CallingConvention = CallingConvention.StdCall, EntryPoint "TestAPI")]
private static extern void TestAPI(ref TestInfo data);
static unsafe void Main(string[] args)
{
TestInfo testinfoObj = new TestInfo();
testinfoObj.TestId = 1;
List<string> names = new List<string>();
names.Add("first");
names.Add("second");
names.Add("third");
testinfoObj.Parameters=names.ToArray();
TestAPI(ref testinfoObj);
}
VC++ Code:
/*Structure with details for TestInfo*/
typedef struct TestInfo
{
int TestId;
char **Parameters;
}TestInfo_t;
//c++ function
__declspec(dllexport) int TestAPI(void *data)
{
TestInfo *cmd_data_ptr= NULL;
cmd_data_ptr = (TestInfo) data;
printf("ID is %d \r\n",cmd_data_ptr->TestId);//Working fine
for(i = 0; i < 3; i++)
printf("value: %s \r\n",((char *)cmd_data_ptr->Parameters)[i]);/*Error-Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt*/
}
When analyzing the memory stack, it is observed that,when i print
((char *)cmd_data_ptr->Parameters), the first array element("first") is getting printed,
but using ((char *)cmd_data_ptr->Parameters)[i], not able access elements and above mentioned exception is coming.
The structure memory address contains address of all the structure elements,but while accessing the data from c++,it is accessing only the first element of the string array.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public String[] Parameters;
is an inline array. The C++ declaration that matches is:
char* Parameters[2];
But you are trying to match it to:
char** Parameters;
and that's completely different.
You will need to marshal this by hand. In the C# struct declare Parameters to be IntPtr. Then allocate native memory with Marshal.AllocHGlobal to hold an array of pointers. And then populate those pointers with pointers to your strings.
[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
public int TestId;
public IntPtr Parameters;
}
static void Main(string[] args) // no need for unsafe
{
TestInfo testInfo;
testInfo.TestId = 1;
testInfo.Parameters = Marshal.AllocHGlobal(2*Marshal.SizeOf(typeof(IntPtr)));
IntPtr ptr = testInfo.Parameters;
Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("foo"));
ptr += Marshal.SizeOf(typeof(IntPtr));
Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("bar"));
TestAPI(ref testinfoObj);
// now you want to call FreeHGlobal, I'll leave that code to you
}
An alternative would be to use a pinned IntPtr[] and put that in testInfo.Parameters.
This is really more of an expansion/extension to David's answer, but here's one way to wrap up the custom marshalling:
public struct LocalTestInfo
{
public int TestId;
public IEnumerable<string> Parameters;
public static explicit operator TestInfo(LocalTestInfo info)
{
var marshalled = new TestInfo
{
TestId = info.TestId,
};
var paramsArray = info.Parameters
.Select(Marshal.StringToHGlobalAnsi)
.ToArray();
marshalled.pinnedHandle = GCHandle.Alloc(
paramsArray,
GCHandleType.Pinned);
marshalled.Parameters =
marshalled.pinnedHandle.AddrOfPinnedObject();
return marshalled;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct TestInfo : IDisposable
{
public int TestId;
public IntPtr Parameters;
[NonSerialized]
public GCHandle pinnedHandle;
public void Dispose()
{
if (pinnedHandle.IsAllocated)
{
Console.WriteLine("Freeing pinned handle");
var paramsArray = (IntPtr[])this.pinnedHandle.Target;
foreach (IntPtr ptr in paramsArray)
{
Console.WriteLine("Freeing # " + ptr);
Marshal.FreeHGlobal(ptr);
}
pinnedHandle.Free();
}
}
}
Note for my test I swapped over to CDecl:
[DllImport(#"Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestAPI(ref TestInfo info);
Also I think you had a typo in the C++ side:
extern "C"
__declspec(dllexport) int TestAPI(void *data)
{
TestInfo *cmd_data_ptr= NULL;
cmd_data_ptr = (TestInfo*) data;
printf("ID is %d \r\n",cmd_data_ptr->TestId);
// char**, not char*
char** paramsArray = ((char **)cmd_data_ptr->Parameters);
for(int i = 0; i < 3; i++)
{
printf("value: %s \r\n",paramsArray[i]);
}
return 0;
}
And a test rig:
static void Main(string[] args)
{
var localInfo = new LocalTestInfo()
{
TestId = 1,
Parameters = new[]
{
"Foo",
"Bar",
"Baz"
}
};
TestInfo forMarshalling;
using (forMarshalling = (TestInfo)localInfo)
{
TestAPI(ref forMarshalling);
}
}
The reverse marshalling operator is left as an exercise to the reader, but should basically look like the inverse of the explicit TestInfo operator.

Categories

Resources