none
CFile::Openの第一引数(LPCTSTR lpszFileName)へ渡す文字列の扱い方について RRS feed

  • 質問

  • XPのパソコンで、visual studio 2008 MFC ダイアログベースをやってます。ファイル操作をしようと思い、以下のようなソースコードを書いて実験しています。パス名とファイル名を取得したあとにCFile::Openへ渡すわけですが、失敗するときと成功するときの違いが把握できずにいます。LPCTSTR lpszFileNameを、どう取得し、扱えば良いのか教えて頂けきたいと思います。よろしくお願いします。

    -------------------------------------------------------------------------------------------------------------------------------------

    #define DIRECTTEST //この行を生かしたり、コメントアウトすることで、ソースコードを変更しています。
    #define DISP_NAME //この行を生かしたり、コメントアウトすることで、ソースコードを変更しています。

    CString Source_FileName;
    CString Read_FileName;
    static LPCTSTR Name;
    CFile Open_File;

    /*
    GetOpenFileNameを使って、OPENFILENAME型の OpenFileNameへファイルパス、ファイル名を代入
    */

    ifdef #DIRECTSET
      Name = _T("C:\\Documents and Settings\\abcde\\My Documents\\Visual Studio 2008\\Projects\\Test_MFC_Dialog\\TestFile.txt");
    Read_FileName = Name;

    #else
     //ファイル名の取得 GetBuffer
     Read_FileName.GetBuffer(100);
     Read_FileName = OpenFileName.lpstrFile;
     Name = OpenFileName.lpstrFile;
    #endif

     Open_File.Open( Name, Open_File.modeReadWrite, NULL );

    フォルダ名とファイル名を、ダイアログ上に配置したテキストボックスへ表示する。
     int Last_Yen = Read_FileName.ReverseFind( _T('\\') ) - 1;
     static int Length = Read_FileName.GetLength() - 1;
     m_ReadFolerName.SetWindowText( Read_FileName.Left(Last_Yen + 1) );
     m_ReadFileName.SetWindowText( Read_FileName.Right(Length - Last_Yen - 1));

     BYTE ReadBuf[4096];
      tatic int ReadBytCnt = Open_File.Read( ReadBuf, 4096);
     

    というようなソースコードを書いて、#define DIRECTTESTを有効にしたり、コメントアウトしたりしながら動きを見ています。すると、条件によってエラーでとまることがあります。しかし、どちらの場合も、Nameにはフォルダ名、ファイル名を意味する同じ文字列が代入されていました。その文字列は、ifdef #DIRECTSETと#elseに挟まれた行のc:\からTestFile.txtまでの文字列です。


    <#define DIRECTTEST を有効にする>
    Nameへじかに文字列を代入したときです。Open_File.Openが正常に実行されました。(exの中身はCFileException::none)

    <#define DIRECTTEST をコメントアウトする>
    OpenFileName.lpstrFileの中身をNameへ代入した場合です。Open_File.Openを実行したときにエラーが出て、そのエラーメッセージは、
     Test_My_Dialog.exe の 0x7c96a93b でハンドルされていない例外が発生しました: 0xC0000005:
      場所 0x00030ffc に書き込み中にアクセス違反が発生しました。
    です。

    見かけ上、Nameの中身はどちらの場合も同じ文字列が入っているのですが、Openの実行結果に違いが出るのはなぜでしょうか?ただ、どちらの場合でもReadBytCntは0のままで、読み取りができていないというあまり嬉しくない共通点があるにはあるのですが、まずはファイルを開くことを実現したいと思います。

    2010年11月21日 13:59

回答

  • DIRECTSET が定義されていない場合の処理部分がめちゃくちゃです。

    Read_FileName はCStringオブジェクトです。

    ここに、OpenFileName の結果をコピーしてきたいのであれば、

    Read_FileName = OpenFileName.lpstrFile; 
    

    としてください。Name もわかりやすいようにするなら、

    Name = Read_FileName;
    

    とするのがいいでしょう。

    ただし、いずれの場合も

    OpenFileName.lstrFile = Read_FileName.GetBuffer(MAX_PATH);
    

    としていないことが前提です。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク AnalogTerebi 2010年11月24日 10:47
    2010年11月22日 4:57

