locked
FAQs:P/Invoke(平台调用)本地函数的最好方法是什么,该本地函数以TCHAR*类型数组为输入参数,返回该数组的第一个元素? RRS feed

答案

  • 假设一个本地函数以TCHAR*类型数组为输入参数,返回该数组的第一个元素。当使用P/Invoke调用该本地函数时,可以引用下面的代码:
    NativeDll.cpp:
    #include "stdafx.h"
    #include <tchar.h>
    #include <strsafe.h>
    #include <objbase.h>

    extern "C" __declspec(dllexport) TCHAR* test(TCHAR** strarr)
    {
        int length = _tcslen(strarr[0]) + 1; // '+1' 结束NULL字符
        STRSAFE_LPWSTR result = (STRSAFE_LPWSTR)CoTaskMemAlloc(length * sizeof(TCHAR));
        // CoTaskMemAlloc 接受以字节为单位的长度
        StringCchCopy(result, length, strarr[0]);
        return (TCHAR*) result;
    }

    CSharpCaller.cs:
    using System.Runtime.InteropServices;

    class Program
    {
        [DllImport("NativeDll.dll", CharSet = CharSet.Auto)]
        static extern IntPtr test(string[] array);
       
        static void Main(string[] args)
        {
            string[] array = new string[2];
            array[0] = "hello";
            array[1] = "world";
            IntPtr ptr = test(array);
            string result = Marshal.PtrToStringAuto(ptr);
            Marshal.FreeCoTaskMem(ptr);
            System.Console.WriteLine(result);
        }
    }

    根据CLR interop的特性,本地代码中,我们用CoTaskMemAlloc分配一块本地内存保存返回值,并将输入参数的字符拷贝至内存块,以这种方法代替直接返回输入数组的第一个元素。(原因将会在下面讨论)

    托管代码中,我们不把本机函数test的P/Invoke签名中的返回类型声明成String类型。实际上CoTaskMemAlloc为返回值分配的内存被CLR interop层自动释放。虽然在x86平台的.NET Framework runtime2中,将返回类型声明为String类型不会引起内存泄漏,但即将发布的runtime4极大的改变了内存整理机制。对于上面的例子,P/Invoke返回String类型的值时,interop层不会释放内存。如果我们不主动释放本地代码中使用的内存块,便会引起内存泄漏。至少有两种方法修复:
    1. 和上面代码例子中建议的一样,返回一个IntPtr并调用Marshal.FreeCoTaskMem方法释放指针指向的内存地址。
    2. 在本地DLL中提供一个方法专门用于清理内存,并在托管代码中P/Invoke。用这种方法可以隐藏内存分配及释放的具体实现。这样做的好处是可以使用不同的堆操作对,比如malloc – free, new – delete, new[] – delete[],以此来代替使用CoTaskMemAllo和CoTaskMemFree强制分配及释放内存。

    论坛帖子分析:
    http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/ccbf1e8c-c5de-4903-8d8d-d9b2bcae18ff/

    如果在本地函数中按照下面代码中的方法直接返回第一个数组元素,那么托管方的string类型返回值将接受到一个无用的字符。(x86平台下.NET Framework runtime v2)
    NativeDLL.cpp:
    extern "C" __declspec(dllexport) TCHAR* test(TCHAR** strarr)  
    {
        return strarr[0];
    }

    CSharpCaller.cs:
    class Program
    {
        [DllImport("NativeDll.dll", CharSet = CharSet.Auto)]
        static extern string test(string[] array);

        static void Main(string[] args)
        {
            string[] array = new string[2];
            array[0] = "hello";
            array[1] = "world";
            string result = test(array);
            Console.WriteLine(result);
        }
    }

    问题原因(基于X86平台下的.NET Framework runtime v2)

    托管代码调用本地函数时,不会直接传递string数组到本机。取而代之的做法是,CLR分配内存,根据.NET对象中的内容创建的本地 TCHAR* 数组,这是CLR分配和清理内存的职责所在。

    Interop层为P/Invoke执行了以下工作:
    1. 分配本地TCHAR*数组
    2. 根据托管的string数组初始化TCHAR*数组
    3. 利用刚分配的TCHAR*数组,调用本机函数
    4. 释放步骤1中分配的内存
    5. 分配一个string类型的对象存放返回值(输入的TCHAR*数组的第一个元素)
    6. 将TCHAR* 返回的字符类型转化为新建的.NET string类型
    7. 释放存放返回值的本地内存

    因为本地内存在第4步中已经释放了,后面的步骤(5,6,7)访问已释放的内存空间,并获得错误的值传入.NET string类型的对象作为返回值。

    可以用第一段中的代码例子解决此问题。

    其他相关的帖子:
    http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/daf8d0d6-1e0d-4f25-9ae9-d7b317946c16
    http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/af2d0101-0bf4-49c9-bc2a-10a40074674e


    如果您对我们的论坛在线支持服务有任何的意见或建议,请通过邮件告诉我们。
    MSDN 论坛好帮手 立刻免费下载  MSDN 论坛好帮手
    2011年3月2日 10:29