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 与指针 - C++ 到 C#

Windows Mobile 平台上的 PInvoke

PInvoke - 方法的类型签名与 PInvoke 不兼容

PInvoke - 如何编组'SomeType * []'?

C# DllImport“调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配 ”

如何制作一个 PInvoke 友好的原生 API?