none
WinodowフォームアプリケーションでEXCELファイルのオープンが出来ない RRS feed

  • 質問

  •  

    菊次郎と申します。

     

    初めて質問させていただきます。
    WindowsフォームアプリケーションでEXCELのタイプライブラリを使用し、
    EXCELファイルのオープンをしたいと考えています。

    環境は、Visual C++ Express Edition 2005/EXCEL 2003を使用しています。

    色々ネットで調べて頑張ってみたのですが、どうしても出来ませんでしたので、
    ご教授願えないでしょうか?


    単純にEXCELを起動し、シートを追加することは出来ました。
     参考:http://blogs.wankuma.com/blue/archive/2007/01/16/56466.aspx

    ビルドはでき、コンパイルは通るのですが、F5で実行させると、
    pBook = pBooks->Open("C:\\test.xls");
    のところで、dbgheap.c の1473行目?(自分の環境だと1414行目)でASSERTが発生してしまいます。

    おそらく、pBooks->Open内のパラメータを省略してしまっていることに、
    原因があるとは思っているのですが、具体的に何を指定してよいのか分からず困っています。
    予想なので全然関係ないのかもしれませんが。。。

     


    ソースは以下になります。Form.hとは別にファイルで作成しています。

    #import "C:\Program Files\Common Files\Microsoft Shared\OFFICE11\MSO.DLL" no_namespace rename("DocumentProperties", "DocumentPropertiesXL") rename("RGB", "MSO_RGBXL")
    #import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\Vbe6ext.olb" no_namespace
    #import "C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE" rename("ReplaceText", "ReplaceTexXL") rename("CopyFile", "CopyFileXL") rename("DialogBox", "DialogBoxXL") rename("RGB", "RBGXL") rename("DocumentProperties", "DocumentPropertiesXL") exclude("IFont") exclude("IPicture") no_dual_interfaces

    using namespace System;
    using namespace Excel;
    _ApplicationPtr pXL;

     

    void CExcelOpe:SurprisepenExcel()
    {

        WorkbooksPtr pBooks;
        _WorkbookPtr pBook;
        _variant_t v(DISP_E_PARAMNOTFOUND, VT_ERROR);

        //Excelの起動
        pXL.CreateInstance(L"Excel.Application");
        pXL->Visible = TRUE;

        pBooks = pXL->Workbooks;
    //    pBook  = pBooks->Add(v);              /* ブックの追加は正常 */
        pBook = pBooks->Open("C:\\test.xls");   /* オープンが出来ない */

    }

    以上です。宜しくお願いします。

     

    2008年1月8日 11:43

回答

  • 変更箇所は2つ。

     

    プロジェクトプロパティの

    [構成プロパティ]-[全般]-[共通言語ランタイムサポート]

         「純粋 MSIL 共通言語ランタイム サポート(/crlStick out tongueure)」→「共通言語ランタイムサポート(/clr)」

    [構成プロパティ]-[リンカ]-[詳細]-[エントリポイント]

          「main」→「WinMainCRTStartup」

     

    次に (プロジェクト名).cppファイルの末尾に

     

    コード ブロック

      int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
      {
          return main(System::Environment::GetCommandLineArgs());
      }

     

     

    を追加。
    2008年1月9日 0:38
  • [構成プロパティ]-[リンカ]-[詳細]-[エントリポイント]

          「main」→「WinMainCRTStartup」

    が間違っていました。wWinMainCRTStartupにしてください。

    2008年1月10日 1:39
  • Value2プロパティで取得できるのはvariant_t型変数です。

    デバッグ時にステップイン実行してみるとわかると思いますが、Range::GetValue2内では正しく取得できているようです。

     

    おそらく、VARIANT型変数に直接入れると、Value2プロパティで値を返すとき一時的な_variant_t型の変数ができ、

    そこからVARIANT型にコピーするという動きになります。

    ここで、問題なのは「一時的な_variant_t型の変数」がコピーしたときに役目を終えてしまうということだと思います。


    http://msdn2.microsoft.com/en-us/library/k74e1xsh(VS.80).aspx
    のfCopy引数でコピーしたときにメモリを取り直したものをコピーさせるかという指定ができます。

    Range::GetValue2メソッドで

    return _variant_t(_result, false);

    となっています。

    ここでコピーしたものをVARIANT型変数に入れるとメモリはコピーされません。(ポインタの値のみコピーされるのかな)

    で、コピーした後「一時的な_variant_t型の変数」が破棄されます。

    ここで_variant_tクラスのデストラクタでメモリの解放がおそらく行われ、VARIANT型変数にコピーされている値はすでに

    破棄された領域を参照してしまうため正しい値が取れなくなるのでしょう。

     

    取得のみであれば、variant_t型の変数で受けておけば無難だと思います。

    2008年1月14日 9:13

