Необходимо помнить о двух вещах: соглашение о вызовах между битом C # и вашей DLL и как данные char * распределяются через этот интерфейс. Если вы ошибетесь, вы получите жалобы на повреждение стека. При определении вашего интерфейса гораздо проще, если вы можете ограничить размер вашего блока данных чем-то фиксированным, т.е. установить максимальную длину строки.
Вот статическая версия, в которой имя DLL является фиксированным, а ваша строка обрабатывается как байт [] и имеет размер не более 2 КБ, и вы можете определить динамическую версию из этого:
private const string MYDLL = @"my.dll";
[DllImport(MYDLL, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern int DataBlockDownload([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);
// NOTE: The data block byte array is fixed at 2Kbyte long!!
public delegate int DDataBlockCallback([MarshalAs(UnmanagedType.U4)] int A, [MarshalAs(UnmanagedType.LPArray, SizeConst = 2048)] byte[] B, [MarshalAs(UnmanagedType.U4)] int C);
Вы также можете указать используемый набор символов, если хотите использовать типы символов, как указано выше.
Вы не говорите, что делаете со своими символами *, если они входят в ваш код C ++ в качестве параметра или если код C ++ передает их обратно в управляемый мир. Прочтите ключевые слова C # ref и out как способы избежать типа char * и модификатора unsafe.
Немного погуглив, вы сможете это понять.