简体   繁体   中英

Proper way of passing a pointer for P/Invoke function

Dear skilled. I'm developing an entity which allows user to copy multiple files in async manner with cancellation ability (and reporting progress as well). Obviously the process of copying runs in another thread, different from thread where CopyAsync was called.

My first implementation uses FileStream.BeginRead/BeginWrite with a buffer and reporting progress against number of usages of that buffer.

Later, for education purposes, I was trying to implement the same stuff thru Win32 CopyFileEx function. Eventually, I've stumbled upon the following thing: this function takes a pointer to bool value which is treated as cancellation indicator. According to MSDN this value is to be examined multiple times by Win32 during copying operation. When user sets this value to “false” the copying operation is cancelled.

The real problem for me is how to create a boolean value, pass it to Win32 and to make this value accessible for external user to give him an ability to cancel the copying operation. Obviously the user will call CancelAsync(object taskId), so my question is about how to get access to that boolean value in another thread fro my CancelAsync implementation.

My first attempt was to use Dictionary where key is an identifier of async operation and value points to allocated for boolean value memory slot. When user calls “CancelAsync(object taskId)” method, my class retrieves a pointer to that allocated memory from dictionary and writes “1” there.

Yesterday I've developed another solution which is based on creating a bool local variable in my method of copying and holding the address of that value in dictionary until copying operation completes. This approach could be described in the following lines of code (very simple and rough, just to illustrate an idea):

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);
        }
    }
}

Actually I'm a bit new to P/Invoke and all unsafe stuff so hesitating about latter approach for holding a reference to local value in dictionary and exposing this value to another thread.

Any other thoughts on how to expose that pointer to boolean in order to support cancellation of copying operation?

Ah, so that's what that other thread was about. There's a much better way to accomplish this, CopyFileEx() also supports a progress callback. That callback allows you to update the UI to show progress. And it allows you to cancel the copy, just return PROGRESS_CANCEL from the callback.

Visit pinvoke.net for the callback delegate declaration you'll need.

If your goal is to support being able to cancel a file copy operation in progress, I recommend using a CopyProgressRoutine . This gets called regularly during the copy, and allows you to cancel the operation with a return code. It will let you cancel the operation asynchronously without having to deal with pointers directly.

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
}

If you do want to use the pbCancel argument, then manually allocating unmanaged memory as you are already doing is probably the safest way to do it. Taking the address of a local variable is a little dangerous because the pointer will no longer be valid once the variable goes out of scope.

You could also use a boolean field in an object rather than a boolean local variable, but you will need to pin it in memory to prevent the garbage collector from moving it. You can do this either using the fixed statement or using GCHandle.Alloc .

也许我缺少了一些东西,但是为什么您不能只使用defn @ http://pinvoke.net/default.aspx/kernel32/CopyFileEx.html ,然后在取消时将ref int(pbCancel)设置为1。 ?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM