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 参数的主要内容,如果未能解决你的问题,请参考以下文章
.Net调用非托管代码(P/Invoke与C++InterOP)
我是不是需要一个包装类来从 user32.dll 中 p/invoke'ing 一些函数