سؤال

هل هناك طريقة (عبر الأنظمة الأساسية) للحصول على مؤشر C FILE* من C++ std::fstream ؟

سبب سؤالي هو أن مكتبة C++ الخاصة بي تقبل fstreams وفي وظيفة معينة أرغب في استخدام مكتبة C التي تقبل FILE*.

هل كانت مفيدة؟

المحلول

الجواب القصير هو لا.

والسبب هو أن std::fstream ليس من الضروري استخدام أ FILE* كجزء من تنفيذه.لذلك، حتى لو تمكنت من استخراج واصف الملف من ملف std::fstream الكائن وقم بإنشاء كائن FILE يدويًا، فستواجه مشكلات أخرى لأنه سيكون لديك الآن كائنان مخزنان مؤقتًا يكتبان إلى نفس واصف الملف.

السؤال الحقيقي هو لماذا تريد تحويل std::fstream كائن في أ FILE*?

على الرغم من أنني لا أوصي بذلك، يمكنك محاولة البحث funopen().
لسوء الحظ، هذا هو لا واجهة برمجة تطبيقات POSIX (وهي امتداد BSD) لذا فإن إمكانية نقلها موضع تساؤل.وهذا أيضًا هو السبب وراء عدم تمكني من العثور على أي شخص قام بتغليف ملف std::stream مع كائن مثل هذا.

FILE *funopen(
              const void *cookie,
              int    (*readfn )(void *, char *, int),
              int    (*writefn)(void *, const char *, int),
              fpos_t (*seekfn) (void *, fpos_t, int),
              int    (*closefn)(void *)
             );

هذا يسمح لك ببناء FILE الكائن وتحديد بعض الوظائف التي سيتم استخدامها للقيام بالعمل الفعلي.إذا كتبت الوظائف المناسبة، فيمكنك جعلها تقرأ من ملف std::fstream الكائن الذي يحتوي بالفعل على الملف مفتوحًا.

نصائح أخرى

لا توجد طريقة موحدة.أفترض أن السبب في ذلك هو أن مجموعة توحيد C++ لا تريد افتراض أنه يمكن تمثيل مؤشر الملف على أنه fd.

يبدو أن معظم المنصات توفر طريقة غير قياسية للقيام بذلك.

http://www.ginac.de/~krekel/fileno/ يوفر كتابة جيدة للموقف ويوفر رمزًا يخفي كل الفظائع الخاصة بالمنصة، على الأقل بالنسبة لدول مجلس التعاون الخليجي.نظرًا لمدى خطورة هذا الأمر على دول مجلس التعاون الخليجي فقط، أعتقد أنني سأتجنب القيام بذلك معًا إن أمكن.

تحديث:شاهدJettatura ما أعتقد أنه أفضل إجابة https://stackoverflow.com/a/33612982/225186 (لينكس فقط؟).

إبداعي:

(ربما لا يكون عبر النظام الأساسي، ولكنه بسيط)

تبسيط الاختراق في http://www.ginac.de/~krekel/fileno/ (إجابة دفوراك)، والنظر إلى امتداد مجلس التعاون الخليجي هذا http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/api/a00069.html#a59f78806603c619eafcd4537c920f859، لدي هذا الحل الذي يعمل عليه GCC (4.8 على الأقل) و clang (3.3 على الأقل)

#include<fstream>
#include<ext/stdio_filebuf.h>

typedef std::basic_ofstream<char>::__filebuf_type buffer_t;
typedef __gnu_cxx::stdio_filebuf<char>            io_buffer_t; 
FILE* cfile_impl(buffer_t* const fb){
    return (static_cast<io_buffer_t* const>(fb))->file(); //type std::__c_file
}

FILE* cfile(std::ofstream const& ofs){return cfile_impl(ofs.rdbuf());}
FILE* cfile(std::ifstream const& ifs){return cfile_impl(ifs.rdbuf());}

