none
EXCELマクロからのDLL(.NET)呼び出し RRS feed

  • 質問

  • お世話になっております。

    EXCELのマクロからDLL呼び出しを行うアプリケーションの改修を行っています。
    従来はマクロからDLL1(VC++で作成)からDLL2(VC++で作成)を呼び出していました。
    このDLL2をDLL3(C#で作成)したものをに置き換えて動かそうとしています。
    DLL3はCOMコンポーネントとして登録をしてDLL2から呼び出しています。

    別のWindowsアプリケーション(VC++作成)からはDLL1→DLL3の呼び出しが正常に行われて稼動しているのですが、EXCELマクロからの呼び出しでは実行時エラーが発生してしまいます。

    そこで、マクロからC++DLL、C#DLLを呼び出すサンプルを作成して動かしてみましたが、同様のエラーが発生しました。
    EXCELマクロから.NETDLLを呼び出す場合は、C++アプリから呼び出す場合とは別のコーディングまたは設定を行う必要があるのでしょうか?

    サンプルコードを以下に示します。

    よろしくお願いいたします。

    [Excelマクロのサンプルコード]

    Declare Function MSGDllTest Lib "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" () As Long

    Sub test()

        Call MSGDllTest

    End Sub


    [C# DLLのサンプルコード]

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Windows.Forms;

    namespace CLR_DLL

    {

        public interface IMsgBoxDLL

        {

            void ShowMessage1();

        }

        public class MsgBoxDLL:IMsgBoxDLL

        {

            public MsgBoxDLL()

            {

                MessageBox.Show("[C#DLL]コンストラクタ");

            }

            public void ShowMessage1()

            {

                MessageBox.Show("[C#DLL]publicメソッドその1呼び出し");

            }

        }

    }


    2008年11月14日 7:20

回答

  • こんにちは!(^^)!ふ~です。

     

    EXCELマクロ -> VC++ DLL -> C# DLL (COM)  : ×

    最近は、EXCELマクロから直接C#(COM)を呼び出す設計になっているようです。

     

    ご参考資料 C#で作成したアセンブリをExcelから呼び出す。
    http://d.hatena.ne.jp/akiramei/20071227/1198771429

     

    多少キャプチャ画面が古いと思いますので補足します。(不要なソースも修正)

    <プロジェクトのプロパティより>

    1) アプリケーション ‐> アセンブリ情報‐>アセンブリをCOM参照にするを選択する。

    2)ビルド ‐> COM相互運用機能の登録を選択する

     

    Code Snippet

    <C#のクラスライブラリを作成する>

    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace ASExcelCmd
    {
     public class calc
     {
      public int Add(int x, int y)
      {
       return x + y;
      }

      public int Sub(int x, int y)
      {
       return x - y;
      }
     }
    }

     

    <Excel側のマクロで呼び出す。>

    Public Sub Test()
      Dim tcalc As New ASExcelCmd.calc
      ans = tcalc.Add(10, 30)
      MsgBox ans
    End Sub

     

     

    ご提案としまして、VC++で作成しましたラッパ部分を取り除き、新規作成しましたC#のCOMを直接Excelマクロより呼び出すことが適当と思われます。

    どうしてもVC++からC#を呼び出す場合、通常は、Managed C++からC#を呼び出す設計が普通のようです。

    しかし、現状でも、C#のCOMがExcelマクロで起動できる事を確認し、「VC++でラッパすると失敗する。」この追及も面白いと思います。

    2008年11月18日 6:42
  •  masakiy さんからの引用

    [Excelマクロのサンプルコード]
    Declare Function MSGDllTest Lib "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" () As Long

    Sub test()
        Call MSGDllTest
    End Sub


     masakiy さんからの引用

    [VC++ DLLのサンプルコード]
    __declspec(dllexport)       
    void __stdcall MSGDllTest() {    

    見比べると何かかおかしくありませんか?

    VBAにおけるFunctionは戻り値のある関数を示します。またAs Longと末尾につけることで、C++でのint型を期待していますよね?
    対して、C++側のコードは戻り値のない関数になっています。
    これが原因とは言い切れませんが、直しておいた方が良いです。

     

     

    -----

    突っ込みをするのはあまり良くないかもしれないんだけど、気になったので書いておきます。

     

     !(^^)!ふ~ さんからの引用

    最近は、EXCELマクロから直接C#(COM)を呼び出す設計になっているようです。

    それは要件や状況によりますので、絶対ではありません。

     

     !(^^)!ふ~ さんからの引用

    どうしてもVC++からC#を呼び出す場合、通常は、Managed C++からC#を呼び出す設計が普通のようです。

    VS2005の環境でやるのであれば、Managed C++はほぼ使いません。代わりにC++/CLI(C++/CLRとも言う)を使用します。

    言語構造が結構変わっていて、やりやすくなっています。

     

    なお、C++/CLIでC#のモジュールを呼び出しつつ、ネイティブの関数としてエクスポートすることで、C#のDLLをCOMにする必要がなくなりメリットはあります。

    2008年11月18日 14:47
    モデレータ
  • こんばんは!(^^)!ふ~です。

    私の発言で、お仕事が余分に増えてしまっても困りますのでテストしてみました。

    結果は、『Excelから、VC++DLL経由C#のCOM』も動作しました。

     

    最初、私のMicrosoftExcel2002 SP3では、COMアドイン
    が動作していない為下記エラーで止まりました。

    <エラー内容>
    Microsoft Visual C++ Runtime Library

    Runtime Error!
    Progran:C\Program Files\Microsoft Office\Offce10\EXCEL.EXE

    This apprication has requested the Runtime to tetminate it in an unusual way.
    Pleease contact the apprication's support team for more information.

     

    しかし、『MicrosoftExcel2007』では、問題なく動作致しました。
    ヘルプのオプションや、ヘルプのバージョン、システム情報でCOMアドインが
    可能であるか判断できるようです。

    折角ですから成功したソースを記述します。

     

    ◆エクセルのVBAソース
    Option Explicit
    Declare Sub MSGDllTest Lib "C:\VS2005\Projects\ASExcelDllTestMain\debug\VCCExcelDll.dll" _
        Alias "_MSGDllTest@0" ()

    Sub test()
        Call MSGDllTest
    End Sub

    <作成方法>
    1.VCCExcelDll.mapの中の"_MSGDllTest@0"を使う
    2.CLR_DLLは参照設定して有ります。
        参照状態試験用コード(直接呼ぶ)
        Public Sub test()
        Dim mstest As New CLR_DLL.MsgBoxDLL
        Call mstest.ShowMessage1
        End Sub

     

    ◆VC++Win32 コンソールのメインソース (これもテスト用です)
    // ASExcelDllTestMain.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
    //

    #include "stdafx.h"

    #define DLL_IMPORT __declspec(dllimport)

     

    extern "C" {
     DLL_IMPORT void __stdcall MSGDllTest();
    }

     

    // VC++ メイン処理
    int _tmain(int argc, _TCHAR* argv[])
    {
        MSGDllTest();
        return 0;
    }
    <作成方法>
    1.全てのプロジェクトをASExcelDllTestMainへ追加登録してまとめる

     

    ◆VC++のWin32DLLのソース(ここがラッパです)
    ////////////////////////////////////////////////////////////////////////////
    // アンマネージDLLより、マネージCOMを呼び出す     //
    ////////////////////////////////////////////////////////////////////////////

    // VCCExcelDll.cpp : DLL アプリケーションのエントリ ポイントを定義します。
    //

    #include "stdafx.h"

     

    // VC++のCOM から .NET 型を参照する( ネームスペースなし、GUID構造体を使ったCLSID、COMインターフェース用)
    #import "..\CLR_DLL\bin\Debug\CLR_DLL.tlb" no_namespace named_guids raw_interfaces_only

     

    #define DLL_EXPORT __declspec(dllexport)

     

    extern "C" {
     DLL_EXPORT void MSGDllTest();
    }

     

    // テスト関数
    void MSGDllTest()
    {
        MessageBox(NULL, (LPCWSTR)L"[C++]MSG呼び出し", (LPCWSTR)L"test", MB_OK);

        //Initialize COM.
        HRESULT hr = CoInitialize(NULL);

        // Create the interface pointer.
        IMsgBoxDLLPtr pIMsgBoxDL(__uuidof(MsgBoxDLL));

        // Call the Add method.
        pIMsgBoxDL->ShowMessage1();

        // Uninitialize COM.
        CoUninitialize();
    }    
    <作成方法>
    1.C/C++のコード生成 アライメント 4バイト
    2.C/C++の詳細 呼び出し規約 _stdCall
    3.リンカーデバッグ マップファイル作成する

    ◆C#のクラスライブラリのソース

    /////////////////////////////////////////////////
    // マネージのCOM                     //
    ////////////////////////////////////////////////
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;

    namespace CLR_DLL
    {
        public interface IMsgBoxDLL
        {
              void ShowMessage1();
        }

        public class MsgBoxDLL:IMsgBoxDLL
        {
            public MsgBoxDLL()
            {
                MessageBox.Show("[C#DLL]コンストラクタ");
            }

            public void ShowMessage1()
            {
                MessageBox.Show("[C#DLL]publicメソッドその1呼び出し");
            }
        }
    }
    <作成方法>
    1.アプリケーション ‐> アセンブリ情報‐>アセンブリをCOM参照にするを選択する。
    2.ビルド ‐> COM相互運用機能の登録を選択する

     

    <参考資料>

    アンマネージ コードとの相互運用
    http://msdn.microsoft.com/ja-jp/library/sd10k43k(VS.80).aspx

    以上 ご参考になれば幸いです。

    2008年11月19日 16:34

すべての返信

  • 肝心の、「実行時エラー」の内容の記述がありませんが……。
    dll の配置はどうなってるんでしょうか。
    2008年11月14日 7:29
  • レスありがとうございます。
    実行時エラーは、以下のダイアログが出ます。

    -------------------------------------
    Microsoft Visual C++ Runtime Library
    × Runtime Error!
        Program : C\Program Files\Microsoft Office\Office10\EXCEL.EXE

    This application has requested the Runtime to terminate it in an unusual way.
    Please contact the application's support team for more information.
    -------------------------------------

    C#のDLLは、マクロのコードに書かれた場所 (
    "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" ) においています。


    2008年11月14日 9:59
  • こんばんは!(^^)!ふ~です。

     

    初期の頃、微かに記憶に残っているのですが、難解なパターンと思います。

    通常はC#を先に実行して、EXCEL.EXEを呼び出したり、VC++のCOMなどを呼び出すのが一般的と思います。(一寸古いかも)

     

    ◇マネージ コードとアンマネージ コード間でマーシャリングする

    http://msdn.microsoft.com/ja-jp/magazine/cc164193.aspx

     

    ◇Excelで作成する業務アプリ

    VS.NET 2005環境でVSTO開発する。

    MS Office2003(SP1以降)

    IIS、SQL Serverも使用する

     

    VSTOとは、Officeドキュメントから.NETのアセンブリを呼び出し、実行する仕組みである。

    http://www.atmarkit.co.jp/fdotnet/special/vstodev/vstodev_02.html

    最近は、いろいろ手段があるようです。

    2008年11月16日 9:58
  • Visual Studioのツールメニューからプロセスにアタッチの機能を使って、Excelを対象にすれば、何が起きているかは見えるかもしれません。

    対象としてはネイティブのコードとマネージのコードの両方を設定しておくと良いかと思います。

     

    ところで、そのC#で作ったCOMのDLLは、別のDLLを参照していたりしますか?

    2008年11月16日 22:23
    モデレータ
  • 皆さま、レスありがとうございます。
    すいません、事実関係に多少違いがありましたので、あらためて整理させてください。

    サンプルでは、EXCELマクロからC#を呼び出すと書いていましたが、これは間違いでした。
    EXCELマクロからは、VC++のDLLを呼び出し、VC++ DLLからC#のDLLを呼び出しています。
    VC++は通常はVC++6.0ですが、サンプルではVC++2005を使用しています。
    C# DLLを呼び出すVC++ DLLのサンプルソースは、以下になります。

    [VC++ DLLのサンプルコード]


    #include <windows.h>
    #include "main.h"

    #import "..\CLR_DLL\bin\Debug\CLR_DLL.tlb" no_namespace named_guids raw_interfaces_only
    main::main(void)
    {
    }

    main::~main(void)
    {
    }

    __declspec(dllexport)       
    void __stdcall MSGDllTest() {    
        MessageBox(NULL, "[C++]MSG呼び出し", "test", MB_OK);

       // Initialize COM.
        HRESULT hr = CoInitialize(NULL);

        // Create the interface pointer.
        IMsgBoxDLLPtr pIMsgBoxDL(__uuidof(MsgBoxDLL));

        long lResult = 0;

        // Call the Add method.
        pIMsgBoxDL->ShowMessage1();


        // Uninitialize COM.
        CoUninitialize();

    }    

    int WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, PVOID pvReserved)    
    {    
        return TRUE;    
    }

    EXCELマクロにある"D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" は、
    VC++のDLLになります。
    このVC++ DLLがC#のDLLを呼び出しているわけですが、C# DLLの配置は、system32のフォルダに配置して、
    system32にパスを通しています。
    一応、以下の手順で、C#のタイプライブラリを作成しています。

    1.クラスライブラリのコードを作成
    2.厳密名ツール(sn.exe)でキーペアを作成する
    3.AssemblyInfoのComVisibleをtrueにする
    4.プロジェクトのプロパティから署名を選択して、2,で作成したキーペアファイルを選択する
    5.Regasmでタイプライブラリを作成 (regsam***.dll /tlb:***tlb /codebase)

    このC# DLLを、VC++のDLLで、以下のようにインポートしています。
    ・ regasmで作成したタイプライブラリのインポート文を記述
    (#import "..\DLLSample\bin\Debug\***.tlb")

    なお、EXCELマクロの代わりに、VC++のmainでVC++ DLLを呼び出すと、その先のC# DLLがうまく呼び出せています。

    [VC++ mainのサンプルコード]

    #include <windows.h>
    #include "main.h"

    __declspec(dllimport)
    void __stdcall MSGDllTest();

    int main(int argc, char* argv[])
    {
        MSGDllTest();
        return 0;
    }

    呼び出し関係と成否を整理すると、以下になります。

    EXCELマクロ -> VC++ DLL -> C# DLL (COM)  : ×
    VC++ main    -> VC++ DLL -> C# DLL (COM)  : ○

    なお、C# DLLは、その先のDLLなどを呼び出すということはないです。

    このように、呼び出す元の一番親(先祖)が、VC++からEXCELに代わることで、なぜうまくいかないのかが、課題になっています。
    なぜこのようなややこしいことをしているかといいますと、既存システムでVC++6で作ったものが大量に存在し、それを
    活かすために、C#で新規で作成したプログラムをCOMにしたりしています。

    よろしくお願いします。
    2008年11月18日 3:17
  • こんにちは!(^^)!ふ~です。

     

    EXCELマクロ -> VC++ DLL -> C# DLL (COM)  : ×

    最近は、EXCELマクロから直接C#(COM)を呼び出す設計になっているようです。

     

    ご参考資料 C#で作成したアセンブリをExcelから呼び出す。
    http://d.hatena.ne.jp/akiramei/20071227/1198771429

     

    多少キャプチャ画面が古いと思いますので補足します。(不要なソースも修正)

    <プロジェクトのプロパティより>

    1) アプリケーション ‐> アセンブリ情報‐>アセンブリをCOM参照にするを選択する。

    2)ビルド ‐> COM相互運用機能の登録を選択する

     

    Code Snippet

    <C#のクラスライブラリを作成する>

    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace ASExcelCmd
    {
     public class calc
     {
      public int Add(int x, int y)
      {
       return x + y;
      }

      public int Sub(int x, int y)
      {
       return x - y;
      }
     }
    }

     

    <Excel側のマクロで呼び出す。>

    Public Sub Test()
      Dim tcalc As New ASExcelCmd.calc
      ans = tcalc.Add(10, 30)
      MsgBox ans
    End Sub

     

     

    ご提案としまして、VC++で作成しましたラッパ部分を取り除き、新規作成しましたC#のCOMを直接Excelマクロより呼び出すことが適当と思われます。

    どうしてもVC++からC#を呼び出す場合、通常は、Managed C++からC#を呼び出す設計が普通のようです。

    しかし、現状でも、C#のCOMがExcelマクロで起動できる事を確認し、「VC++でラッパすると失敗する。」この追及も面白いと思います。

    2008年11月18日 6:42
  •  masakiy さんからの引用

    [Excelマクロのサンプルコード]
    Declare Function MSGDllTest Lib "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" () As Long

    Sub test()
        Call MSGDllTest
    End Sub


     masakiy さんからの引用

    [VC++ DLLのサンプルコード]
    __declspec(dllexport)       
    void __stdcall MSGDllTest() {    

    見比べると何かかおかしくありませんか?

    VBAにおけるFunctionは戻り値のある関数を示します。またAs Longと末尾につけることで、C++でのint型を期待していますよね?
    対して、C++側のコードは戻り値のない関数になっています。
    これが原因とは言い切れませんが、直しておいた方が良いです。

     

     

    -----

    突っ込みをするのはあまり良くないかもしれないんだけど、気になったので書いておきます。

     

     !(^^)!ふ~ さんからの引用

    最近は、EXCELマクロから直接C#(COM)を呼び出す設計になっているようです。

    それは要件や状況によりますので、絶対ではありません。

     

     !(^^)!ふ~ さんからの引用

    どうしてもVC++からC#を呼び出す場合、通常は、Managed C++からC#を呼び出す設計が普通のようです。

    VS2005の環境でやるのであれば、Managed C++はほぼ使いません。代わりにC++/CLI(C++/CLRとも言う)を使用します。

    言語構造が結構変わっていて、やりやすくなっています。

     

    なお、C++/CLIでC#のモジュールを呼び出しつつ、ネイティブの関数としてエクスポートすることで、C#のDLLをCOMにする必要がなくなりメリットはあります。

    2008年11月18日 14:47
    モデレータ
  • 皆さま、どうもありがとうございます。

    戻り値が変な点については、サンプルのため勢いで書いてしまいこのようになっているのですが、問題の種を除く一つとしても、直しておきます。

    やり方ですが、今私がやっているのよりもっと効率のいいのがありそうですね。
    それも、皆さまのご意見を参考に、あらためて検討したいと思います。
    2008年11月19日 4:36
  • こんばんは!(^^)!ふ~です。

    私の発言で、お仕事が余分に増えてしまっても困りますのでテストしてみました。

    結果は、『Excelから、VC++DLL経由C#のCOM』も動作しました。

     

    最初、私のMicrosoftExcel2002 SP3では、COMアドイン
    が動作していない為下記エラーで止まりました。

    <エラー内容>
    Microsoft Visual C++ Runtime Library

    Runtime Error!
    Progran:C\Program Files\Microsoft Office\Offce10\EXCEL.EXE

    This apprication has requested the Runtime to tetminate it in an unusual way.
    Pleease contact the apprication's support team for more information.

     

    しかし、『MicrosoftExcel2007』では、問題なく動作致しました。
    ヘルプのオプションや、ヘルプのバージョン、システム情報でCOMアドインが
    可能であるか判断できるようです。

    折角ですから成功したソースを記述します。

     

    ◆エクセルのVBAソース
    Option Explicit
    Declare Sub MSGDllTest Lib "C:\VS2005\Projects\ASExcelDllTestMain\debug\VCCExcelDll.dll" _
        Alias "_MSGDllTest@0" ()

    Sub test()
        Call MSGDllTest
    End Sub

    <作成方法>
    1.VCCExcelDll.mapの中の"_MSGDllTest@0"を使う
    2.CLR_DLLは参照設定して有ります。
        参照状態試験用コード(直接呼ぶ)
        Public Sub test()
        Dim mstest As New CLR_DLL.MsgBoxDLL
        Call mstest.ShowMessage1
        End Sub

     

    ◆VC++Win32 コンソールのメインソース (これもテスト用です)
    // ASExcelDllTestMain.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
    //

    #include "stdafx.h"

    #define DLL_IMPORT __declspec(dllimport)

     

    extern "C" {
     DLL_IMPORT void __stdcall MSGDllTest();
    }

     

    // VC++ メイン処理
    int _tmain(int argc, _TCHAR* argv[])
    {
        MSGDllTest();
        return 0;
    }
    <作成方法>
    1.全てのプロジェクトをASExcelDllTestMainへ追加登録してまとめる

     

    ◆VC++のWin32DLLのソース(ここがラッパです)
    ////////////////////////////////////////////////////////////////////////////
    // アンマネージDLLより、マネージCOMを呼び出す     //
    ////////////////////////////////////////////////////////////////////////////

    // VCCExcelDll.cpp : DLL アプリケーションのエントリ ポイントを定義します。
    //

    #include "stdafx.h"

     

    // VC++のCOM から .NET 型を参照する( ネームスペースなし、GUID構造体を使ったCLSID、COMインターフェース用)
    #import "..\CLR_DLL\bin\Debug\CLR_DLL.tlb" no_namespace named_guids raw_interfaces_only

     

    #define DLL_EXPORT __declspec(dllexport)

     

    extern "C" {
     DLL_EXPORT void MSGDllTest();
    }

     

    // テスト関数
    void MSGDllTest()
    {
        MessageBox(NULL, (LPCWSTR)L"[C++]MSG呼び出し", (LPCWSTR)L"test", MB_OK);

        //Initialize COM.
        HRESULT hr = CoInitialize(NULL);

        // Create the interface pointer.
        IMsgBoxDLLPtr pIMsgBoxDL(__uuidof(MsgBoxDLL));

        // Call the Add method.
        pIMsgBoxDL->ShowMessage1();

        // Uninitialize COM.
        CoUninitialize();
    }    
    <作成方法>
    1.C/C++のコード生成 アライメント 4バイト
    2.C/C++の詳細 呼び出し規約 _stdCall
    3.リンカーデバッグ マップファイル作成する

    ◆C#のクラスライブラリのソース

    /////////////////////////////////////////////////
    // マネージのCOM                     //
    ////////////////////////////////////////////////
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Forms;

    namespace CLR_DLL
    {
        public interface IMsgBoxDLL
        {
              void ShowMessage1();
        }

        public class MsgBoxDLL:IMsgBoxDLL
        {
            public MsgBoxDLL()
            {
                MessageBox.Show("[C#DLL]コンストラクタ");
            }

            public void ShowMessage1()
            {
                MessageBox.Show("[C#DLL]publicメソッドその1呼び出し");
            }
        }
    }
    <作成方法>
    1.アプリケーション ‐> アセンブリ情報‐>アセンブリをCOM参照にするを選択する。
    2.ビルド ‐> COM相互運用機能の登録を選択する

     

    <参考資料>

    アンマネージ コードとの相互運用
    http://msdn.microsoft.com/ja-jp/library/sd10k43k(VS.80).aspx

    以上 ご参考になれば幸いです。

    2008年11月19日 16:34
  • ふ~さん、レスおよび詳細な再現手順どうもありがとうございました。
    また、返答遅れてすみません。
    私のほうでも、Excel2007 or 2003でうまくいって、2002でうまくいかないということがありました。
    原因は、COMアドインにありそうなのですね。
    あらためて、確認してみます。
    2008年12月1日 2:42
  • こんにちは。中川俊輔です。

     

    皆様、たくさんの回答ありがとうございます。

     

    masakiyさん、フォーラムのご利用ありがとうございます。

    勝手ながら、有用な情報と思われる回答へ回答済みチェックをつけさせていただきました。

    追加の質問等ありましたら、ぜひまた投稿してみてください。

     

    今後ともフォーラムをよろしくお願いします。

    それでは!

    2008年12月15日 9:18