Of course I decided to create a pure C# implementation. This was my goal for the implementation. See the project here.
using (Curl c = new Curl()) { CURLcode ret = c.SetUserAgent("MyUserAgent"); ret = c.SetUrl(urlToDownload); c.OnWriteCallback = c_OnWriteCallback; c.OnProgressCallback = c_OnProgressCalback; using (FileStream downloadDestFile = new FileStream(downloadDestPath, FileMode.Append)) { c.SetWriteData(downloadDestFile); c.SetProgressData(fileInfo); ret = c.Perform(); } } int c_OnWriteCallback(byte[] buffer, int size, object userdata) { FileStream downloadDestFile = (FileStream)userdata; downloadDestFile.Write(buffer, 0, size); return size; }
The first task was understanding what the C library was being used for. One of the main uses was for the libcurl callbacks. So how do you get a C library to invoke a method within your C# code. I read a few articles on this subject. Here is one. All were convoluted in some way. I wanted something streamlined.
// this matches the C method signature private delegate int _GenericCallbackDelegate(IntPtr ptr, int sz, int nmemb, IntPtr userdata); [DllImport(m_libCurlBase, CallingConvention = CallingConvention.StdCall)] private static extern CURLcode curl_easy_setopt(IntPtr pCurl, CURLoption opt, _GenericCallbackDelegate callback); ... _GenericCallbackDelegate cb = new _GenericCallbackDelegate(internal_OnWriteCallback); cbHandle = GCHandle.ToIntPtr(GCHandle.Alloc(cb, GCHandleType.Pinned)); curl_easy_setopt(m_pCURL, CURLoption.CURLOPT_WRITEFUNCTION, cb); ... int internal_OnWriteCallback(IntPtr ptrBuffer, int sz, int nmemb, IntPtr ptrUserdata) { }
The solution is to pin the callback object so it can be found by the C library. So now curl invokes its write function and that will callback into internal_OnWriteCallback. The full implementation is like this.
IntPtr GetHandle(object obj) { if (obj == null) return IntPtr.Zero; return GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Pinned)); } public delegate int GenericCallbackDelegate(byte[] buffer, int size, object userdata); public GenericCallbackDelegate OnWriteCallback { set { if (_OnWriteCallbackHandle != IntPtr.Zero) { FreeHandle(ref _OnWriteCallbackHandle); _OnWriteCallback = null; } if (value != null) { _OnWriteCallback = value; _GenericCallbackDelegate cb = new _GenericCallbackDelegate(internal_OnWriteCallback); _OnWriteCallbackHandle = GetHandle(cb); curl_easy_setopt(m_pCURL, CURLoption.CURLOPT_WRITEFUNCTION, cb); } } } int internal_OnWriteCallback(IntPtr ptrBuffer, int sz, int nmemb, IntPtr ptrUserdata) { if (_OnWriteCallback != null) { int bytes = sz * nmemb; byte[] b = new byte[bytes]; Marshal.Copy(ptrBuffer, b, 0, bytes); object userdata = GetObject(ptrUserdata); return _OnWriteCallback(b, bytes, userdata); } return 0; }
When internal_OnWriteCallback is called from the C library we turn around and invoke the C# delegate.