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

时间:2012-02-10 23:12:50

标签: c# pinvoke dllimport stringbuilder

我很遗憾在这里问这个,因为我确定它必须“在那里”回答,但我已经坚持了好几个月了,而且我找到的解决方案都没有用到我

我有以下可用的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)

我试图在C#项目中实现它。我能找到的最好的等价物是:

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

然而,当我运行它时,应用程序冻结,我必须强行退出它。通过试验ref和out,我偶尔设法让它崩溃而不是冻结,但我所取得的唯一“进展”是用以下代码替换dll函数调用:

    DeviceSendRead(txtSend.Text, null, null, 10);

这可以防止崩溃,但当然不会做任何事情(我可以​​检测到)。因此我假设这是传递导致问题的两个返回字符串参数的方式。如果有人能够提出我可能做错的事情,我会很高兴听到它。感谢。

5 个答案:

答案 0 :(得分:1)

我已经得到了答案,我将在此完整记录,并感谢所有指出我正确方向的人。

根据其他地方的this post,在类似的VB代码上使用.NET Reflector表明需要使用string类型代替我的StringBuilder,正如Alex Mendez所建议的那样,JamieSee和Austin Salonen,以及Nanhydrin建议的显式编组,但使用非托管类型VBByRefStr而不是AnsiBStr。这个难题的最后一个关键是,然后需要使用ref关键字通过引用传递字符串参数。

我可以确认这是有效的,因此我的最终工作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);

我希望这对面临类似问题的其他人有用。

答案 1 :(得分:0)

尝试将其作为等价物:

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

答案 2 :(得分:0)

试试这个:

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

答案 3 :(得分:0)

Default Marshalling for strings
Default Marshalling behaviour

您可能需要在dllimport声明中更具体,并添加一些MarshalAs属性,如果您有关于被调用函数所期望的字符串类型(Ansi,Unicode,null终止等)的更多详细信息,那么救命。 事实上,它期望空终止字符串可能解释为什么它挂起而不是错误。

[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]  
public static extern int DeviceSendRead(string outString, [MarshalAs(UnmanagedType.AnsiBStr)]string readString, string errorString, double timeout);

您可能还需要使用参数属性[In,Out]明确声明您的参数是输入,输出或两者。

答案 4 :(得分:-1)

[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);

您无法在此处封送StringBuilder。编组StringBuilder有一些规则可以遵循(参见CLR Inside Out: Marshaling between Managed and Unmanaged Code):

  

StringBuilder和Marshaling

     

CLR封送程序具有StringBuilder类型和内置的内置知识   对待它与其他类型不同。默认情况下,StringBuilder是   传递为[InAttribute,OutAttribute]。 StringBuilder很特别   因为它有一个可以确定大小的Capacity属性   在运行时需要缓冲区,并且可以动态更改。   因此,在编组过程中,CLR可以固定   StringBuilder,直接传递用于的内部缓冲区的地址   StringBuilder,并允许更改此缓冲区的内容   原生代码到位。

     

要充分利用StringBuilder,您需要全部遵循   这些规则:

     

1.不要通过引用传递StringBuilder(使用out或ref)。否则,CLR将期望此参数的签名为wchar_t **   而不是wchar_t *,它将无法固定StringBuilder   内部缓冲区。性能将显着下降。

     

2.当非托管代码使用Unicode时,使用StringBuilder。否则,CLR必须复制字符串并进行转换   它在Unicode和ANSI之间,从而降低了性能。通常你   应该将StringBuilder编组为Unicode字符的LPARRAY或   LPWSTR。

     

3.始终预先指定StringBuilder的容量,并确保容量足以容纳缓冲区。最佳做法   在非托管代码端是接受字符串缓冲区的大小   作为避免缓冲区溢出的参数。在COM中,您也可以使用   size_is在IDL中指定大小。

规则3似乎并不满足。