I have string with the following format:
"arg1" "arg2" "arg3" ... "argx"
I am going to use this string as the string[] for my program's command-line arguments.
How can I turn this string into a string array?
It's not easy to implement all the escaping by yourself, especially in the way the CLR does for you.
So, you'd better look at CLR sources. It mentions CommandLineToArgvW api which has a nice documentation.
But we're C# guys and must search this function signature here. Luckily, it has a good sample (my styling):
internal static class CmdLineToArgvW
{
public static string[] SplitArgs(string unsplitArgumentLine)
{
int numberOfArgs;
var ptrToSplitArgs = CommandLineToArgvW(unsplitArgumentLine, out numberOfArgs);
// CommandLineToArgvW returns NULL upon failure.
if (ptrToSplitArgs == IntPtr.Zero)
throw new ArgumentException("Unable to split argument.", new Win32Exception());
// Make sure the memory ptrToSplitArgs to is freed, even upon failure.
try
{
var splitArgs = new string[numberOfArgs];
// ptrToSplitArgs is an array of pointers to null terminated Unicode strings.
// Copy each of these strings into our split argument array.
for (var i = 0; i < numberOfArgs; i++)
splitArgs[i] = Marshal.PtrToStringUni(
Marshal.ReadIntPtr(ptrToSplitArgs, i * IntPtr.Size));
return splitArgs;
}
finally
{
// Free memory obtained by CommandLineToArgW.
LocalFree(ptrToSplitArgs);
}
}
[DllImport("shell32.dll", SetLastError = true)]
private static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
out int pNumArgs);
[DllImport("kernel32.dll")]
private static extern IntPtr LocalFree(IntPtr hMem);
}
PS. Note, that executable name should be the first argument in the line.
Use the String.Split method to split the string on the original string.
if you need to remove the Quotation marks as well you could either loop through the resulting array and getting the substrings without the quotation marks
Alternatively you could use the Regex.Split to do it in one go.
Related
I need some help with the following. I've got a c++ API (no access to source) and I'm struggling with the methods returning char* attributes, or returned structures containing char* attributes. According to the API's documentation the return value is as follows:
Return Values
If the function succeeds, the return value is a pointer to a series of null-terminated strings, one for each project on the host system, ending with a second null character. The following example shows the buffer contents with <null> representing the terminating null character:
project1<null>project2<null>project3<null><null>
If the function fails, the return value is NULL
The problem I'm having is that the returned pointer in C# only contains the first value... project1 in this case. How can I get the full list to be able to loop through them on the managed side?
Here's the c# code:
[DllImport("vmdsapi.dll", EntryPoint = "DSGetProjectList", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr DSGetProjectList();
Calling method:
IntPtr ptrProjectList = DSAPI.DSGetProjectList();
string strProjectList = Marshal.PtrToStringAnsi(ptrProjectList).ToString();
strProjectList only contains the first item.
Here's the info from the API's header file...
DllImport char *DSGetProjectList dsproto((void));
Here's some sample code from a c++ console app which I've used for testing purposes...
char *a;
a = DSGetProjectList( );
while( *a ) {
printf("a=%s\n", a);
a += 1 + strlen(a);
}
Each iteration correctly displays every project in the list.
The problem is that when converting the C++ char* to a C# string using Marshal.PtrToStringAnsi, it stops at the first null character.
You shouldn't convert directly the char* to a string.
You could copy the char* represented by an IntPtr to a byte[] using Marshal.Copy and then extract as many string as necessary (see Matthew Watson's answer for extracting strings from a managed array), but you'll need to get the multi-string size first.
As leppie suggest you can also extract the first string using Marshal.PtrToStringAnsi then increment the pointer by this string size and extract the next string and so on. You stops when is extracts an empty string (from the last NULL character).
Something like :
IntPtr ptrProjectList = DSAPI.DSGetProjectList();
List<string> data;
string buffer;
do {
buffer = Marshal.PtrToStringAnsi(ptrProjectList);
ptrProjectList += buffer.size() + 1;
data.Add(buffer);
}while(buffer.size() > 0)
This kind of string is called a Multi-String, and it's quite common in the Windows API.
Marshaling them is fiddly. What you have to do is marshal it as a char[] array, rather than a string, and then convert the char[] array to a set of strings.
See here for an example solution. I've copied the relevant code into this answer, but it is copied from the link I gave:
static List<string> MultiStringToList(char[] multistring)
{
var stringList = new List<string>();
int i = 0;
while (i < multistring.Length)
{
int j = i;
if (multistring[j++] == '\0')
break;
while (j < multistring.Length)
{
if (multistring[j++] == '\0')
{
stringList.Add(new string(multistring, i, j - i - 1));
i = j;
break;
}
}
}
return stringList;
}
I'm trying to pass string from c++ to c#.
C++:
extern "C" __declspec(dllexport) void GetSurfaceName(wchar_t* o_name);
void GetSurfaceName(wchar_t* o_name)
{
swprintf(o_name, 20, L"Surface name");
}
C#:
[DllImport("SurfaceReader.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void GetSurfaceName(StringBuilder o_name);
StringBuilder name = new StringBuilder(20);
GetSurfaceName(name);
But only first symbol is passed: name[0] == 'S'. Other symbols are nulls. Could you tell me what is wrong here?
Thanks,
Zhenya
You forgot to tell the pinvoke marshaller that the function is using a Unicode string. The default is CharSet.Ansi, you'll need to use CharSet.Unicode explicitly. Fix:
[DllImport("SurfaceReader.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Unicode)]
private static extern void GetSurfaceName(StringBuilder o_name);
You'll get a single "S" now because the utf-16 encoded value for "S" looks like a C string with one character.
Do in general avoid magic numbers like "20". Just add an argument that say how long the string buffer is. That way you'll never corrupt the GC heap by accident. Pass the StringBuilder.Capacity. And give the function a return value that can indicate success so you also won't ignore a buffer that's too small.
I am not sure, but instead of using StringBuilder, I would pass from C# char (wchar) array to C++, fill it and then operate with this array in C#.
The function in C DLL looks like this:
int my_Funct(char* input, char* output);
I must call this from C# app. I do this in the following way:
...DllImport stuff...
public static extern int my_Funct(string input, string output);
The input string is perfectly transmitted to the DLL (I have visible proof of that). The output that the function fills out although is wrong. I have hexa data in it, like:
3F-D9-00-01
But unfortunately everything that is after the two zeros is cut, and only the first two bytes come to my C# app. It happens, because (I guess) it treats as null character and takes it as the end of the string.
Any idea how could I get rid of it? I tried to specifiy it as out IntPtr instead of a string, but I don't know what to do with it afterwards.
I tried to do after:
byte[] b1 = new byte[2];
Marshal.Copy(output,b1,0,2);
2 should be normally the length of the byte array. But I get all kind of errors: like "Requested range extends past the end of the array." or "Attempted to read or write protected memory..."
I appreciate any help.
Your marshalling of the output string is incorrect. Using string in the p/invoke declaration is appropriate when passing data from managed to native. But you cannot use that when the data flows in the other direction. Instead you need to use StringBuilder. Like this:
[DllImport(...)]
public static extern int my_Funct(string input, StringBuilder output);
Then allocate the memory for output:
StringBuilder output = new StringBuilder(256);
//256 is the capacity in characters - only you know how large a buffer is needed
And then you can call the function.
int retval = my_Funct(inputStr, output);
string outputStr = output.ToString();
On the other hand, if these parameters have null characters in them then you cannot marshal as string. That's because the marshaller won't marshal anything past the null. Instead you need to marshal it as a byte array.
public static extern int my_Funct(
[In] byte[] input,
[Out] byte[] output
);
That matches your C declaration.
Then assuming the ANSI encoding you convert the input string to a byte array like this:
byte[] input = Encoding.Default.GetBytes(inputString);
If you want to use a different encoding, it's obvious how to do so.
And for the output you do need to allocate the array. Assuming it's the same length as the input you would do this:
byte[] output = new byte[input.Length];
And somehow your C function has got to know the length of the arrays. I'll leave that bit to you!
Then you can call the function
int retval = my_Funct(input, output);
And then to convert the output array back to a C# string you use the Encoding class again.
string outputString = Encoding.Default.GetString(output);
I have a problem with calling a C DLL fom C#
The C function is (I don't have a c header or a good spec for this :( )
int knr12_read ( char *kn12, char *ik9, char *wok, char *wlc,
char *plz, char *ort, char *woz );
kn12 is a ref parameter
This is what I've tried in C#
[return: MarshalAs(UnmanagedType.U4)]
[DllImport("Knr12.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "knr12_read", CharSet = CharSet.Ansi)]
unsafe public static extern int knr12_read(out IntPtr buffer, string ik9, string wok, string wlc, string plz, string ort, string woz);
int knr = knr12_read(out pBuffer, knrTemp, "11111", "", "98529", "Suhl", "1");
string data = Marshal.PtrToStringAnsi(pBuffer);
The returning int is always right, how it should be, but I have problems with the ref parameter pBuffer...
Also the sting type for the other variables is working...
When I use a ref,I always get an AccessViolation error knr12_read().In case I use out I get a pointer,but the String is always empty which can't be.I even tried out String as ref for char* but I get an AccessViolation error on knr12_read().
Please guide.
StringBuilder is often a good type to use when P/Invoking to functions with string returning parameters:
static extern int knr12_read(StringBuilder kn12, ...)
You'll need to initialise the string builder before you call the function, something like:
StringBuilder outString = new StringBuilder(100);
You shouldn't need the 'unsafe', and unless the 'C' code holds onto the pointers for longer than the duration of the call, you shouldn't need to worry about pinning - the framework is doing that for you.
Here's a SO question which should help: Marshal "char *" in C#
Probably you have not pinned the buffer. here is the example of how to pin the buffer data.
GCHandle pinnedRawData = GCHandle.Alloc(rawData,
GCHandleType.Pinned);
Pinning the object makes sure that the pointer is valid cause .Net runtime can always reallocate the memory as and when it thinks fit.
Try it out and let me know if it helps you.
I'm trying to use kernel32.dll's lstrcpy to get a string from a pointer in C#, but it isn't working. lstrlenA IS working, it gives me the length of the string, so I'm hitting the kernel32.dll at least. lstrcpy is working in the VB6 app I'm converting, so I know it CAN work, but I don't have a clue why it isn't here.
The string s never gets filled with the actual string, it just returns the initial padded string.
[DllImport("kernel32.dll", EntryPoint = "lstrlenA", CharSet = CharSet.Ansi)]
private static extern int lstrlen( int StringPointer );
[DllImport( "kernel32.dll",EntryPoint = "lstrcpyA", CharSet = CharSet.Ansi )]
private static extern int lstrcpy(string lpString1, int StringPointer );
private static string StringFromPointer(int pointer)
{
//.....Get the length of the LPSTR
int strLen = lstrlen(pointer);
//.....Allocate the NewString to the right size
string s = "";
for (int i = 0; i < strLen; i++)
s += " ";
//.....Copy the LPSTR to the VB string
lstrcpy(s, pointer);
return s;
}
I suspect that it might be something to do with managed strings being immutable, so that whenever you think you're changing it, you're actually creating a new string and change the reference to look at the new string instead.
I'm not sure how that works when you use windows API functions, but it's possible that during the call to lstrcpy a new string is created containing the text that the pointer points to, but because lstrcpy might not be aware of System.String, it doesn't handle it properly and so it doesn't change s to reference the new string.
I think that what you want to use is a Text.StringBuilder since that's not immutable.