P/Invoke 有时会导致 Win32 1008 错误与 StringBuilder 参数

Posted

技术标签:

【中文标题】P/Invoke 有时会导致 Win32 1008 错误与 StringBuilder 参数【英文标题】:P/Invoke sometimes cause Win32 1008 Error with StringBuilder parameters 【发布时间】:2012-03-07 15:55:46 【问题描述】:

我有一个 DLL,我需要从中 P/Invoke 以下 C 方法:

int DAStart(
    HANDLE hOpen,
    char* IPAddress,
    int IPPort,
    int threadPriority,
    char* name,
    char* password,
    char* userName)

使用 P/Invoke 助手和我自己的研究,我想出了以下 C# 签名:

[DllImportAttribute("<libName>", EntryPoint="DAStart")]  
static extern  int DAStart(  
    IntPtr hOpen,  
    [MarshalAs(Unmanaged.LPStr)] StringBuilder IPAddress,
    int IPPort,
    int threadPriority, 
    [MarshalAs(Unmanaged.LPStr)] StringBuilder name, 
    [MarshalAs(Unmanaged.LPStr)] StringBuilder password,   
    [MarshalAs(Unmanaged.LPStr)] StringBuilder userName);

现在,我正在通过以下方式拨打电话:

int port = 3000;
int threadPriority = 20;

DAStart(
    this.nativeDllHandle, // an IntPtr class field  
    new StringBuilder("10.0.10.1"),
    int port,
    int threadPriority,
    new StringBuilder("admin"),
    new StringBuilder("admin"),
    new StringBuilder("admin"));

现在,有时这工作得很好,但是我在以下调用此库时收到 Win32 错误1008 - An attempt was made to reference a token that does not exist

可能是我的 StringBuilder 对象被垃圾收集,因此如果本机代码尝试使用它,引用将不再存在?我应该为每个人保留一个参考吗?

在这种情况下,IntPtr 是否是传递我的字符串的更好解决方案?

** 更新 **

这是我为 DAStart 准备的所有 API 文档:

输入 HANDLE hInfo DAOpen 返回的句柄 char *IPAdress_in TMEE 服务器的 IP 地址 int IPPort TMEE 服务器的控制台端口(默认为 3000 端口) int threadPriority 发送文件线程的线程优先级。 char *name 未在硬件 DLL 中使用 char *password 未在硬件 DLL 中使用 char *username 未在硬件 DLL 中使用

退货 ERROR_SUCCESS 0 ERROR_BAD_HANDLE -1 ERROR_BIND_FAILED -10

评论 DAStart API 将客户端 dll 连接到活动的 TMEE 服务器服务。客户端线程以设置为 threadPriority 参数的优先级启动。 IP 地址参数必须设置为 TMEE 服务器的地址。端口参数必须设置为 TMEE 服务器监听控制台连接的端口(控制台使用的默认端口是 3010)。控制台线程以设置为 threadPriority 参数的优先级启动。

【问题讨论】:

请不要输入代码。使用剪贴板。这样我们就可以尝试调试真实的代码了。 您有 DAStart 的任何文档吗?它说什么? @DavidHefferman 我正在编码的机器没有连接到互联网或本地网络,也没有 USB。否则,我会复制/粘贴它。 @DavidHefferman 我包含了 DAStart API 文档。 看起来您可以为最后 3 个参数传递 NULL。不得不说,我不明白为什么这个函数会修改字符串参数。我也不敢相信它不会复制输入数据。这是令人震惊的糟糕形式。你确定调用约定是stdcall 【参考方案1】:

在 cmets 中,您指出 DLL 获取char* 指针的副本(而不是字符串的内容),然后修改字符串的内容 DAStart 返回之后。

面对这种非常规的界面设计,您唯一的选择就是负责字符串参数的编组。您不能使用StringBuilder,因为传递给本机代码的char* 仅在pinvoke 调用期间有效。在 pinvoke 调用返回后不能依赖它是有效的。

因此,您唯一的解决方案是将字符串参数作为IntPtr 传递。使用Marshal.StringToHGlobalAnsi 分配内存。将生成的IntPtr 传递给DAStart。当您确定 DLL 使用指针完成时,使用 Marshal.FreeHGlobal 解除分配。

【讨论】:

【参考方案2】:

垃圾收集器不知道托管环境之外的引用。 因此,只要您需要对象,您就需要在某处保留额外的引用 - 最简单的是通过调用类中的静态属性。 IntPtr 没有任何区别 - 它也会被收集。

【讨论】:

我不太明白这个。我看不出它有帮助。 问题是图书馆的问题是“关于后续呼叫”。这些调用可能会在稍后发生,然后可能会收集 StringBuilder,因为没有已知的 GC 引用。 @weismat 我认为你是完全正确的......保留字符串生成器的引用似乎可以防止错误! 我记得我花了一整天的时间来解决托管代码和 C dll 之间的函数指针/委托的类似问题。 -1 这不是解决方案。 DLL 接口设计的首要规则之一是复制输入的char* 数据。如果 DLL 确实没有做到这一点,而是进行了引用,那么简单地使 StringBuilder 静态无济于事。使用 StringBuilder 编组时传递给 DLL 的指针仅对 pinvoke 调用有效。想象一下当 GC 移动 StringBuilder 对象时会发生什么。

以上是关于P/Invoke 有时会导致 Win32 1008 错误与 StringBuilder 参数的主要内容,如果未能解决你的问题,请参考以下文章

P/Invoke 从 C# 到 C++ 的外部方法

.Net调用非托管代码(P/Invoke与C++InterOP)

我是不是需要一个包装类来从 user32.dll 中 p/invoke'ing 一些函数

WPF与WinForm

设置高 DPI 时防止我的 Win32 应用程序的 UI 元素按比例放大

Process.Start() 打开 URL 有时会抛出 Win32Exception?