Libcurl: 使用 fstream::write 而不是 fwrite 写入时图像损坏

3

我正在尝试从URL下载图像文件。我按照使用fwrite的示例进行了操作,并且成功了。现在我正在尝试使用fstream::write保存数据(ios::binary),但是数据已损坏。这是我的代码:

#include"stdafx.h"
#include<fstream>
#include<iostream>
#include <curl/curl.h>
#include <string.h>

using namespace std;

size_t write_data(void *ptr, size_t size, size_t nmemb, char* out) {
    //void *ptr, size_t size, size_t nmemb, File* fp
   
    fstream file;
    if (file.is_open()){
        file.close();
        file.clear();
    }
    file.open(out, ios::out | ios::binary);
    if (file.is_open()){
        cout << "open successfully\n" << endl;
        
        file.write((char*)ptr, nmemb*size);  // Does it correct?
    };
    // fwrite(ptr,size,nmemb,fp);
    file.close();
    file.clear();
    cout <<"\n sizeof(ptr): " << sizeof(ptr) //size of ptr[0]?
         <<"\n sizeof(char): " << sizeof(char)
         <<"\n size: " << size
         <<"\n nmemb: " << nmemb<< endl;
         return size*nmemb;
}

我对write_data函数中的参数感到困惑,根据CURLOPT_WRITEFUNCTION:

size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);

"ptr指向传递的数据,该数据的大小为size乘以nmemb。"

......那么size和nmemb的含义是什么?

当尝试从网站下载数据时,我打印了前3个参数。看起来char*ptr是存储数据的内存地址(如'char a[]'?),而size是元素大小,nmemb是元素数量。因此,数据大小=大小* nmemb。我正确吗?

输出也很令人困惑:

open successfully
sizeof(ptr):4
sizeof(char):1
size:1
nmemb:2715 
open successfully
sizeof(ptr):4
sizeof(char):1
size:1
nmemb:4865
download successfully

当下载相同的URL时,nmemb和文件的打开次数经常发生变化。
我也对“sizeof(ptr)”感到困惑,它返回“4”(int的大小?)。我该如何使用“sizeof”来获取数据内存的大小,以便我可以证明数据大小为“size * nmemb”?
CURLcode download(char* url,char* out){
    CURL *curl = NULL;
    //FILE *fp = NULL;
    CURLcode res;
    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, out);    //fp
        res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        return res;
    }
    else
    {
        return CURLE_FAILED_INIT;
    }
}    

int main()
{
    CURLcode res = download("http://XXXXXX.gif", "D:\\test.gif");
    if (CURLE_OK == res)
        cout << "download successfully.\n" << endl;
    else
        cout<<"cannot download.\n"<<endl;
    return 0;
}

谢谢!:)


3
如果你没有注意到,你第一个代码列表的写入函数总是将数据写入文件的开头。除非你的函数只被调用一次,否则我一点都不惊讶它与使用curl进行文件IO时不匹配。 - WhozCraig
我还注意到另一件事情。你创建了一个 fstream,但它只用于写入。为什么不使用 ofstream 呢?这样可以在编译时静态地确保你只使用它进行写入操作。此外,你还检查流是否打开,这是完全不必要的!只需使用 ofstream file(out, ios_base::binary); 即可。稍后,你明确地调用 close()clear() 流状态,这是多余的,因为之后你不再使用该流。相反,应该使用 flush() 函数刷新流,并验证流状态以进行错误检查。尽管如此,所有这些都无法解释你遇到的问题。 - Ulrich Eckhardt
顺便提一下,sizeof 的定义是返回以 char 为单位的大小,因此根据该定义,sizeof (char) 恰好为一。总是这样。 - Ulrich Eckhardt
ptr只是指向数据的指针 - 它与提供给回调函数的数据量无关。您可以查看文档,了解Curl如何将数据缓冲区的大小传递给回调函数。除了通过下载已知数据量的URL进行测试之外,没有其他方法可以“证明”传递给回调函数的数据量为size * nmemb - 这两个参数是Curl告诉您数据大小的方式。您只能相信Curl的说法。 - Michael Burr
1个回答

5
这个回调函数可能会在文件中被多次调用。你不应该每次函数被调用时都创建一个新的文件流——你应该使用用户数据参数将其传递进去。否则,你只会不断地覆盖文件开头的数据。
以下是一个示例实现:
size_t write_data(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    std::ofstream *out = static_cast<std::ofstream *>(userdata);
    size_t nbytes = size * nmemb;
    out->write(ptr, nbytes);
    return nbytes;
}

你还需要调整对curl_easy_setopt的调用,使用参数CURLOPT_WRITEDATA将文件流传递进去。确保在函数运行期间该流不会超出范围!
CURLcode download(char* url, char* out) {
    CURL *curl = NULL;
    std::ofstream output(out, ios::binary);
    CURLcode res;
    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &output);
        res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        return res;
    }
    else
    {
        return CURLE_FAILED_INIT;
    }
}

请原谅我如果我错了,但这不会创建内存泄漏吗?在函数返回之前,输出文件没有关闭吗? - silvergasp
当 std::ofstream 对象超出作用域并且其析构函数运行时,它就会关闭。 - Krzysztof Kosiński

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接