ويمكن استخدام هذا،

int main(){
    std::ofstream ofs("file.txt");
    fprintf(cfile(ofs), "sample1");
    fflush(cfile(ofs)); // ofs << std::flush; doesn't help 
    ofs << "sample2\n";
}

محددات: (نرحب بالتعليقات)

  1. أجد أنه من المهم أن fflush بعد fprintf الطباعة إلى std::ofstream, وإلا فإن "sample2" سيظهر قبل "sample1" في المثال أعلاه.لا أعرف ما إذا كان هناك حل أفضل لذلك من الاستخدام fflush.بشكل ملحوظ ofs << flush لا يساعد.

  2. لا يمكن استخراج الملف* من std::stringstream, ، لا أعرف حتى إذا كان ذلك ممكنًا.(انظر أدناه للحصول على تحديث).

  3. ما زلت لا أعرف كيفية استخراج C stderr من std::cerr إلخ، على سبيل المثال للاستخدام في fprintf(stderr, "sample"), ، في رمز افتراضي مثل هذا fprintf(cfile(std::cerr), "sample").

فيما يتعلق بالقيد الأخير، الحل الوحيد الذي وجدته هو إضافة هذه الأحمال الزائدة:

FILE* cfile(std::ostream const& os){
    if(std::ofstream const* ofsP = dynamic_cast<std::ofstream const*>(&os)) return cfile(*ofsP);
    if(&os == &std::cerr) return stderr;
    if(&os == &std::cout) return stdout;
    if(&os == &std::clog) return stderr;
    if(dynamic_cast<std::ostringstream const*>(&os) != 0){
       throw std::runtime_error("don't know cannot extract FILE pointer from std::ostringstream");
    }
    return 0; // stream not recognized
}
FILE* cfile(std::istream const& is){
    if(std::ifstream const* ifsP = dynamic_cast<std::ifstream const*>(&is)) return cfile(*ifsP);
    if(&is == &std::cin) return stdin;
    if(dynamic_cast<std::ostringstream const*>(&is) != 0){
        throw std::runtime_error("don't know how to extract FILE pointer from std::istringstream");
    }
    return 0; // stream not recognized
}

محاولة التعامل iostringstream

من الممكن أن تقرأ مع fscanf من istream استخدام fmemopen, ، ولكن هذا يتطلب الكثير من حفظ الكتب وتحديث موضع إدخال الدفق بعد كل قراءة، إذا أراد المرء الجمع بين قراءات C وقراءات C++.لم أتمكن من تحويل هذا إلى cfile وظيفة مثل أعلاه.(ربما cfile فصل الذي يستمر في التحديث بعد كل قراءة هو الطريق الصحيح).

// hack to access the protected member of istreambuf that know the current position
char* access_gptr(std::basic_streambuf<char, std::char_traits<char>>& bs){
    struct access_class : std::basic_streambuf<char, std::char_traits<char>>{
        char* access_gptr() const{return this->gptr();}
    };
    return ((access_class*)(&bs))->access_gptr();
}

int main(){
    std::istringstream iss("11 22 33");
    // read the C++ way
    int j1; iss >> j1;
    std::cout << j1 << std::endl;

    // read the C way
    float j2;

    char* buf = access_gptr(*iss.rdbuf()); // get current position
    size_t buf_size = iss.rdbuf()->in_avail(); // get remaining characters
    FILE* file = fmemopen(buf, buf_size, "r"); // open buffer memory as FILE*
    fscanf(file, "%f", &j2); // finally!
    iss.rdbuf()->pubseekoff(ftell(file), iss.cur, iss.in); // update input stream position from current FILE position.

    std::cout << "j2 = " << j2 << std::endl;

    // read again the C++ way
    int j3; iss >> j3;
    std::cout << "j3 = " << j3 << std::endl;
}

