传递P / Invoke函数指针的正确方法

时间:2010-07-18 10:41:54

标签: c# .net winapi pinvoke

亲爱的熟练。我正在开发一个实体,允许用户以异步方式复制多个文件,并具有取消功能(以及报告进度)。显然,复制过程在另一个线程中运行,与调用CopyAsync的线程不同。

我的第一个实现使用FileStream.BeginRead / BeginWrite和缓冲区,并根据该缓冲区的使用次数报告进度。

后来,出于教育目的,我试图通过Win32 CopyFileEx函数实现相同的东西。最后,我偶然发现了以下内容:此函数采用指向bool值的指针,该值被视为取消指示符。根据MSDN,在复制操作期间,Win32将多次检查该值。当用户将此值设置为“false”时,复制操作将被取消。

对我来说真正的问题是如何创建一个布尔值,将其传递给Win32并使外部用户可以访问该值,以使其能够取消复制操作。显然用户会调用CancelAsync(object taskId),所以我的问题是如何在我的CancelAsync实现的另一个线程中访问该布尔值。

我的第一次尝试是使用Dictionary,其中key是异步操作的标识符,值是为布尔值内存槽分配的值。当用户调用“CancelAsync(object taskId)”方法时,我的类从字典中检索指向该已分配内存的指针,并在那里写入“1”。

昨天我开发了另一个解决方案,它基于在我的复制方法中创建一个bool局部变量,并在字典中保存该值的地址,直到复制操作完成。这种方法可以在以下代码行中描述(非常简单和粗略,只是为了说明一个想法):

class Program
{
    // dictionary for storing operaitons identifiers
    public Dictionary<string, IntPtr> dict = new Dictionary<string,IntPtr>();

    static void Main(string[] args)
    {
        Program p = new Program();

        p.StartTheThread(); // start the copying operation, in my   
                            // implementation it will be a thread pool thread
    }

    ManualResetEvent mre;

    public void StartTheThread()
    {
        Thread t = new Thread(ThreadTask);
mre = new ManualResetEvent(false);
        t.Start(null);

        GC.Collect(); // just to ensure that such solution works :)
        GC.Collect();

        mre.WaitOne();

        unsafe // cancel the copying operation
        {
            IntPtr ptr = dict["one"];
            bool* boolPtr = (bool*)ptr; // obtaining a reference  
                                      // to local variable in another thread
            (*boolPtr) = false;
        }
    }

    public void ThreadTask(object state)
    { 
        // In this thread Win32 call to CopyFileEx will be
        bool var = true;
        unsafe
        {
            dict["one"] = (IntPtr)(&var); // fill a dictionary  
                                           // with cancellation identifier
        }
        mre.Set();

        // Actually Win32 CopyFileEx call will be here
        while(true)
        {
            Console.WriteLine("Dict:{0}", dict["one"]);
            Console.WriteLine("Var:{0}", var);
            Console.WriteLine("============");
            Thread.Sleep(1000);
        }
    }
}

实际上我对P / Invoke和所有不安全的东西都有点新意,因此犹豫了后一种方法,即在字典中保存对本地值的引用并将此值暴露给另一个线程。

关于如何将指针暴露给布尔值以支持取消复制操作的任何其他想法?

3 个答案:

答案 0 :(得分:1)

啊,这就是其他主题的内容。有一个更好的方法来实现这一点,CopyFileEx()也支持进度回调。该回调允许您更新UI以显示进度。它允许您取消副本,只需从回调中返回PROGRESS_CANCEL。

访问pinvoke.net以获取您需要的回调委托声明。

答案 1 :(得分:0)

如果您的目标是支持取消正在进行的文件复制操作,我建议您使用CopyProgressRoutine。这在复制期间会定期调用,并允许您使用返回代码取消操作。它将允许您异步取消操作,而无需直接处理指针。

private class FileCopy
{
    private bool cancel = false;

    public void Copy(string existingFile, string newFile)
    {
        if (!CopyFileEx(existingFile, newFile, 
            CancelableCopyProgressRoutine, IntPtr.Zero, IntPtr.Zero, 0))
        {
            throw new Win32Exception();
        }
    }

    public void Abort()
    {
        cancel = true;
    }

    private CopyProgressResult CancelableCopyProgressRoutine(
        long TotalFileSize,
        long TotalBytesTransferred,
        long StreamSize,
        long StreamBytesTransferred,
        uint dwStreamNumber,
        CopyProgressCallbackReason dwCallbackReason,
        IntPtr hSourceFile,
        IntPtr hDestinationFile,
        IntPtr lpData)
    {
        return cancel ? CopyProgressResult.PROGRESS_CANCEL : 
            CopyProgressResult.PROGRESS_CONTINUE;
    }

    // Include p/inovke definitions from 
    // http://www.pinvoke.net/default.aspx/kernel32.copyfileex here
}

如果您确实想使用pbCancel参数,那么手动分配非托管内存可能是最安全的方法。获取局部变量的地址有点危险,因为一旦变量超出范围,指针将不再有效。

您还可以在对象中使用布尔字段而不是布尔局部变量,但是您需要将其固定在内存中以防止垃圾收集器移动它。您可以使用fixed语句或使用GCHandle.Alloc来执行此操作。

答案 2 :(得分:0)

也许我错过了什么,但为什么你不能只使用defn已经@ http://pinvoke.net/default.aspx/kernel32/CopyFileEx.html然后在取消时将ref int(pbCancel)设置为1?