Pinvoke 中的一个问题
Posted
技术标签:
【中文标题】Pinvoke 中的一个问题【英文标题】:A Problem in Pinvoke 【发布时间】:2011-05-16 14:13:54 【问题描述】:我在 C++ 原生 dll 中有以下函数,我想在 C# 应用程序中使用它。
DWORD __cdecl Foo(
LPCTSTR Input,
TCHAR** Output,
DWORD Options,
ErroneousWord** List = NULL,
LPDWORD Count = 0
);
使用 Pinvoke
[DllImport("dllName", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern UInt32 Foo(string InputWord, out string Output, UInt32 Options, out object List,out UInt32 Count);
调用代码:
string output;
object dummyError = null;
uint dummyCount = 0;
uint x = 0;
Foo(Text, out output, x | y,out dummyError,out dummyCount);
我得到了以下异常
试图读或写保护 记忆。这通常是一个迹象 其他内存已损坏
附注: ErroneousWord 是结构体,我不需要它的输出,所以我将其编组为对象
【问题讨论】:
什么是ErroneousWord
?不管它是什么,我都无法想象它可以编组为 C# 对象。那么Output
参数呢?编组的协议是什么?
输出是从原生代码创建的,并且有暴露的函数来释放它,所以原生代码分配输出并暴露一个函数来释放它
我认为对于输出,您将需要使用Marshal.PtrToStructure
。我对ErroneousWord
不太乐观。
@David:为什么输出会有问题?它是一个指向char*
的指针,而char*
是一个字符串。那应该编组为string
没有任何问题。
对于字符串Output
,我建议使用BSTR
,这样就不必导出释放器。见***.com/questions/5308584/…
【参考方案1】:
该错误很可能意味着您有编组问题。
您没有向我们展示 ErroneousWord
类型是什么,但我认为它是您的 C++ 代码中定义的某种类。我的猜测是它没有被正确编组到 .NET object
。
考虑到它是一个指针(或指向指针的指针),请尝试将该参数更改为 IntPtr
type 以表示指针。没关系,因为无论如何您只是将NULL
传递给参数,可以使用静态IntPtr.Zero
field 轻松表示。
您可能还想以完全相同的方式编组Output
。如果您将参数更改为 IntPtr
类型,您将收到指向 TCHAR*
的指针,然后您可以将其传递给其他非托管函数,但您认为合适(例如,释放它)。
试试下面的代码:
[
DllImport("dllName",
CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)
]
public static extern UInt32 Foo(
string InputWord,
out IntPtr Output, // change to IntPtr
UInt32 Options,
out IntPtr List, // change to IntPtr
out UInt32 Count);
IntPtr output;
IntPtr dummyError = IntPtr.Zero;
uint dummyCount = 0;
uint x = 0;
Foo(Text, out output, x | y, out dummyError, out dummyCount);
您可能还需要使用 Marshal.AllocHGlobal
method 从您的进程中分配 C++ 代码可访问的非托管内存。确保如果你这样做了,你也调用了对应的Marshal.FreeHGlobal
method来释放内存。
【讨论】:
我很难相信out string Output
可以工作。
Output 参数是从本机 dll 创建的,而 dll 公开了另一个函数来释放它,那么为什么我需要使用 StringBuilder??
@Ahmed:呃,输出是由本机 DLL创建 的吗?那你为什么要将它 back 传递给本机 DLL?如果您不关心它的返回值,只需将其作为IntPtr
类型传递,就像您为List
所做的一样。将其视为不透明的句柄。
@Cody: 输出是指向 TCHAR* (字符串)的指针,它是由 dll 创建的,在调用者完成对输出的工作后,它使用 dll 公开的另一个函数来释放它(释放)
@Ahmed:我不太明白这意味着什么,但我不明白为什么不能简单地将其视为 IntPtr
类型,它表示指向字符串的指针.然后,您可以将该指针 back 传递给负责释放非托管 DLL 中的字符串的函数。【参考方案2】:
鉴于 Cody 的回答和 cmets,您将不得不这样做:
[DllImport("dllName", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
extern static UInt32 Foo(string InputWord, out IntPtr Output, UInt32 Options, out IntPtr List, out UInt32 Count);
现在要将输出中的字符串值编组到托管内存,您将执行以下操作:
string outputValue = Marshal.PtrToStringAnsi(Output);
您必须知道 TCHAR 是 Ansi 还是 Unicode,并使用适当的编组。
记得挂在 Output IntPtr 上,这样您就可以将它传递给本机 Free 方法。
【讨论】:
【参考方案3】:感谢 Cody 的回答,但我想单独制作一个,首先输出由 Foo 从本机端创建,然后我调用 FreeFoo 以释放 Foo 分配的内存。 以下是代码
[DllImport("dllname", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern UInt32 Correct(string InputWord, out IntPtr Output, UInt32 Options, out object List,out UInt32 Count);
[DllImport("dllname", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void FreeFoo(IntPtr Output);
使用它:
public string FooWrapper(string Text)
IntPtr output;
object dummyError = null;
uint dummyCount = 0;
uint x = 0;
Foo(Text, out output, x,out dummyError,out dummyCount);
string str = Marshal.PtrToStringUni(output);
FreeFoo(output);
return str;
【讨论】:
【参考方案4】:无论ErroneousWord
类型是什么,都不能将数组编组为单个输出对象。如果有可能将其编组为一个对象......
【讨论】:
以上是关于Pinvoke 中的一个问题的主要内容,如果未能解决你的问题,请参考以下文章
PInvoke - 方法的类型签名与 PInvoke 不兼容
PInvoke - 如何编组'SomeType * []'?
C# DllImport“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ”