简体   繁体   中英

Return string from unmanaged dll to 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 as an equivalent:

string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);

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);

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM