将字符串从非托管dll返回到C#
本文关键字:dll 返回 字符串 | 更新日期: 2023-09-27 18:24:00
我很抱歉在这里问这个问题,因为我确信它必须被回答为"在那里",但我已经在这个问题上坚持了几个月,我找到的解决方案都不适合我。
我有以下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);
这可以防止崩溃,但当然没有任何作用(我可以检测到)。因此,我假设是传递两个返回字符串参数的方式导致了问题。如果有人能告诉我可能做错了什么,我会很高兴听到的。谢谢。
我已经找到了答案,为了完整起见,我将在这里记录下来,并感谢所有为我指明正确方向的人。
根据其他地方的这篇文章,在类似的VB代码上使用.NET Reflector表明,需要使用string
类型来代替我的StringBuilder
,正如Alex Mendez、JamieSee和Austin Salonen在这里建议的那样,以及Nanroden建议的显式封送处理,但使用非托管类型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);
我希望这对其他面临类似问题的人有用。
试试这个:
[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);
尝试将其作为等价物:
string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);
字符串的默认封送
默认编组行为
您可能需要在dllimport声明中更加具体,并添加一些MarshalAs属性,如果您有更多关于被调用函数所需字符串类型的详细信息(Ansi、Unicode、null终止等),那么这将有所帮助。事实上,它期望以null结尾的字符串也许可以解释为什么它挂起而不是出错。
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outString, [MarshalAs(UnmanagedType.AnsiBStr)]string readString, string errorString, double timeout);
您可能还需要通过使用参数属性[In,Out]来明确声明您的参数是输入、输出或两者都是。
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);
不能在此处封送StringBuilder。封送StringBuilder需要遵循一些规则(请参阅CLR Inside-Out:在托管代码和非托管代码之间封送):
StringBuilder和编组
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在这里似乎并不令人满意。