トップ回答者
EXCELマクロからのDLL(.NET)呼び出し

質問
-
お世話になっております。
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呼び出し");
}
}
}
回答
-
こんにちは!(^^)!ふ~です。
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++でラッパすると失敗する。」この追及も面白いと思います。
-
masakiy さんからの引用
[Excelマクロのサンプルコード]
Declare Function MSGDllTest Lib "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" () As LongSub 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にする必要がなくなりメリットはあります。
-
こんばんは!(^^)!ふ~です。
私の発言で、お仕事が余分に増えてしまっても困りますのでテストしてみました。
結果は、『Excelから、VC++DLL経由C#のCOM』も動作しました。
最初、私のMicrosoftExcel2002 SP3では、COMアドイン
が動作していない為下記エラーで止まりました。<エラー内容>
Microsoft Visual C++ Runtime LibraryRuntime Error!
Progran:C\Program Files\Microsoft Office\Offce10\EXCEL.EXEThis 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以上 ご参考になれば幸いです。
すべての返信
-
レスありがとうございます。
実行時エラーは、以下のダイアログが出ます。
-------------------------------------
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" ) においています。 -
こんばんは!(^^)!ふ~です。
初期の頃、微かに記憶に残っているのですが、難解なパターンと思います。
通常は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
最近は、いろいろ手段があるようです。
-
皆さま、レスありがとうございます。
すいません、事実関係に多少違いがありましたので、あらためて整理させてください。
サンプルでは、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にしたりしています。
よろしくお願いします。 -
こんにちは!(^^)!ふ~です。
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++でラッパすると失敗する。」この追及も面白いと思います。
-
masakiy さんからの引用
[Excelマクロのサンプルコード]
Declare Function MSGDllTest Lib "D:\DATA\MyDocuments\Visual Studio 2005\Projects\DLLTest\debug\MSG_DLL.dll" () As LongSub 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にする必要がなくなりメリットはあります。
-
こんばんは!(^^)!ふ~です。
私の発言で、お仕事が余分に増えてしまっても困りますのでテストしてみました。
結果は、『Excelから、VC++DLL経由C#のCOM』も動作しました。
最初、私のMicrosoftExcel2002 SP3では、COMアドイン
が動作していない為下記エラーで止まりました。<エラー内容>
Microsoft Visual C++ Runtime LibraryRuntime Error!
Progran:C\Program Files\Microsoft Office\Offce10\EXCEL.EXEThis 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以上 ご参考になれば幸いです。