マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。

目次

概要

#関数エクスポート方式 \ DLL呼出方法暗黙のうちにリンク(暗黙のリンク)明示的にリンク(明示的なリンク)
1明示的ロードが不要だが、関数のエクスポートが手間な方式以下で説明(VCからのDLL呼び出し)
2明示的ロードが必要だが、関数のエクスポートが簡単な方式以下で説明(.NETからのDLL呼び出し)

手順

プロジェクトの作成

  • 新規作成→Win32プロジェクトを選択
  • ウィザードで、DLLを選択。
  • プロジェクトが作成される。
    • dllmain.cppはDLLのエントリポイント。
    • (プロジェクト名).cppにエクスポート関数を定義する。

エクスポート関数の実装

以下、2つのメソッドをエクスポートしている。

  • Test_MYLIBAPI
  • TestArrayMethod?

明示的ロードが不要だが、関数のエクスポートが手間な方式

  • ヘッダ・ファイル
  • ソース・ファイル
  • モジュール定義ファイル

を作成して関数をエクスポートする方式

  • ヘッダ・ファイル(MYLIBAPI.h)
    プロジェクトに、ヘッダ・ファイルを追加する。
    // エクスポートとインポートの切り替え
    #ifdef MYLIBAPI // VC_DLL_EXPORTS でもいいかも
    
    // DLLのPJではソース・ファイル(MYLIBAPI.cpp)を使用して関数をエクスポート。
    
    #else
    
    // EXEの(DLLを利用する)PJではヘッダ・ファイル(MYLIBAPI.h)を使用して関数をインポート。
    // extern "C" は、他の言語から呼ぶ場合に必要。
    #define MYLIBAPI extern "C" __declspec(dllimport)
    
    #endif
    
    // 以下、エクスポート関数のプロトタイプ宣言
    // 通常、__stdcallを適用する(__stdcall = WINAPI)。
    MYLIBAPI void __stdcall Test_MYLIBAPI(LPCWSTR lpText, LPCWSTR lpCaption);
    
    // この方式だと、C4603、C4273の警告が出るが、問題はない。
    // ・C4603:'<identifier>': マクロが定義されていないか、
    //          プリコンパイル済みヘッダーが使用している定義とは異なります。
    // ・C4273:dll リンクが一貫していません。
  • ソース・ファイル(MYLIBAPI.cpp)
    プロジェクトに、ソース・ファイルを追加する。
    // このソースコードの関数と変数をエクスポート
    #define MYLIBAPI extern "C" __declspec(dllexport)
    
    #include <stdafx.h>
    #include <windows.h>
    #include <stdio.h>
    
    // エクスポート関数の宣言
    #include "MYLIBAPI.h"
    
    // 以下、エクスポート関数の実装
    // 通常、__stdcallを適用する(__stdcall = WINAPI)。
    void __stdcall Test_MYLIBAPI(LPCWSTR lpText, LPCWSTR lpCaption)
    {
    	// MessageBoxを呼び出すだけ。
    	MessageBox(NULL, lpText, lpCaption, MB_OK);
    }
  • モジュール定義ファイル(Exports.def)
    プロジェクトに、モジュール定義ファイルを追加する。
    ;MS系以外のツールから呼ぶときは、
    ;DefファイルのEXPORTSを使用してExportする。
    LIBRARY	"VC_DLL"
    EXPORTS
       Test_MYLIBAPI
       TestArrayMethod

明示的ロードが必要だが、関数のエクスポートが簡単な方式

簡単な方式では明示的ロードでVC、DOTNETから使用するので、
ヘッダ・ファイル、モジュール定義ファイルなどのモジュールが不要。

  • ソース・ファイル(Simple.cpp)
    プロジェクトに、ソース・ファイルを追加する。
    // 簡単な方式(明示的ロードが必要)
    // Simple.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
    
    #include "stdafx.h"
    #include <stdio.h>
    
    // 以下は、
    // The try, catch, and throw Statements
    // http://msdn.microsoft.com/en-us/library/6dekhbbc(VS.80).aspx
    // から引用
    
    class CTest {
    public:
       CTest() {};
       ~CTest() {};
       const char *ShowReason() const { 
          return "Exception in CTest class."; 
       }
    };
    
    extern "C"{
    	typedef struct _A{
    		int m1;
    		char m2_in[12]; // 11文字まで (MarshalAs(UnmanagedType.ByValTStr, SizeConst=12)
    	 	char m3_out[48]; // 47文字まで (MarshalAs(UnmanagedType.ByValTStr, SizeConst=48)
    	} A;
    
    	// 明示的ロードでVC、DOTNETから使用するのでヘッダーファイルは不要
    	// なお、__cdeclでもDOTNETから正しく呼び出せることを確認している。
    	// 通常、__stdcallを適用する(__stdcall = WINAPI)。
    	__declspec(dllexport) int __stdcall TestArrayMethod(A a, A *pa, int len, A aa[]);
    
    	int __stdcall TestArrayMethod(A a, A *pa, int len, A aa[]){
    
    		// 配列の長さが0未満はありえないので
    		// 例外を発行する(C++の例外を発行してみる)
    		if(len < 0){
    			throw CTest();
    		} // extern "c" で警告が表示される(__cdeclでも__stdcallでも)。
    
    		// 値渡しの構造体 a
    		char *p_inputmessage = a.m2_in;
    		int counter = a.m1;
    
    		// 参照渡しの構造体 *pa(構造体 a からコピー)
    		pa->m1 = ++counter; // 先に ++ してから代入
    		sprintf_s(pa->m3_out, 47, "%s by pa->", p_inputmessage);
    
    		// 参照渡しの構造体配列 aa[](構造体 a からコピー)
    		int i;
    		for (i = 0; i < len; i++){
    			aa[i].m1 = ++counter; // 先に ++ してから代入
    			sprintf_s(aa[i].m3_out, 47, "%s by aa[%d].", p_inputmessage, i);
    		}
    
    		return counter;
    	}
    }