حسنًا، يمكنك الحصول على واصف الملف - نسيت ما إذا كانت الطريقة هي fd() أو getfd(). التطبيقات التي استخدمتها توفر مثل هذه الأساليب، ولكن أعتقد أن معيار اللغة لا يتطلبها - لا ينبغي أن يهتم المعيار بما إذا كان نظامك الأساسي يستخدم fd للملفات.

ومن ذلك، يمكنك استخدام fdopen(fd, mode) للحصول على ملف*.

ومع ذلك، أعتقد أن الآليات التي يتطلبها المعيار لمزامنة STDIN/cin وSTDOUT/cout وSTDERR/cerr لا يجب أن تكون مرئية لك.لذا، إذا كنت تستخدم كلاً من fstream وFILE*، فقد يؤدي التخزين المؤقت إلى إفسادك.

أيضًا، إذا تم إغلاق fstream أو FILE، فمن المحتمل أن يقوموا بإغلاق fd الأساسي، لذلك تحتاج إلى التأكد من مسح كليهما قبل إغلاق أي منهما.

في تطبيق POSIX أحادي الترابط، يمكنك بسهولة الحصول على رقم fd بطريقة محمولة:

int fd = dup(0);
close(fd);
// POSIX requires the next opened file descriptor to be fd.
std::fstream file(...);
// now fd has been opened again and is owned by file

تنقطع هذه الطريقة في تطبيق متعدد الخيوط إذا كان هذا الرمز يتسابق مع سلاسل رسائل أخرى تفتح واصفات الملفات.

طريقة أخرى للقيام بذلك في Linux:

#include <stdio.h>
#include <cassert>

template<class STREAM>
struct STDIOAdapter
{
    static FILE* yield(STREAM* stream)
    {
        assert(stream != NULL);

        static cookie_io_functions_t Cookies =
        {
            .read  = NULL,
            .write = cookieWrite,
            .seek  = NULL,
            .close = cookieClose
        };

        return fopencookie(stream, "w", Cookies);
    }

    ssize_t static cookieWrite(void* cookie,
        const char* buf,
        size_t size)
    {
        if(cookie == NULL)
            return -1;

        STREAM* writer = static_cast <STREAM*>(cookie);

        writer->write(buf, size);

        return size;
    }

    int static cookieClose(void* cookie)
    {
         return EOF;
    }
}; // STDIOAdapter

الاستخدام، على سبيل المثال:

#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/device/file.hpp>

using namespace boost::iostreams;

int main()
{   
    filtering_ostream out;
    out.push(boost::iostreams::bzip2_compressor());
    out.push(file_sink("my_file.txt"));

    FILE* fp = STDIOAdapter<filtering_ostream>::yield(&out);
    assert(fp > 0);

    fputs("Was up, Man", fp);

    fflush (fp);

    fclose(fp);

    return 1;
}

هناك طريقة للحصول على واصف الملف من fstream ومن ثم تحويله إلى FILE* (عبر fdopen).شخصيا لا أرى أي حاجة لذلك FILE*, ، ولكن باستخدام واصف الملف، يمكنك القيام بالعديد من الأشياء المثيرة للاهتمام مثل إعادة التوجيه (dup2).

حل:

#define private public
#define protected public
#include <fstream>
#undef private
#undef protected

std::ifstream file("some file");
auto fno = file._M_filebuf._M_file.fd();

السلسلة الأخيرة تعمل مع libstdc++.إذا كنت تستخدم مكتبة أخرى، فسوف تحتاج إلى إجراء هندسة عكسية لها قليلًا.

هذه الخدعة قذرة وستكشف عن جميع أعضاء fstream الخاصين والعامين.إذا كنت ترغب في استخدامه في رمز الإنتاج الخاص بك، أقترح عليك إنشاء ملف منفصل .cpp و .h مع وظيفة واحدة int getFdFromFstream(std::basic_ios<char>& fstr);.يجب ألا يتضمن ملف الرأس fstream.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top