すべての返信

  • 確認です。

    OPENFILENAME.lpstrFileのBufferは自分で確保してGetOpenFileNameを使用してしますか?

    そのBuffer Sizeは256文字以上の文字が収められるBuffer Sizeですか?

    GetOpenFileName周りの処理はどうなっているのでしょうか?

     

    >見かけ上、Nameの中身はどちらの場合も同じ文字列が入っている

    見かけ上とはDebugger上での確認でしょうか?たまたま開放済みや未初期化のMemory上にそれらしき文字列が入っていることはあります。

     

    >Read_FileName.GetBuffer(100);
    この処理は不必要に思えます。

    ちなみにMFCを利用しているなら、CFileDialogを利用したほうが楽ですよ。

    2010年11月21日 15:30
  • DIRECTSET が定義されていない場合の処理部分がめちゃくちゃです。

    Read_FileName はCStringオブジェクトです。

    ここに、OpenFileName の結果をコピーしてきたいのであれば、

    Read_FileName = OpenFileName.lpstrFile; 
    

    としてください。Name もわかりやすいようにするなら、

    Name = Read_FileName;
    

    とするのがいいでしょう。

    ただし、いずれの場合も

    OpenFileName.lstrFile = Read_FileName.GetBuffer(MAX_PATH);
    

    としていないことが前提です。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク AnalogTerebi 2010年11月24日 10:47
    2010年11月22日 4:57
  • kozzさん、ありがとうございます。返事が遅くなって済みません。

    OPENFILENAME.lpstrFileのBufferは自分で確保してGetOpenFileNameを使用してしますか?

    そのBuffer Sizeは256文字以上の文字が収められるBuffer Sizeですか?

      MAX_PATHで確保しています。

    GetOpenFileName周りの処理はどうなっているのでしょうか?

     BOOL Result = Get_FileName(&OpenFileName);

    というふうになっていて、

    CTest::Get_FileName
    {
           TCHAR FileName[MAX_PATH]; 

            FileName[0] = '\0';
            ::ZeroMemory( Arg_OpenFileName, sizeof(*Arg_OpenFileName) );
            Arg_OpenFileName->lStructSize = sizeof(*Arg_OpenFileName);
            Arg_OpenFileName->Flags = 0;

            Arg_OpenFileName->lpstrFile = FileName;
            Arg_OpenFileName->nMaxFile = sizeof(FileName);
            Arg_OpenFileName->lpstrInitialDir = DefaultPath;
            //Arg_OpenFileName->lpstrFilter = (LPCWSTR)( _T("ini\0" "*.ini\0" "txt\0" "*.txt\0" "all\0" "*.*\0") );
            Arg_OpenFileName->lpstrFilter = (LPCWSTR)( _T("*.txt\0") );
            Arg_OpenFileName->nFilterIndex = 1;
           
            return ::GetOpenFileName(Arg_OpenFileName);
    }

    という具合になっています。(元ネタはどこかのホームページです。)

    >見かけ上、Nameの中身はどちらの場合も同じ文字列が入っている

    見かけ上とはDebugger上での確認でしょうか?たまたま開放済みや未初期化のMemory上にそれらしき文字列が入っていることはあります。 

    はい。ウォッチで見た結果を見て、「見かけ上、同じ文字列」だと思っています。もともと自動変数などでは正確にウォッチすることができないことも多いので、一応、デバッグ用にグローバル変数を用意したり、一時的にstatic宣言に変えてみたりしてはいるのですが・・・。 

    ちなみにMFCを利用しているなら、CFileDialogを利用したほうが楽ですよ。

     それは知りませんでした。これから情報を探してみます。

     

    2010年11月22日 11:29
  • とっちゃんさん、有難うございます。

    返事が遅くなり済みません。

    >OpenFileName.lstrFile = Read_FileName.GetBuffer(MAX_PATH);
    >としていないことが前提です。

    これは、、MAX_PATH以外の値なら何でもよいということですか?誤解があれば、お手数ですが教えて頂ければ幸いです。

    よろしくお願いします。

    2010年11月22日 11:33
  • TCHAR FileName[MAX_PATH]; はLocal 変数なので、Get_FileName()関数を抜けたら解放されます。

    Get_FileName()の呼び出し側でBufferの確保と解放を行うなど、Bufferの生存期間を見直してください。

    2010年11月22日 11:45
  • 違います。

    MAX_PATH以外ならではなく、GetBufferの戻り値の使い方が問題になります。

    OpenFileName.lpstrFile = Read_FileName.GetBuffer( 1000 );
    // ファイルダイアログの呼び出し
    Read_FileName = OpenFileName.lpstrFile;
    

    CStringがこのような呼び出しを認めておらず、ReleaseBufferを呼び出す前に代入しようとしてはいけない決まりになっています。

    そのため、「としていないことが」と書いています。

    CベースからC++に変更しているのかもしれませんが、急がば回れということわざもあるのように、まずはC++の基本的なところをきっちり抑えることと、各クラスの利用方法をきちんと学ぶことをお勧めします。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2010年11月22日 14:39
  •  とっちゃんさん、ありがとうございます。

    MAX_PATH以外ならではなく、GetBufferの戻り値の使い方が問題になります。
    OpenFileName.lpstrFile = Read_FileName.GetBuffer( 1000 );
    // ファイルダイアログの呼び出し
    Read_FileName = OpenFileName.lpstrFile;
    CStringがこのような呼び出しを認めておらず、ReleaseBufferを呼び出す前に代入しようとしてはいけない決まりになっています。

    済みませんが、 とっちゃんさんの説明を理解できない(原因はこちら側にあります。)のですが、どこかにそういう整理された情報はないでしょうか?

     http://msdn.microsoft.com/ja-jp/library/8a994dfk(v=VS.90).aspx

     というあたりは見ているのですが、とっちゃんさんの説明と結び付けて読み取ることができません。例えば、「ReleaseBufferを呼び出す前に代入しようとしてはいけない」という決まりはどこを見れば分かりますか?

     

     

    CベースからC++に変更しているのかもしれませんが、急がば回れということわざもあるのように、まずはC++の基本的なところをきっちり抑えることと、各クラスの利用方法をきちんと学ぶことをお勧めします。

     おっしゃることが正論なのはよく分かるのですが、私は、学生の授業(実験)や趣味の一環でやっているわけではないので、正論ばかり振りかざしていても事が進まないという現実があるのです。

    済みません。
    2010年11月23日 3:28
  • 例えば、「ReleaseBufferを呼び出す前に代入しようとしてはいけない」という決まりはどこを見れば分かりますか?

    CSimpleStringT.GetBuffer メソッドに「CSimpleStringT の他のメンバ メソッドを使用する前に、ReleaseBuffer を呼び出す必要があります」という記載があります。
    代入も operator = を呼び出す処理なので包含されると解するべきかと思います。
    なお、CStringT クラスは CSimpleStringT クラスの派生クラスです。
    http://msdn.microsoft.com/ja-jp/library/kt26tkzx(VS.80).aspx

    私は、学生の授業(実験)や趣味の一環でやっているわけではないので、正論ばかり振りかざしていても事が進まないという現実があるのです。

    そのことによって生じるリスクを容認しているのですよね?

    発生しうるリスク(脅威)としては、調査工数が通常よりもかかる、テストや発売後に致命的な不具合が発見されるなどでしょうか。
    前者はフォーラムベースで聞いている時点で数日を要していることから現実化していますし、今後も発生確率が高いと予見されます。
    後者はコンパイルしてある程度動けば OK と判断して、動作仕様などの裏付けをとらなかった場合、基礎を押さえていなかったがために見逃された問題が潜在している危険性です。基礎を押さえた・裏付けをとったからといってなくならないものですが、発生確率は変化します。

    これらのリスクへの対策として、事前に学習時間をとる、経験豊富な技術者を同じチームに加えるなどがあるかもしれません。
    時間がないのであれば、MFC 経験者を加えてすぐ聞けるようにするといった対策なら即効性があるかもしれません。
    人材難、費用面の問題であれば、その対策を打てませんが、対策を打たないことによる影響・可能性・損害を容認しているのであれば私からは何とも言えません。
    (影響(損害額) * 発生確率 と対策コスト次第)

    長々と書きましたが、「現場の担当者レベルでは何もできない!」という可能性もあります。
    せめて、チームメンバー(同僚)、リーダー(上司)などには「***という懸念があるので学習時間を確保したい」といった申し出をしておき、断られた・却下されたら、「***という事態に今後なるかもしれない」ということを共有し、責任を分担しましょう。
    個人の責任として問われると後々しんどいので…。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年11月23日 4:53
    モデレータ
  •  Azuleanさん、ありがとうございます。

    済みません、

    「CSimpleStringT の他のメンバ メソッドを使用する前に、ReleaseBuffer を呼び出す必要があります」

    なのですが、「GetBuffer で返されたポインタを使用して」という書きだして始っていると思います。これから使うものを、なんで解放する(=、ReleaseBuffer を呼び出す)必要があるのでしょうか?この説明文の日本語表現が、これから使う予定のもの(GetBufferでとったもの)を使う前に開放している(ReleaseBuffer をしている)ように読めてしまい、日本語として理解できないのです。

     --------------------------------------------------------------------------------------------------------------------------------------------------------------

    一つは言えることは、販売目的ではないソフト(社内利用)なので、世間様に迷惑はかけないで済むはず、ということです。目下、金ナシ、人ナシです。過去にはwindowsに詳しい人がいましたが、今は誰もいません。

    VC++に関しては今回初めて携わったド素人ですが、私も技術屋のはしくれですので、

    「対策を打たないことによる影響・可能性・損害」

    という話は持ち出しても通じる相手(上司、会社)ではありませんでした。これ以上は、会社のことですのであまり詳しくは書けません。 

    (ここまで突っ込んだ話を出てくるとは思いませんでした)

    2010年11月23日 5:56
  • GetBufferのヘルプの書き出しは、

    GetBuffer で返されたポインタを使用して文字列の内容を変更する場合は、CSimpleStringT の他のメンバ メソッドを使用する前に、ReleaseBuffer を呼び出す必要があります。」

    とあります。ちゃんと読みましょう。

    また、代入したらおかしくなるということについては、ReleaseBufferのリファレンス http://msdn.microsoft.com/ja-jp/library/32x198c7(VS.80).aspx にあります。GetBufferと対になるものですし、ReleaseBufferを呼び出す前にということは私も書いています。

    わからないのなら、わからないと言っていただいて構いません。ですが、素人を自負するのなら、出てきた用語から引けるリファレンスくらいは一通り読みましょう。質問のやり取りをしてる時間で充分読むだけの時間はあったはずですし、回答側は、いちいちここのメソッド類の説明までしたりしません。

    また、正論ばかりといわれてしまいそうですが、ほかに書きようがないんで。。。頑張ってください。

     


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2010年11月23日 7:17
  • とっちゃんさん、ありがとうございます。

     

    私の頭で

    GetBuffer で返されたポインタを使用して文字列の内容を変更する場合は、CSimpleStringT の他のメンバ メソッドを使用する前に、ReleaseBuffer を呼び出す必要があります。」

    の日本語を理解するには、ちょっと時間がかかりそうです。このまま文字による会話だけを続けて、私がこの内容を理解することは今の私には無理だと思いますので、このスレッドをはここで閉じておこうと思います。

    2010年11月24日 10:46