DLLの呼び出し方法

  • 参考:Advanced Windows
    • 第 4 部 DLL
      • 第 19 章 DLL の基礎
      • 第 20 章 DLL の高度なテクニック

暗黙のうちにリンク(暗黙のリンク)

∗.libファイルを使用する。

リンカが*.libファイルを使ってインポートされている関数/変数の参照を解決しながら*.objモジュールを結合し、
∗.exeファイル(必要なDLLとインポートされているシンボルのリストであるインポートテーブルを格納している)を生成する。

明示的にリンク(明示的なリンク)

明示的なリンクでは、*.libファイルを使用せず
(従って、*.exeファイルはインポートテーブルを持たない)、
Win32APIのLoadLibrary?を使用してDLLをロードする。

DLL呼び出し側サンプル

VC++

  • VC++からのDLL呼び出しでは、∗.libファイルを使用した暗黙のリンクを使用している。
  • ∗.libファイルを参照する場合は、以下の手順に従う。
    • リンカ入力としての .lib ファイル
      http://msdn.microsoft.com/ja-jp/library/ba1z7822.aspx
      1. プロジェクトの [プロパティ ページ] ダイアログ ボックスを開きます。
      2. [リンカ] フォルダをクリックします。
      3. [入力] プロパティ ページをクリックします。
      4. [追加の依存ファイル] プロパティを変更します。
// VC_Client1.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <windows.h>
#include "../../VC_DLL/MYLIBAPI.h"

int _tmain(int argc, _TCHAR* argv[])
{
  // 通常のLibを使用したDLL関数の呼び出し方法。
  Test_MYLIBAPI(L"text", L"caption");

  return 0;
}

C#

.NETからのDLL呼び出し(P/Invoke)では、明示的なリンクを使用している。

/// <summary>VC_DLL.TestArrayMethod</summary>
[DllImport("VC_DLL.dll")]
private extern static int TestArrayMethod(A a, ref A pa, int len, [In, Out] A[] aa);
// 第4引数(構造体配列)を、結果受け取り用で宣言する場合は 
// "ref" では不可、"[Out]" 属性を設定する必要がある

/// <summary>DLL呼び出し</summary>
private void Button1_Click(object sender, RoutedEventArgs e)
{
  try
  {
    A a1 = new A();
    A a2 = new A();
    A[] a3 = new A[3];

    a1.m1 = 8;
    a1.m2_in = "{入力値側}";

    // 正常系: 第3引数(配列サイズ)を正しく指定
    int ret1 = TestArrayMethod(a1, ref a2, a3.Length, a3);
  }
  catch (SEHException ex)
  {
    //MessageBox
    MessageBox.Show(
      "Err.Number:" + ex.ErrorCode + "\r\n" +
      "Err.Description:" + ex.Message,
      "例外", MessageBoxButton.OK);
  }
}

参考

DLLの作成

補足

  • MYLIBAPI
    • 上記コード中で定義されるマクロです。
    • 当該(DLLの)プロジェクトではエクスポートを実行
    • (DLLの)利用側プロジェクトではインポートを実行
  • extern "C"
    関数がC言語形式 になっていることを示す。

サンプル

https://github.com/OpenTouryoProject/SampleProgram

  • VC++ DLL:「\InteropWithUnmanage?\VC\VC_DLL」
  • VC++ 呼出元:「\InteropWithUnmanage?\VC\VC_Client\VC_Client1」
  • C# 呼出元:「\InteropWithUnmanage?\DNET\DNET_Client」

マネージドコードとアンマネージドコードのブリッジ


Tags: :Windows, :プログラミング, :.NET開発


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-02-03 (土) 13:16:43 (594d)