简体   繁体   English

将字符串从非托管dll返回C#

[英]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: 我有下面的VB代码有效:

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. 我正在尝试在C#项目中实现它。 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: 通过试验ref和out,我偶尔设法使其崩溃而不是冻结,但是我实现的唯一“进步”是用以下命令替换了dll函数调用:

    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 . 根据其他地方的帖子 ,在类似的VB代码上使用.NET Reflector表示需要使用string类型代替StringBuilder ,如Alex Mendez,JamieSee和Austin Salonen所建议的,以及显式编组,如Nanhydrin,但使用非托管类型VBByRefStr而不是AnsiBStr The final key to the puzzle is that the string parameter then needs to be passed by reference using the ref keyword. 难题的最后一个关键是,然后需要使用ref关键字通过引用传递字符串参数。

I can confirm that this works, and that my final working C# code is therefore: 我可以确认这行得通,因此我最终的C#代码是:

    [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. 您可能需要在dllimport声明中更具体一些,并添加一些MarshalAs属性,如果您对被调用函数期望使用哪种类型的字符串(Ansi,Unicode,空终止等)有更多详细信息,则可能会有所帮助。 In fact it expecting null terminated strings could perhaps explain why it's hanging rather than erroring out. 实际上,它期望以null终止的字符串可能可以解释为什么它挂而不是出错。

[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]. 您可能还需要使用参数属性[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. 您不能在此处封送StringBuilder。 There are some rules to follow for marshalling StringBuilder (see CLR Inside Out: Marshaling between Managed and Unmanaged Code ): 编组StringBuilder时需要遵循一些规则(请参阅CLR Inside Out:托管代码和非托管代码之间的封送处理 ):

StringBuilder and Marshaling StringBuilder和封送处理

The CLR marshaler has built-in knowledge of the StringBuilder type and treats it differently from other types. CLR封送处理程序具有StringBuilder类型的内置知识,并与其他类型区别对待。 By default, StringBuilder is passed as [InAttribute, OutAttribute]. 默认情况下,StringBuilder作为[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. StringBuilder的特殊之处在于它具有Capacity属性,该属性可以在运行时确定所需缓冲区的大小,并且可以动态更改它。 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. 因此,在封送处理过程中,CLR可以固定StringBuilder,直接传递StringBuilder中使用的内部缓冲区的地址,并允许通过本地代码就地更改此缓冲区的内容。

To take full advantage of StringBuilder, you'll need to follow all of these rules: 要充分利用StringBuilder,您需要遵循以下所有规则:

1.Don't pass StringBuilder by reference (using out or ref). 1.不要通过引用传递StringBuilder(使用out或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. 否则,CLR将期望此参数的签名为wchar_t **而不是wchar_t *,并且它将无法固定StringBuilder的内部缓冲区。 Performance will be significantly degraded. 性能将大大降低。

2.Use StringBuilder when the unmanaged code is using Unicode. 2.当非托管代码使用Unicode时,请使用StringBuilder。 Otherwise, the CLR will have to make a copy of the string and convert it between Unicode and ANSI, thus degrading performance. 否则,CLR将必须复制字符串并在Unicode和ANSI之间进行转换,从而降低性能。 Usually you should marshal StringBuilder as LPARRAY of Unicode characters or as LPWSTR. 通常,应将StringBuilder编组为Unicode字符的LPARRAY或LPWSTR。

3.Always specify the capacity of StringBuilder in advance and make sure the capacity is big enough to hold the buffer. 3.始终预先指定StringBuilder的容量,并确保容量足以容纳缓冲区。 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. 在COM中,还可以在IDL中使用size_is指定大小。

Rule 3 doesn't seem like it is satisied here. 规则3在这里似乎并不令人满意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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