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.
No comments:
Post a Comment