64bitDLLと32bitDLLを共存させたい
提供されたDLLが方やx64、方やx86指定でないと使えないものを1つのアプリケーションで使う必要にかられました。
今まで自分ではアーキテクチャ依存のコードはほとんど書いたことがないため、意外とこういった状況は経験がありませんでした。
とりあえず一つのプロセスではx64とx86は共存できません。
さてどうしたものか。
プロセス間通信の実現方法
プロセス間通信の手段として思いつくのは、
- 昔ながらのメモリ共有
- コンソールアプリを別プロセスで起動して標準出力で通信
- ローカルに対してソケット通信
この辺は割とすぐに思いつくものの、お互いがお互いを知らないといけないと言うなるべく避けたい構成。
.Netリモーティングなるものもあるらしい
C# プロセス間通信(IPC)
割と新しい記事ではWCFを勧められている記事が多め?
そもそも記事数が少ないのでイバラの道の予感しかありませんが。
色々と大変だったので備忘録。
とりあえず作ってみたら失敗
WCFサービスライブラリでx86DLLをラップしたサービスライブラリを作ってみる、
x64でサービス参照を追加。
WCFサービスをx86指定でビルドして通信!と思ったらデバッグ開始時点で落ちます。
どうやらWCFサービスのホストが64bitマシンでは標準でx64が選択されるようでそこで落ちてしまう様です。
指定するのにVSコマンドプロンプトでコマンド打って・・・みたいなのもありましたが、アプリ配布時に面倒な環境設定が必要になるかも?な予感。
x86DLLを本体側でx64DLLをWCFと言う手段もなくはないですが、アプリ内での使用頻度と遅延を思うと、x64DLLを本体側に配置したい。
ということで勉強がてらホスト部分を自作してみることにしました。
WCFサービスで通信するための概要
主に以下のプロジェクトを用意しました。
- x86DLLの模擬DLL
- WCFサービスのコントラクト(インターフェースだけを持つ)ライブラリ
- WCFサービスをセルフホストしてx86DLLをラップするアプリ
- x64DLLを使うWPFアプリ
WPFアプリとWCFサービスアプリでお互いにインターフェースを参照しておくことでデータの型などが制限できます。
注意点として
- 返り値を持つメソッドはref やoutが使えない場合がある様です。
- 同じ名前のメソッドは複数定義できません。(引数が変わっててもだめ)
x86DLLの模擬プロジェクト
無くてもいいですが目的の形に近づけるためにx86DLLを作りました。
namespace ClassLibrary1
{
public class Class1
{
public int IncrementMethod(int value)
{
return value + 1;
}
}
}
WCFサービスのコントラクト
テンプレートは「空のプロジェクト」を選びました。
出力タイプはクラスライブラリに設定。
System.ServiceModelを参照に追加。
今回は適当な文字列を返すメソッドのを定義してみます。
using System.ServiceModel;
namespace HelloWorldServiceContract
{
[ServiceContract(Namespace = "http://HelloWorldServiceContract")]
public interface IHelloWorldService
{
[OperationContract]
string SayHello(string name);
}
}
WCFサービスをセルフホストするコンソールアプリ
テンプレートは「コンソールアプリ」を選択。
System.ServiceModelを参照に追加。
WCFサービスのコントラクトプロジェクトを参照に追加。
実験のためにx86指定のDLLを参照。
プロジェクト自体もx86指定に。
サービス実装クラスを記述。
using System;
using HelloWorldServiceContract;
namespace ConsoleApp1
{
public class HelloWorldService : IHelloWorldService
{
public string SayHello(string name)
{
var test = new ClassLibrary1.Class1();
var nowsecond = DateTime.Now.Second;
return string.Format("Hello, {0} {1} {2}", name, nowsecond, test.IncrementMethod(nowsecond));
}
}
}
コンソールアプリのProgram.Mainでサービスをホストする。
using System;
using System.ServiceModel;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(HelloWorldService)))//, baseAddress))
{
// Enable metadata publishing.
//MEMO:App.configに記載
host.Open();
Console.WriteLine("The service is ready at {0}");//, baseAddress);
Console.WriteLine("Press <Enter> to stop the service.");
Console.ReadLine();
// Close the ServiceHost.
host.Close();
}
}
}
}
App.configでサービスの設定を追記
使いたいDLLは古-------いものだったので、地味にuseLegacyV2RuntimeActivationPolicy="trur"
が無いと動かなったです。
多分今時X86DLLを使わなければならない様なものは同じ躓きがあるかも知れません。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<!-- サービスとして動作させるクラスをname属性にする -->
<service name="ConsoleApp1.HelloWorldService"
behaviorConfiguration ="metadataAndDebugEnabled">
<host>
<baseAddresses>
<!-- 接続先ベースアドレス -->
<add baseAddress="net.tcp://localhost:8808/service"/>
</baseAddresses>
</host>
<!-- 接続先および接続方法の設定 -->
<!-- addressに何もいれていないのでベースアドレスと同一となる -->
<endpoint address="" binding="netTcpBinding" contract="HelloWorldServiceContract.IHelloWorldService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataAndDebugEnabled">
<serviceDebug
includeExceptionDetailInFaults="true"
/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
サービス起動時に管理者権限が必要でした・・・。
新しい項目の追加でapp.manifestを追加しコメントに従って変更します。
Httpバインディングでは管理者権限が必要ですが、Net.TCPバインディングなら管理者権限は不要でした。
WPFアプリ
とりあえず空のWPFを作りました。
本題ではないのでMainWindow.xaml.csにWCFサービスのプロセス管理とサービスへのアクセスを記述しました。
Loadedイベントで呼び出している部分でサービスのメソッドを呼んでます。
using HelloWorldServiceContract;
namespace WCFHostWPFAppTest
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var serviceProcess = Process.Start("ConsoleApp1.exe");
var address = new EndpointAddress("net.tcp://localhost:8808/service");
var binding = new NetTcpBinding();
var factory = new ChannelFactory<IHelloWorldService>(binding, address);
var channel = factory.CreateChannel();
this.Loaded += (_, __) =>
{
// サービス呼び出し
var IHelloWorldService = channel.SayHello("test");
Console.WriteLine("SayHello() = {0}", IHelloWorldService);
};
this.Closing += (_, __) =>
{
factory.Close();
serviceProcess.Kill();
};
}
}
}
起動したらコンソールに無事文字が出ました。
SayHello() = Hello, test 14 15
これで何とかなりました。