概要
前節では、C#等で作られた「中間アセンブリ」の場合を解説しました。
ここではさらに深堀して、「C#で自作するdllがさらにネイティブdllを呼び出す場合にそのパス」を追加 する場合を解説します。
厳密にはパス自体を追加するわけではなく、dllを読み込んでしまうことで、以降C#の該当プロセスで自由に使えるようにする、というものです。
ネイティブのdllも同じように場所を指定する必要があります。
直接あなたが作るdllがそのネイティブを利用する 場合も考えれますし、
nugetなどを利用して追加した比較的大型な参照アセンブリが、ネイティブdllにも依存している 場合も考えられます。
その両方で今回の手法によって簡単に「extern」や「DllImport(***)」(=P/Invokeで宣言)などの既存部分の書き換えなしで解決することが出来ます。
ネイティブdllのわかりにくい特性
ネイティブのdllは中間アセンブリのdllとは異なり 「カレントフォルダにあるdll 」が読みこめてしまうため、後になってバグが起きやすい性質があります。
例えば、たまたま今 、マクロファイル(例えばClassLibrary36.mac)を秀丸エディタで開いていて 、同じディレクトリにdllがあれば、読み込めてしまいます。
しかし、全て秀丸を閉じたあと、マクロファイルを秀丸で開かずに 、別のフォルダの別のファイル(例:Test.txt)を開き、いきなりマクロ(ClassLibrary36.mac)を実行した場合には、
カレントフォルダ(Test.txtがあるフォルダ)を探してもdllは無いわけですからエラーとなります。
マクロ制作中はネイティブdllが読み込めていたのに、いざ確認作業を始めると読み込めなくなった
というのはこの罠にはまり込んでいることが多いです。
ネイティブdllの作成
まずは、以下のようなネイティブdllを「dll1.dll」として作成し、これをC#側で利用したいとしましょう。
dllmain.cpp // dllmain.cpp : DLL アプリケーションのエントリ ポイントを定義します。
#include "pch.h"
#include "windows.h"
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) intptr_t MyFunc(intptr_t parm1) {
return (intptr_t)3;
}
ネイティブdllのパス解決
自作のdllに、以下のような「NativeResolver」とも呼べるプログラムを、追加することで、「ネイティブdllを読ませる」ことが出来ます。
DllNativeResolver.cs using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
internal class DllNativeResolver
{
[DllImport("kernel32")]
private static extern IntPtr LoadLibrary(string lpFileName);
const string dllName = "Dll1.dll";
public DllNativeResolver()
{
var selfdir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var dllPath = Path.Combine(selfdir, dllName);
System.Diagnostics.Trace.WriteLine(dllPath);
if (!(File.Exists(dllPath) && LoadLibrary(dllPath) != IntPtr.Zero))
{
System.Diagnostics.Trace.WriteLine(dllName + " の読み込みに失敗しました");
throw new DllNotFoundException(dllPath);
}
}
}
秀丸マクロから最初に呼ぶ予定の「メソッドを含むクラス」の「静的コンストラクタ」に以下のようなものを記述することです。
ポイントは秀丸マクロから最初に呼ぶ予定の「メソッドを含むクラス」の「静的 コンストラクタ」 に書くことです。
ClassLibrary36.cs using System;
using System.Runtime.InteropServices;
namespace ClassLibrary36 {
public class Class1
{
static DllNativeResolver dntvr;
// 秀丸から最初に呼ぶメソッドが含まれているクラスの「静的コンストラクタ(static付きのコンストラクタ)」
// でネイティブdllリゾルバーオブジェクトを生成する。
static Class1() {
dntvr = new DllNativeResolver();
System.Diagnostics.Trace.WriteLine("MyTestClass 静的コンストラクタ実行");
}
[DllExport]
// 秀丸から最初に呼ぶメソッド
static IntPtr abc(IntPtr a)
{
IntPtr r = MyFunc((IntPtr)3);
int b = (int)a + 3 + (int)r;
return (IntPtr)b;
}
// DllNatvieResolver内でいちはやく解決されていれば、DllImportの形がそのまま使える。
[DllImport("dll1")]
static extern IntPtr MyFunc(IntPtr i);
}
}
このようにして、dll1.dllの配置場所を自由に操作することができます。