すべての返信

  • ブログの(2007/07/07の)コメントに解決方法が書いてありますよ。

    2008年1月8日 12:19
  • 蒼の洞窟さん回答ありがとうございます。

     

    ご紹介してくださったサイトを見ました。
    ですが、ほとんど素人なので、書いてある内容がよくわかりませんでした。。。

     

    以下に記載したことをやれば出来るみたいなのですが。。
    申し訳ないですが、どういうことか教えてもらえないでしょうか?
    (プロジェクトのプロパティ部分の何を変更してよいかよくわかりません。)

     

    #引用
      2w.) /clr - /SYSTEM:WINDOWS - Wilted FlowerWinMain(<unmanaged args>) - Wilted FlowerWinMainCRTStartup();
      (タイプライブラリを使うので/clr)
      で、/ENTRYはWilted FlowerWinMainCRTStartupにして、
     
      int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
      {
          return main(System::Environment::GetCommandLineArgs());
      }

     

    2008年1月8日 15:40
  • 変更箇所は2つ。

     

    プロジェクトプロパティの

    [構成プロパティ]-[全般]-[共通言語ランタイムサポート]

         「純粋 MSIL 共通言語ランタイム サポート(/crlStick out tongueure)」→「共通言語ランタイムサポート(/clr)」

    [構成プロパティ]-[リンカ]-[詳細]-[エントリポイント]

          「main」→「WinMainCRTStartup」

     

    次に (プロジェクト名).cppファイルの末尾に

     

    コード ブロック

      int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
      {
          return main(System::Environment::GetCommandLineArgs());
      }

     

     

    を追加。
    2008年1月9日 0:38
  • 蒼の洞窟さん

     

    回答が遅れて申し訳ありません。
    丁寧に教えてくださってありがとうございます。

     

    無事EXCELファイルのオープンが出来ました。


    int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
    {
        return main(System::Environment::GetCommandLineArgs());
    }

     

    上記の記載だと、うまくいかなかったので、下記のようにしたところうまくいきました。

     

    int __stdcall WinMain(int hInstance, int hPrevInstance, void *lpCmdLine, int nShowCmd)
    {
        return main(System::Environment::GetCommandLineArgs());
    }

     

     

    また何かありましたら質問させていただくかもしれませんが、そのときはよろしくお願いします。

     

    2008年1月9日 11:36
  • 上記の記載だと、うまくいかなかったので、下記のようにしたところうまくいきました。

    プロパティの[全般]-[文字セット]を「マルチバイト文字セットを使用する」に変更していませんか?

    (CLRならUnicode文字セットを使うにしておいたほうが無難そうだが、、、)
    2008年1月9日 16:36
  • 蒼の洞窟さん

     

    気にかけてくださりありがとうございます。

    文字コードは特に変更していなく、unicodeのままです。

     

    コンパイルしたところ、下記エラーが発生してしまいました。

    何かインクルードしないといけないものがあるのでしょうか?

    (ものすごい基本的なことかもしれませんが分からないので。。。)

     

    ・記載方法

    int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
    {
        return main(System::Environment::GetCommandLineArgs());
    }

     

    ・エラー内容

    .\test.cpp(21) : error C2146: 構文エラー : ';' が、識別子 'wWinMain' の前に必要です。
    .\test.cpp(21) : error C2065: 'HINSTANCE' : 定義されていない識別子です。
    .\test.cpp(21) : error C2146: 構文エラー : ')' が、識別子 'hInstance' の前に必要です。
    .\test.cpp(21) : error C4430: 型指定子がありません - int と仮定しました。メモ: C++ は int を既定値としてサポートしていません
    .\test.cpp(21) : error C2059: 構文エラー : ')'
    .\test.cpp(22) : error C2143: 構文エラー : ';' が '{' の前にありません。
    .\test.cpp(22) : error C2447: '{' : 対応する関数ヘッダーがありません (旧形式の仮引数リスト?)

     

     

    2008年1月9日 23:15
  • あーわかりました。

    windows.hをインクルードしないとだめでしたね。

    2008年1月10日 0:31
  •  

    蒼の洞窟さん

     

    回答ありがとうございます。
    教えていただいたwindows.hをstdafx.hに追加しましたが、
    今度は下記のリンクエラーとなってしまいました。。。

    MSVCRTD.lib(crtexew.obj) : error LNK2019: 未解決の外部シンボル _WinMain@16 が関数 ___tmainCRTStartup で参照されました。

     

    まだ何か追加で設定が必要になるのでしょうか??

     

    2008年1月10日 1:23
  • [構成プロパティ]-[リンカ]-[詳細]-[エントリポイント]

          「main」→「WinMainCRTStartup」

    が間違っていました。wWinMainCRTStartupにしてください。

    2008年1月10日 1:39
  • 蒼の洞窟さん

     

    無事コンパイルでき、エクセルのオープンも出来ました!

    ご丁寧にありがとうございました。

     

    みなさんの知識の豊富さ、理解度には驚くばかりです。

    もっと勉強しなくてはと反省しました。。

     

    また何かあったらよろしくお願いします。

    2008年1月10日 3:22
  •  

    蒼の洞窟さんのおかげで、エクセルの操作が出来るようになったのですが、
    エクセルのデータをセルのXX~XXまで読み込みたい場合に、
    どのように格納してよいのか分からなく、困っています。。
    (VARIANTもしくは、SAFEARRAY型に指定したセルの読み込んだデータを
      格納したいと思っています。)

     

    MFCを使用した場合の方法が記載されているサイトはたくさんあるのですが、
    MFCを使用しない(COleVariant型を使用しない)方法が分からないです。。。

     

    以下のサイトにあるように書き込みの逆を単純に行えば出来ると思って、
    色々試してみたのですが、どうもうまく格納できません。
    http://www-online.kek.jp/~keibun/pukiwiki/index.php?Excel%A4%CB%C7%DB%CE%F3%A4%F2%C5%CF%A4%B9

     

    また質問になってしまうのですが、何かご教授していただけないでしょうか?
    別スレッドにした方が良いのかもしれないですが、、、

     

    サンプルコードは以下になります。
    ★の部分が特に不明な箇所です。

     

    コード ブロック

    #include "StdAfx.h"

     

    #import "C:\Program Files\Common Files\Microsoft Shared\OFFICE11\MSO.DLL" no_namespace rename("DocumentProperties", "DocumentPropertiesXL") rename("RGB", "MSO_RGBXL")
    #import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\Vbe6ext.olb" no_namespace
    #import "C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE" rename("ReplaceText", "ReplaceTexXL") rename("CopyFile", "CopyFileXL") rename("DialogBox", "DialogBoxXL") rename("RGB", "RBGXL") rename("DocumentProperties", "DocumentPropertiesXL") exclude("IFont") exclude("IPicture") no_dual_interfaces

     

    #include "ExcelOpe.h"

     

    #define ROWS 5
    #define COLS 5

     

    // 以下Excel操作
    using namespace Excel;
    _ApplicationPtr pXL;

     

    void CExcelOpe::SetExcel(int Page, VARIANT* arr)
    {
        long index[2];

        CoInitialize(NULL);

     

        //Excelの起動
        pXL.CreateInstance(L"Excel.Application");
        pXL->Visible = TRUE;

     

        //ファイルを開く
        WorkbooksPtr pBooks = pXL->Workbooks;
        _WorkbookPtr pBook  = pBooks->Open("c:\\test.xls");

     

        /* アクティブワークシートを取得 */
        pBook = pXL->GetActiveWorkbook();

     

        /* シートの選択 */
        VARIANT vt;
        ::VariantInit(&vt);
        vt.vt = VT_I2;
        vt.iVal = (short)Page;
        _WorksheetPtr pSheet =  pBook->GetSheets()->GetItem(vt);

     

        // Create SafeArray and fill.
        SAFEARRAYBOUND Bounds[2];
        Bounds[0].lLbound = 0;      /* 最小 */
        Bounds[0].cElements = ROWS; /* 最大 */
        Bounds[1].lLbound = 0;
        Bounds[1].cElements = COLS;

        arr->vt = VT_ARRAY | VT_VARIANT;
        arr->parray = SafeArrayCreate(VT_VARIANT, 2, Bounds);

     

        /* VARIANT型のarrにサンプルデータを格納 */
        int k = 0;
        for(int i = 0; i < ROWS; i++)
        {
            index[0] = i;
            for(int j = 0; j < COLS; j++)
            {
                index[1] = j;
                VARIANT tmp,aaa;
                tmp.vt = VT_I4;
                tmp.lVal = k;
                SafeArrayPutElement(arr->parray, index, &tmp);
                SafeArrayGetElement(arr->parray, index, &aaa);
                k += 1;
            }
        }

     

        /* エクセルのA1からE5の範囲にarrを書き出す */
        pSheet->Range["A1"]["E5"]->Value2 = *arr;  /* 書き込みはOK */

     

     

        /* 実験 */
        VARIANT arr2,tmp2,arr3,tmp3,tmp4;

        /* ★エクセルのデータを読み出す方法が分からないです。。。 */
        arr2 = pSheet->Range["A1"]["E5"]->Value2;  /* arr2に値が入っていないように見える */

        arr3 = *arr;                               /* 単純にarrをarr3に格納することは出来る */

     

        SafeArrayGetElement(arr2.parray, index, &tmp2);   /* tmp2に値が入らない/入ってないように見える */

     

        SafeArrayGetElement(arr3.parray, index, &tmp3);   /* tmp3に想定どおりセルE5の24が入っている */

        tmp4 = pSheet->Range["A4"]->Value;      /* 単純に1セルの読み出しはこれで出来る */

     

        //Excelを閉じる
        pXL->DisplayAlerts = FALSE;
        pXL->Quit();

        pXL = NULL;
        CoUninitialize();

        return;
    }

     

     

    すみませんが、アドバイス宜しくお願いします。

     

     

    2008年1月11日 16:52
  • Value2プロパティで取得できるのはvariant_t型変数です。

    デバッグ時にステップイン実行してみるとわかると思いますが、Range::GetValue2内では正しく取得できているようです。

     

    おそらく、VARIANT型変数に直接入れると、Value2プロパティで値を返すとき一時的な_variant_t型の変数ができ、

    そこからVARIANT型にコピーするという動きになります。

    ここで、問題なのは「一時的な_variant_t型の変数」がコピーしたときに役目を終えてしまうということだと思います。


    http://msdn2.microsoft.com/en-us/library/k74e1xsh(VS.80).aspx
    のfCopy引数でコピーしたときにメモリを取り直したものをコピーさせるかという指定ができます。

    Range::GetValue2メソッドで

    return _variant_t(_result, false);

    となっています。

    ここでコピーしたものをVARIANT型変数に入れるとメモリはコピーされません。(ポインタの値のみコピーされるのかな)

    で、コピーした後「一時的な_variant_t型の変数」が破棄されます。

    ここで_variant_tクラスのデストラクタでメモリの解放がおそらく行われ、VARIANT型変数にコピーされている値はすでに

    破棄された領域を参照してしまうため正しい値が取れなくなるのでしょう。

     

    取得のみであれば、variant_t型の変数で受けておけば無難だと思います。

    2008年1月14日 9:13
  • 蒼の洞窟さん。またまたご回答ありがとうございます。

     

    _variant_tにしたところ、データの取得ができました。

    解説まで丁寧に説明していただき、ありがとうございます。

    (SafeArrayGetElementで取得する際のindexが0からだったのが1に変わってしまったのが、

      単純な疑問として残りましたが、使用するには全く問題ないです。)

     

    最近やっとC++をはじめたばかりで、知識不足であまり理解せず、

    サンプルを元にこうかなという感じでやっていましたが、

    蒼の洞窟さんのように理解/解析できるようになれたらなと思いました。

     

    本当にありがとうございます。

    2008年1月14日 10:36