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

发布于 2025-01-04 16:51:46 字数 1156 浏览 3 评论 0原文

我很抱歉在这里问这个问题,因为我确信它必须在“外面”得到回答,但我已经在这个问题上坚持了几个月了,而且我找到的解决方案都不适合我。

我有以下有效的 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);

这可以防止崩溃,但当然什么也不做(我可以检测到)。因此,我假设传递两个返回字符串参数的方式导致了问题。如果有人可以建议我可能做错了什么,我会很高兴听到。谢谢。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风蛊 2025-01-11 16:51:46

我已经找到了答案,为了完整起见,我将在这里记录下来,并衷心感谢所有为我指明正确方向的人。

根据其他地方的这篇文章,使用.NET类似 VB 代码的 Reflector 建议需要使用 string 类型代替我的 StringBuilder,正如 Alex Mendez、JamieSee 和 Austin Salonen 在此建议的那样,以及显式的封送处理,如 Nanhidin 建议的那样,但使​​用非托管类型 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);

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

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.

怎会甘心 2025-01-11 16:51:46

试试这个:

[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:

[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);
寒江雪… 2025-01-11 16:51:46

尝试将其作为等效项:

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

Try this as an equivalent:

string readstr = new string(' ', 4000);
string errstr = new string(' ', 1000);
那片花海 2025-01-11 16:51:46

字符串的默认编组
默认编组行为

您可能需要在 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] 明确声明您的参数是输入、输出或两者。

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].

黎夕旧梦 2025-01-11 16:51:46
[DllImport("unmanaged.dll", EntryPoint="DeviceSendRead")]
public static extern int DeviceSendRead(string outstr, string readstr, string errstr, double Timeout);

您不能在此处封送 StringBuilder。编组 StringBuilder 需要遵循一些规则(请参阅 CLR 由内而外:托管和托管之间的编组非托管代码):

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中,您还可以使用
IDL 中的 size_is 来指定大小。

规则3在这里似乎并没有得到满足。

[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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文