Return string from unmanaged dll to C# - c#

I'm sorry to ask this here since I'm sure it must be answered "out there", but I've been stuck on this for several months now, and none of the solutions I've found have worked for me.
I have the following VB code that works:
Declare Function DeviceSendRead Lib "unmanaged.dll" (ByVal sCommand As String, ByVal sReply As String, ByVal sError As String, ByVal Timeout As Double) As Integer
Dim err As Integer
Dim outstr As String
Dim readstr As String
Dim errstr As String
outstr = txtSend.Text
readstr = Space(4000)
errstr = Space(100)
Timeout = 10
err = DeviceSendRead(outstr, readstr, errstr, Timeout)
and I am trying to implement it in a C# project. The best equivalent I have been able to find is:
[DllImport("unmanaged.dll")] public static extern int DeviceSendRead(String outstr, StringBuilder readstr, StringBuilder errstr, double Timeout);
int err;
StringBuilder readstr = new StringBuilder(4000);
StringBuilder errstr = new StringBuilder(100);
err = DeviceSendRead(txtSend.Text, readstr, errstr, 10);
However, when I run this, the application freezes and I must force quit it. By experimenting with ref and out, I have occasionally managed to make it crash rather than freeze, but the only "progress" I have achieved is to replace the dll function call with:
DeviceSendRead(txtSend.Text, null, null, 10);
This prevents the crash, but of course does nothing (that I can detect). I'm therefore assuming that it's the manner of passing the two return string parameters that is causing the problem. If anyone can suggest what I might be doing wrong, I'd be very happy to hear it. Thanks.

I have reached an answer, which I will record here for completeness, with grateful thanks to all those who pointed me in the right direction.
According to this post elsewhere, the use of .NET Reflector on similar VB code suggests the need to use the string type in place of my StringBuilder, as suggested here by Alex Mendez, JamieSee and Austin Salonen, together with explicit marshaling, as suggested by Nanhydrin, but utilising the unmanaged type VBByRefStr rather than AnsiBStr. The final key to the puzzle is that the string parameter then needs to be passed by reference using the ref keyword.
I can confirm that this works, and that my final working C# code is therefore:
[DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
public static extern short DeviceSendRead(
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sCommand,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sReply,
[MarshalAs(UnmanagedType.VBByRefStr)] ref string sError,
double Timeout);
short err;
string outstr = txtSend.Text;
string readstr = new string(' ', 4000);
string errstr = new string(' ', 100);
err = DeviceSendRead(ref outstr, ref readstr, ref errstr, 10);
I hope this is useful to others facing a similar issue.

Try this:
[DllImport("unmanaged.dll")]
public static extern int DeviceSendRead(string outString, string readString, string errorString, double timeout);
int err;
string outstr;
string readstr;
string errstr =
outstr = txtSend.Text;
readstr = new string(' ', 4000);
errstr = new string(' ', 100);
double timeout = 10;
err = DeviceSendRead(outstr, readstr, errstr, timeout);

Try this as an equivalent:
string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);

Default Marshalling for strings
Default Marshalling behaviour
You may need to be more specific in your dllimport declaration and add in some MarshalAs attributes, if you have more details on what type of strings the called function is expecting (Ansi, Unicode, null terminated, etc.) then that would help.
In fact it expecting null terminated strings could perhaps explain why it's hanging rather than erroring out.
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outString, [MarshalAs(UnmanagedType.AnsiBStr)]string readString, string errorString, double timeout);
You might also need to explicitly state that your parameters are input, output, or both by using the parameter attributes [In, Out].

[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);
You cannot marshal a StringBuilder here. There are some rules to follow for marshalling StringBuilder (see CLR Inside Out: Marshaling between Managed and Unmanaged Code):
StringBuilder and Marshaling
The CLR marshaler has built-in knowledge of the StringBuilder type and
treats it differently from other types. By default, StringBuilder is
passed as [InAttribute, OutAttribute]. StringBuilder is special
because it has a Capacity property that can determine the size of the
required buffer at run time, and it can be changed dynamically.
Therefore, during the marshaling process, the CLR can pin
StringBuilder, directly pass the address of internal buffer used in
StringBuilder, and allow the contents of this buffer to be changed by
native code in place.
To take full advantage of StringBuilder, you'll need to follow all of
these rules:
1.Don't pass StringBuilder by reference (using out or ref). Otherwise, the CLR will expect the signature of this argument to be wchar_t **
instead of wchar_t *, and it won't be able to pin StringBuilder's
internal buffer. Performance will be significantly degraded.
2.Use StringBuilder when the unmanaged code is using Unicode. Otherwise, the CLR will have to make a copy of the string and convert
it between Unicode and ANSI, thus degrading performance. Usually you
should marshal StringBuilder as LPARRAY of Unicode characters or as
LPWSTR.
3.Always specify the capacity of StringBuilder in advance and make sure the capacity is big enough to hold the buffer. The best practice
on the unmanaged code side is to accept the size of the string buffer
as an argument to avoid buffer overruns. In COM, you can also use
size_is in IDL to specify the size.
Rule 3 doesn't seem like it is satisied here.

Related

stringbuilder as out parameter

extern "C"
__declspec(dllexport)
bool FillString(LPWSTR OutName)
{
LPWSTR out = L"TheName\0";
int len = wcslen(out);
memcpy(
OutName,
out,
len * sizeof(wchar_t));
return true;
}
That is function in my c/c++ dll, the following is my call from c#...
[DllImport(#"My.dll", EntryPoint = "FillString", CallingConvention = CallingConvention.Cdecl)]
public static extern bool MyFunction([MarshalAsAttribute(UnmanagedType.LPWStr)] StringBuilder Name);
var fromdll = new StringBuilder(64);
// I do not know length of out string (Name), but it is null terminated
bool IsFilled = MyFunction(fromdll);
Console.WriteLine(fromdll);
The output is
TheName???
Can anyone help me to get the output...?
TheName
You need to copy one more character. '\0' is called string terminator. Without it C, C++ and PInvoke in .NET do not recognize end of string. If you are copying wcslen characters, zero is not copied. There are many solutions:
Use mentioned wcscpy to copy string with \0
Copy one more
character with memcpy(OutName, out, (len + 1) * sizeof(wchar_t)).
Moreover, it's a good idea to pass buffer (StringBuilder) size. C/C++ lets you write outside variable boudaries. This size enables to avoid that.

DLLImport c++ functions with char* as input or as output parameters

I have two c++ functions that I want to DllImport:
bool SendString(const char* pSendStr, long strSize);
bool ReadString(char* pReadStr, long& readStrSize);
There are a lot of articles that write how to DllImport strings.
Alas quite often I see different replies to the same question.
For instance some say if a c++ function returns a a char* and an int* strLen, then some people say I should use a StringBuilder in my dllImport statement and others say return byte[], some have a marshall statement in the dllImport, some don't. Some answers seem needed because of old C# / .net versions.
So the question is: If the dll call from c++ is fairly straightforward, without strange calling conventions, or other strange items, what should the corresponding DllImport functions be if you have output char* and size or input char * and size?
c++ .h
bool SendString(const char* pSendStr, long strSize);
bool ReadString(char* pReadStr, long& readStrSize);
What are the corresponding DllImports? replace the instr and outstr with string? stringbuilder? char[]? byte[]? Is any marshal statement needed?
bool SendString(const char* pSendStr, long strSize);
This function is the easy one. The text is sent from the caller to the callee. The p/invoke is declared like this:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern bool SendString(string SendStr, int Len);
Note that I'm assuming the cdecl calling convention since that is the default for C++ code. And also do note that long in C++ on Windows is 32 bits wide. So it matches int in C#.
When you call the function you need to pass the string and its length. However, the normal convention is for null-terminated strings to be used so the length parameter is not needed. I'd declare the unmanaged function like this:
bool SendString(const char* pSendStr);
And the p/invoke like this:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern bool SendString(string SendStr);
The other function is a little more complex. You've declared it like this:
bool ReadString(char* pReadStr, long& readStrSize);
Here the caller allocates the buffer which is populated by the callee. You can use StringBuilder for the text and let the marshaler do the work for you. The p/invoke is:
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
static extern bool ReadString(StringBuilder ReadStr, ref int Len);
The convention is that you supply the length of the provided buffer. In turn the function will let you know how many characters it wrote. You'd call the function like this:
int len = 256;
StringBuilder ReadStr = new StringBuilder(len);
bool succeeded = ReadString(ReadStr, ref len);
if (succeeded)
{
string str = ReadStr.ToString();
}
As leppie wrote, what you usually want is:
[DllImport(my.dll)]
static extern bool SendString(string sendString, int stringSize);
[DllImport(my.dll)]
static extern bool ReadString(StringBuilder readString, ref int readStringSize);
This would do automatic conversion to Unicode (and back) for you.
If you want precise access to your char*, you would use byte[]. This way no conversion is done and you have more control on what is going on. Usually you won't need that. One use case might by when your char* can include \0 chars.

Changing the string to which an IntPtr is pointing

In my C# application I have a variable lpData of type IntPtr (received from a call to unmanaged code), and it points to a string.
I have to replace this string with another value.
I tried:
int RegQueryValueExW_Hooked(
IntPtr hKey,
string lpValueName,
int lpReserved,
ref Microsoft.Win32.RegistryValueKind lpType,
IntPtr lpData,
ref int lpcbData)
{
lpData = Marshal.StringToHGlobalUni("new string");
...
}
but this doesn't seem to replace the actual string.
Can someone point me in the right direction on how to do this?
Thanks
Of course it doesn't replace the string - you're getting the pointer to the string where your caller has the value. You're only replacing the value of your "local variable" (the parameter), this doesn't change anything on the caller's side.
If you want to modify the value on the original pointer (and make sure you actually do want that, this is where "weird errors" lurk - it's very easy to overwrite surrounding variables, forget about the null terminator, etc.), you can use Marshal.Copy, for example:
var bytes = Encoding.Unicode.GetBytes("your string\0");
Marshal.Copy(bytes, 0, lpData, bytes.Length);
Again - this is a very dangerous behaviour and you shouldn't be doing this. You're violating several contracts implied by parameter passing etc.
Now that I've answered your question, let me talk about how wrong you are about actually needing to do this (and this is very much related to your other post about using the StringBuilder).
You are trying to modify a value passed to you as a parameter. However, the string was allocated by the caller. You don't even know how long it is! If you start copying data to that pointer, you're going to overwrite the data of eg. completely different variables, that just randomly happened to be allocated just after the string. This is considered "very bad".
Instead, what you want to do, is follow the proper process that RegQueryValueEx has (http://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx). That means you first have to check the lpcbData value. If it is large enough to hold all the bytes you want to write, you just write the data to the lpData and set the lpcbData value to the proper length. If not, you still set lpcbData, but return ERROR_MORE_DATA. The caller should then call RegQueryValueEx again, with a larger buffer.
The sample code would be something like this:
string yourString = "Your string";
int RegQueryValueExW_Hooked(
IntPtr hKey,
string lpValueName,
int lpReserved,
ref Microsoft.Win32.RegistryValueKind lpType,
IntPtr lpData,
ref int lpcbData)
{
var byteCount = Encoding.Unicode.GetByteCount(yourString);
if (byteCount > lpcbData)
{
lpcbData = byteCount;
return ERROR_MORE_DATA;
}
if (lpData == IntPtr.Zero)
{
return ERROR_SUCCESS;
}
lpcbData = byteCount;
var bytes = Encoding.Unicode.GetBytes(yourString);
Marshal.Copy(bytes, 0, lpData, bytes.Length);
return ERROR_SUCCESS;
}
Do note that this is just what I've written after quickly glancing through the documentation - you should investigate further, and make sure you're handling all the possible cases. .NET doesn't protect you in this case, you can cause major issues!

passing string from C++ to C# using void pointer

I am having C++ code like this just to try out to get the string to C# application from c++ dll.
int GetValue(void *pBuffer)
{
int x = 0;
String^ temp;
temp = "TestStringtest1test2";
memcpy(pBuffer, &temp,sizeof(temp));
Console::WriteLine(temp);
return x;
}
on c# side all I am doing is
[DllImport("Cplusplusapp.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetValue(StringBuilder pBuffer);
StringBuilder Buffer = new StringBuilder(100);
int x = GetValue(Buffer);
Console.WriteLine(Buffer);
I have tried marshalling and various other suggestions but I am not able to understand why I am getting garbage values.Its fairly simple but what is that I am missing.
Address of temp IS NOT string itself then memcpy won't work as expected. Moreover sizeof(String) will return size of System.String object (4 bytes on 32 bit machine), not string length.
If you need to copy a value from a managed string to an unmanaged pointer you have to follow next steps.
First of all System.String is always UNICODE then you won't have char* but wchar_t* (but you can convert it to what you need with usual helper macros). To convert a string from System.String to wchar_t* call Marshal::StringToBSTR():
String^ temp = L"My string";
BSTR bstr = Marhshal::StringToBSTR(temp);
memcpy(pBuffer, bstr, SysStringLength(bstr));
Marshal::FreeBSTR(bstr);
Of course if you do not need to use a System.String you can skip all of these simply not assigning your string to System.String. Change it to wchar_t* or char* according to how you'll import that function (CharSet.Ansi or not), take a look to other Marshal::StringToXYZ functions, I would create e temporary string from System::String and then I would strcpy to destination buffer (check functions with Ansi suffix for char* versions). See this post here on SO for an example of that.

C# Dll Invoke String Encoding Problem

StringBuilder codeline = new StringBuilder(100);
protected virtual int OnCodeLine(int code, int docId, ref StringBuilder codeline)
{
////
}
This is what i get with *ref StringBuilder*
ĞĞÑĞÒĞÓĞÔĞÕĞÖĞ×ĞØĞÙĞÚĞÛĞÜĞİĞŞĞßĞàĞáĞâĞãĞäĞåĞæĞçĞèĞéĞêĞëĞìĞíĞîĞïĞğĞñĞòĞóĞôĞõĞöĞ÷ĞøĞùĞúĞûĞüĞıĞşĞÿĞÑÑÑÑÑÑÑ Ñ
ÑÑÑ
ÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑ
and only with StringBuilder i only get 3
This is what its suppose to return
300 078 9059431
By the way this the MICR Code from Cheques
[DllImport("mflib.dll")]
public static extern int mfScanFeeder(int mode, int font, int timeout);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int MFS100_CodeLineDelegate(int code, int docId, ref StringBuilder codeline);
public event MFS100_CodeLineDelegate MFS100_CodeLine;
private static MFS100_CodeLineDelegate cache_CodeLine;
Update : Here is the Original Code that works in vb6
Public Function MFS100_OnCodeline(ByVal code As Long, ByVal docId As Long, ByVal codeline As String) As Long
Dim i As Integer
WriteEvent "OnCodeline:"
WriteEvent " code = " & code
WriteEvent " docId = " & docId
WriteEvent " codeline = " & codeline
MFS100_OnCodeline = -1 ' -1 means: sorting will be done by mfSort()
g_codeline = codeline
CScannerForm.TmrSort.enabled = True
End Function
Update 2
mfSetEvent((int)EventEnum.E_EVENT_CODELINE, cache_CodeLine);
[DllImport("mflib.dll")]
private static extern int mfSetEvent(int eventID, Delegate callsback);
When i use StringBuilder with ref i get a string that have
32361 length. Without ref i get
only the first value of the
string.
OnCodeLine is for the callback
from the scanner device. What is the
problem ?
You should not pass the StringBuilder by ref. When you do that you say you only get one character back. That's the hallmark of expecting ANSI encoding but actually receiving Unicode (UTF-16). Fix it by specifying the character set:
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet=CharSet.Unicode)]
public delegate int MFS100_CodeLineDelegate(int code, int docId, StringBuilder codeline);
I don't understand what are you trying to achieve, but if you want to collect a string generated inside OnCodeLine, you don't need to pass the StringBuilder by reference, as it is a reference type.
Just pass the StringBuilder without ref, populate it, and when you return you'll have the desired string in it.
Regarding what you get after calling the OnCodeLine, can you provide some info regarding the implementation?
Sorry, I didn't notice the PInvoke was involved!! :(

Categories

Resources