20210412のC#に関する記事は7件です。

UnityのInspectorで配列長を追加するとなんか適当に詰め込まれるのをどうにかする

そこそこ腹が立ったので書きました。 Unityのバージョンは2019.4.13です。 Inspectorにて // なんの変哲もないGameObjectの配列 [SerializeField] private GameObject[] _gameObjects = default; こんな感じで宣言した配列に対してInspectorから長さを追加すると、最後のフィールドをそのまま複製して詰め込む暴挙に出ます。 対処 しょうがないのでInspectorの変更によってキックされるOnValidateで面倒を見てやります。1 OnValidate private void OnValidate() { // Inspectorに入力された長さを覚えておく var beforeLength = _gameObjects.Length; // nullと重複を除く _gameObjects = _gameObjects .Where(o => o != null) .Distinct() .ToArray(); // (入力された長さ) - (現在の長さ) // で足りない長さを調べる var length = beforeLength - _gameObjects.Length; if (length > 0) { // 不足がある場合は,足りない長さの空の配列を作ってConcatで追加 _gameObjects = _gameObjects .Concat(Enumerable.Repeat<GameObject>(null, length)) .ToArray(); } } まとめ いいからdefault2ブチこめ おしまい。 毎回ToArray()で確定しているのはIEnumerableな変数をわざわざ用意するのがめんどいのと、以前遅延評価絡みでなんかよくわかんない目にあったからです ↩ nullだとPrimitiveに対応できないので。しかし、この挙動って破壊と混乱を招くだけの気がするのですが、嬉しい人いるんでしょうか……? ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[C#/C++] サービスを作るうえで便利なコマンド

もくじ https://qiita.com/tera1707/items/4fda73d86eded283ec4f サービス関連 https://qiita.com/tera1707/items/4fda73d86eded283ec4f#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E3%81%A4%E3%81%8F%E3%82%8B やりたいこと 下記で、C++とC#で、Windowsのサービスを作成した。 C++ https://qiita.com/tera1707/items/8f4246a5666a01134b59 C# https://qiita.com/tera1707/items/47335420a1a91e2eaa4a サービスは、ビルドしてexeが出来上がっても、そのままVisualStdio2019でF5を押して実行、とかはできない。 いったんサービスとしてPCにインストールして、そのサービスを「開始」したうえで、VisualStudioから「アタッチ」しないといけない。 そのインストールなどなどをするときに、scコマンドを使うのだが、速攻で忘れてしまいそうなので、使いそうなコマンドを下記に全部まとめておきたい。 使えそうなコマンドを一個にまとめたbat 使えそうなコマンドを全部まとめた.bat @echo off set SERVICENAME="SvcName" set SERVICEDISPNAME=%SERVICENAME% set BINPATH=C:\Users\username\Desktop\test\serviceJikken1\Debug\serviceJikken1.exe set DISCRIPTION="Svc Description..." rem サービスのプロセスを無理やり終了させる taskkill /f /im %BINPATH% rem サービスをアンインストール sc delete %SERVICENAME% pause rem サービスをインストール rem 試した限り、binPathにはフルパスを指定しないとうまくいかない sc create %SERVICENAME% start=auto binPath= %BINPATH% DisplayName= %SERVICEDISPNAME% pause rem 説明文を編集 sc description %SERVICENAME% %DISCRIPTION% pause rem サービスをスタート rem sc start %SERVICENAME% net start %SERVICENAME% pause rem サービスをストップ sc stop %SERVICENAME% pause コードをビルドしなおして、そいつでサービスを作り直してサービススタート サービスを作っている最中に、コードをちょっと直したときに、いったんサービスをstopして、ビルドしなおして、サービス作り直して再度スタートさせて、、、とやっていると非常にめんどくさいので、下記のようなbatを作って簡単にビルド→サービス再起動までできるようにしておけば便利かもしれない。 ビルドしなおして再スタート.bat @echo off set SOLUTIONPATH="C:\Users\username\Desktop\test\serviceJikken1\serviceJikken1.sln" set SERVICENAME="SvcName" set SERVICEDISPNAME=%SERVICENAME% set BINPATH=C:\Users\username\Desktop\test\serviceJikken1\Debug\serviceJikken1.exe set DISCRIPTION="Svc Description..." call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" echo -サービスのプロセスを無理やり終了させる taskkill /f /im %BINPATH% echo -サービスをアンインストール sc delete %SERVICENAME% echo -ソリューションをリビルドする MSBuild %SOLUTIONPATH% /t:clean;rebuild /p:Configuration=Debug;Platform="x86" > nul if %ERRORLEVEL% neq 0 ( echo ErrorLevel:%ERRORLEVEL% echo ビルド失敗 pause exit ) echo -サービスをインストール rem 試した限り、binPathにはフルパスを指定しないとうまくいかない sc create %SERVICENAME% start=auto binPath= %BINPATH% DisplayName= %SERVICEDISPNAME% echo -説明文を編集 sc description %SERVICENAME% %DISCRIPTION% echo -サービスをスタート rem sc start %SERVICENAME% net start %SERVICENAME% pause
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# Ben.Demystifier でスタックトレースを読みやすくする

このドキュメントの内容 例外のスタックトレースを読みやすく整形してくれるライブラリ Ben.Demystifier の紹介です。 近年 C# の機能は大きく進化し、async, LINQ, イテレータなどコーディングスタイルも変わってきています。それに伴い、スタックトレースが人間には読みづらい内容になることも多いです。そのような場面で便利なライブラリです。 実行サンプル ソースコード Exception に対する拡張メソッド Demystify を呼び出し、整形されたスタックトレースを出力します。 using System.Diagnostics; private async void button1_Click(object sender, EventArgs e) { try { await RunAsync().ConfigureAwait(false); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("----- StackTrace -----"); System.Diagnostics.Debug.WriteLine(ex.StackTrace); System.Diagnostics.Debug.WriteLine("----- Demystify -----"); System.Diagnostics.Debug.WriteLine(ex.Demystify()); } } private Task RunAsync() { // ローカル関数を介して処理を実行する Task RunTasks<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3) { // 例外をキャッチしてスローしなおす try { return Task.WhenAll(Execute001Async(arg1), Execute002Async(arg2, arg3)); } catch (Exception ex) { throw new Exception("処理中に例外が発生しました。", ex); } } return RunTasks("abc", 1, DateTime.Now); } private Task Execute001Async<T>(T arg) { return Task.CompletedTask; } private Task Execute002Async<T1, T2>(T1 arg1, T2 arg2) { int[] values = new[] { 1, 2, 3, 0 }; // ここでゼロ除算例外がスローされる foreach (var result in values.Select(x => 10 / x)) { } return Task.CompletedTask; } 出力結果 Exception.StackTrace 場所 WindowsFormsApp1.Form1.<RunAsync>g__RunTasks|2_0[T1,T2,T3](T1 arg1, T2 arg2, T3 arg3) 場所 E:\Source\Work\DemystifierSample\WindowsFormsApp1\Form1.cs:行 47 場所 WindowsFormsApp1.Form1.RunAsync() 場所 E:\Source\Work\DemystifierSample\WindowsFormsApp1\Form1.cs:行 51 場所 WindowsFormsApp1.Form1.<button1_Click>d__1.MoveNext() 場所 E:\Source\Work\DemystifierSample\WindowsFormsApp1\Form1.cs:行 25 ローカル関数や LINQ 式がリーダブルな形式で展開されています。 内部例外が格納されている場合、内部例外も出力されています。 Exception.Demystify() System.Exception: 処理中に例外が発生しました。 ---> System.DivideByZeroException: 0 で除算しようとしました。 at Task WindowsFormsApp1.Form1.Execute002Async<T1, T2>(T1 arg1, T2 arg2)+(int x) => { } in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 64 at bool System.Linq.Enumerable+WhereSelectArrayIterator<TSource, TResult>.MoveNext() at Task WindowsFormsApp1.Form1.Execute002Async<T1, T2>(T1 arg1, T2 arg2) in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 64 at Task WindowsFormsApp1.Form1.RunAsync()+RunTasks(T1 arg1, T2 arg2, T3 arg3) in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 43 --- 内部例外スタック トレースの終わり --- at Task WindowsFormsApp1.Form1.RunAsync()+RunTasks(T1 arg1, T2 arg2, T3 arg3) in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 47 at Task WindowsFormsApp1.Form1.RunAsync() in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 51 at async void WindowsFormsApp1.Form1.button1_Click(object sender, EventArgs e) in E:/Source/Work/DemystifierSample/WindowsFormsApp1/Form1.cs:line 25
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソース ジェネレーターをWPFで使う場合の注意点

概要 C# 9.0のソースジェネレーターの実例としてよく挙げられるのが、INotifyPropertyChangedです。 そこで実際にWPFに組み込もうとしたところ、結構苦労したので、その注意点を説明します。 組み込むソースジェネレータ INotifyPropertyChangedを実装するソースジェネレータは 公式サンプルもありますが、より多機能な@okazukiさんのプロジェクトを組み込みます。 テストアプリ 題材とするテストアプリです。ViewModelは上記の記事のままです。ソースジェネレータを組み込んでいないので、まだビルドすることはできません。 ViewModel public partial class MainWindowViewModel { [AutoNotify("Xxxx")] private string _firstName; [AutoNotify] private string _lastName; [AutoNotify] public string FullName => $"{Xxxx} {LastName}"; } View <Window x:Class="MvvmGenerator.WpfTests.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MvvmGenerator.WpfTests" Width="300" Height="250"> <Window.DataContext> <local:MainWindowViewModel /> </Window.DataContext> <StackPanel> <TextBox Text="{Binding Xxxx}" /> <TextBox Text="{Binding LastName}" /> <TextBlock Text="{Binding FullName}" /> </StackPanel> </Window> ソースジェネレータプロジェクト コンソールアプリケーションではプロジェクト参照でも動きますが、現時点(2021/04/11)ではWPFでは動きません。 nugetパッケージ化 まず、ソースジェネレータプロジェクトをnugetパッケージ化する必要があります。 通常のパッケージとは違うので、いくつか変更するところがあります。 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>preview</LangVersion> <Nullable>enable</Nullable> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <IncludeBuildOutput>false</IncludeBuildOutput> <IncludeSymbols>false</IncludeSymbols> <DevelopmentDependency>true</DevelopmentDependency> <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> <TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput</TargetsForTfmSpecificContentInPackage> <Version>1.3.0</Version> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" /> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.0" PrivateAssets="all" /> </ItemGroup> <Target Name="_AddAnalyzersToOutput"> <ItemGroup> <TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" /> </ItemGroup> </Target> </Project> プロパティ名 値 説明 IncludeBuildOutput false パッケージに通常のdll組み込みはしない IncludeSymbols false パッケージにシンボルファイル(.pdb)は含めない SuppressDependenciesWhenPacking true プロジェクトで使用しているパッケージの依存関係は含めない DevelopmentDependency true 開発用のパッケージとして認識されるようにする。 TargetsForTfmSpecificContentInPackage $(Targ... パッケージに組み込むカスタムターゲットを指定する TargetsForTfmSpecificContentInPackageから下記のカスタムターゲットを指定します。 <Target Name="_AddAnalyzersToOutput"> <ItemGroup> <TfmSpecificPackageFile Include="$(OutputPath)\MvvmGenerator.dll" PackagePath="analyzers/dotnet/cs" /> </ItemGroup> </Target> .csprojファイルの編集後ビルドして、.nupkgファイルを作成します。 作成したnupkgファイルの中を見てみると、通常のnupkgファイルとは異なった構成になっているのが分かります。 ローカルでパッケージ配布 作成したパッケージはnuget.orgを使用してもいいですが、自分が簡単に使うだけなら、ローカルフォルダに置いてnugetレポジトリとして、VisualStudioに登録することで使えるようになります。 詳しいやり方は以下を参照してください。 WPFプロジェクト .csproj編集 作成したnugetパッケージをWPFプロジェクトに追加します。 ここでも.csprojファイルの修正が必要です。 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net5.0-windows</TargetFramework> <UseWPF>true</UseWPF> <IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation> </PropertyGroup> <ItemGroup> <PackageReference Include="MvvmGenerator" Version="1.3.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> </Project> <IncludePackageReferencesDuringMarkupCompilation>をtrueに設定します。この長いプロパティは最近追加されたもののようで、まだMicrosoft Docsにも含まれていません。 動作確認 この状態でビルドして動作させます。 2つのTextBoxに入力された内容が、下のTextBlockに反映されることから、INotifyPropertyChangedが正しく実装されていることが分かります。 参考 https://github.com/ufcpp/ValueChangedGenerator/ https://zenn.dev/naminodarie/articles/32973a36fcbe99 ソースコード 今回の完全なソースコードをGithubにおいておきます。okazukiさんの記事のレポジトリをフォークしたものです。 https://github.com/soi013/MvvmGenerator 環境 VisualStudio 2019 Version 16.9.3 .NET 5 C# 9.0 謝辞 WPFで組み込もうとしたところ、うまく動かなかったので、@okazukiさんに助けてもらいました。ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#:備忘録】Unity Container(IoC Container)の使い方

概要 IoC ContainerであるUnity Containerの使い方の備忘録。 下記事態に陥ったので、メモを残す。 1.ゲームエンジンのUnityが検索に引っかかる。 2.英語の解説ばかり。。。日本語の解説は意外と少ない。  英語のチュートリアルを何度も読みたくない。 前置き 本記事はUnity Containerの使い方にフォーカスするため、IoC(制御の反転)、DIP(依存性逆転の原則)、依存性注入(DI)、IoCコンテナといった用語の説明はしません。 実行環境 .NET Framework 4.6.1 Unity Container 5.11.10 インストール 「ソリューションのNugetパッケージの管理」→「Unity」で検索 Unity Container適用対象のサンプル クラス図 依存性逆転の原則に則り、IFを定義して、DriverはIVehicleに依存し、Car/BikeはIVehicleに依存させる。 コンストラクタで外部から具象クラスインスタンスを渡して、振る舞いを変える(コンストラクタインジェクション)。 クラスの実装 public interface IVehicle { int Run(); } public class Car : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public class Bike : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public class Driver { private IVehicle _vehicle = null; [InjectionConstructor] public Driver(IVehicle vehicle ) { _vehicle = vehicle; } public void Run() { Console.WriteLine($"Run {_vehicle.GetType().Name} - {_vehicle.Run()} mile"); } } Unity Containerの適用 1. Unity Containerインスタンスを作成する using System; using Unity; using Unity.Injection; static void Main(string[] args) { IUnityContainer container = new UnityContainer(); //(略) } 2. Register -[抽象クラスor IF]と[具象クラス]をマッピング- IFを継承しているクラスが複数存在するため、文字列でラベリング(※)する。 ※RegisterType()の引数に文字列を渡している箇所 ラベリングしない場合は、IVehicle/Driverをキーに検索を掛けたとき、 後に登録した方がヒットする。 static void Main(string[] args) { //(略) // Register Named Type container.RegisterType<IVehicle, Car>(nameof(Car)); container.RegisterType<IVehicle, Bike>(nameof(Bike)); container.RegisterType<Driver>(nameof(Car) + "Driver", new InjectionConstructor(container.Resolve<IVehicle>(nameof(Car)))); container.RegisterType<Driver>(nameof(Bike) + "Driver", new InjectionConstructor(container.Resolve<IVehicle>(nameof(Bike)))); //(略) } 3.Resolove -マッピングした情報を元に依存性を注入する- static void Main(string[] args) { //(略) if (container.Resolve<IVehicle>(nameof(Car)) is Car) { Console.WriteLine("Return Car"); } if(container.Resolve<IVehicle>(nameof(Bike)) is Bike) { Console.WriteLine("Return Bike"); } var CarDriver = container.Resolve<Driver>(nameof(Car) + "Driver"); CarDriver.Run(); var BikeDriver = container.Resolve<Driver>(nameof(Bike) + "Driver"); BikeDriver.Run(); //(略) } 実行結果 Return Car Return Bike Run Car - 1 mile Run Bike - 1 mile 参考 Unty Container Documentation Tutorials Teacher Unity Containerのパフォーマンスに関して GitにIoC Containerのパフォーマンスの一覧が公開されている。 IoC Performance
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】Unity Container(IoC Container)の使い方

概要 Unity Containerの使い方の備忘録です。 Unity Containerの解説は英語ばかりで、日本語の解説が少ないため投稿した。 前置き 1.ゲームエンジンのUnityではありません。念のため。 2.本記事では使い方にフォーカスするため、IoC(制御の反転)、DIP(依存性逆転の原則)、依存性注入(DI)、IoCコンテナといった用語の説明はあまりしません。 下記がわかりやすいので、そちらを参照してください。 DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる 実行環境 .NET Framework 4.6.1 Unity Container 5.11.10 インストール 「ソリューションのNugetパッケージの管理」→「Unity」で検索 Unity Containerサンプル-IFの利用者が単数- クラス図 IFを定義し、DriverはIVehicleに依存し、Car/BikeはIVehicleに依存させる。 クラスの実装 Dependency Injectionパターンのコンストラクタインジェクションを使う。 コンストラクタで外部から具象クラスインスタンスを渡して、振る舞いを変える。 public interface IVehicle { int Run(); } public class Car : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public class Bike : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public class Driver { private IVehicle _vehicle = null; [InjectionConstructor] public Driver(IVehicle vehicle ) { _vehicle = vehicle; } public void Run() { Console.WriteLine($"Run {_vehicle.GetType().Name} - {_vehicle.Run()} mile"); } } Unity Containerの適用 Dependency Injectionパターンのコンストラクタインジェクションを使う上で、下記作業が必要になる。 これをUnity Containerにやってもらう。 ・抽象クラスと具象クラスの対応付け ・具象クラスに応じたインスタンスを提供する。 1. Unity Containerインスタンスを作成する using System; using Unity; using Unity.Injection; static void Main(string[] args) { IUnityContainer container = new UnityContainer(); //(略) } 2. Register -[抽象クラス]と[具象クラス]をマッピング- static void Main(string[] args) { //(略) // 同じ型に対して、複数の型情報を登録するため名前付きにする container.RegisterType<IVehicle, Car>(nameof(Car)); container.RegisterType<IVehicle, Bike>(nameof(Bike)); container.RegisterType<Driver>(nameof(Car) + "Driver", new InjectionConstructor(container.Resolve<IVehicle>(nameof(Car)))); container.RegisterType<Driver>(nameof(Bike) + "Driver", new InjectionConstructor(container.Resolve<IVehicle>(nameof(Bike)))); //(略) } 3.Resolove -マッピング情報を基に依存性を注入- static void Main(string[] args) { //(略) if (container.Resolve<IVehicle>(nameof(Car)) is Car) { Console.WriteLine("Return Car"); } if(container.Resolve<IVehicle>(nameof(Bike)) is Bike) { Console.WriteLine("Return Bike"); } var CarDriver = container.Resolve<Driver>(nameof(Car) + "Driver"); CarDriver.Run(); var BikeDriver = container.Resolve<Driver>(nameof(Bike) + "Driver"); BikeDriver.Run(); //(略) } 実行結果 Return Car Return Bike Run Car - 1 mile Run Bike - 1 mile Unity Container適用サンプル-IFの利用者が複数- クラス図 実装 class Program { static void Main(string[] args) { IUnityContainer container = new UnityContainer(); // 同じ型に対して、複数の型情報を登録するため名前付きにする container.RegisterType<IVehicle, Car>(nameof(Car)); container.RegisterType<IVehicle, Bike>(nameof(Bike)); container.RegisterType<CarDriver>( new InjectionConstructor( new ResolvedParameter<IVehicle>(nameof(Car)) ) ); container.RegisterType<BikeDriver>( new InjectionConstructor( new ResolvedParameter<IVehicle>(nameof(Bike)) ) ); if(container.Resolve<IVehicle>(nameof(Car)) is Car) { Console.WriteLine("Return Car"); } if(container.Resolve<IVehicle>(nameof(Bike)) is Bike) { Console.WriteLine("Return Bike"); } var carDriver = container.Resolve<CarDriver>(); if (carDriver is CarDriver) { carDriver.Run(); } var bikeDriver = container.Resolve<BikeDriver>(); if (bikeDriver is BikeDriver) { bikeDriver.Run(); } } } public interface IVehicle { int Run(); } public class Car : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public class Bike : IVehicle { private int _miles = 0; public int Run() { return ++_miles; } } public abstract class Driver { protected IVehicle Vehicle = null; public void Run() { Console.WriteLine($"Run {Vehicle.GetType().Name} - {Vehicle.Run()} mile"); } } public class CarDriver : Driver { [InjectionConstructor] public CarDriver(IVehicle vehicle) { this.Vehicle = vehicle; } } public class BikeDriver : Driver { [InjectionConstructor] public BikeDriver(IVehicle vehicle) { this.Vehicle = vehicle; } } 実行結果 Return Car Return Bike Run Car - 1 mile Run Bike - 1 mile Unity Containerのパフォーマンスに関して GitにIoC Containerのパフォーマンスの一覧が公開されている。 IoC Performance 参考 Unty Container Documentation Tutorials Teacher StackOverFlow
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# dll呼び出し(P/Invoke)でShift-Jisの文字列を渡す

C#で動的ライブラリ(DLL)を利用したくなった場合、P/Invokeという方法がある。特に文字列を引数として渡したいとき、ライブラリ側が要求している文字コードがUnicode(Utf-16)であれば、C#側は何も考えず string を渡しても中で色々変換したりしなかったりしてくれて、正常に動作する。 例 // ライブラリ側の関数定義がこのような場合 void SomeMethod(const char *hoge); // C#ではこう利用できる using System.Runtime.InteropServices; public class Program { // ... [DllImport("lib.dll")] static extern void SomeMethod(string hoge); void Use() { SomeMethod("普通にstring渡しちゃってOK"); } } しかしライブラリ側がShift-Jisの文字列を要求している場合は、とたんに難しくなる。まずstringの自動でよしなにやってくれる部分をスルーして直接バイト列を渡せるように、extern定義の型を byte[] に変更する。 [DllImport("lib.dll")] static extern void SomeMethod(byte[] hoge); そして利用する側は、stringをShift-Jisのバイト列に変換する。 void Use() { var str = "そのままstringを渡せないので、Shift-Jisバイト列に変換!"; var bytes = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(str); SomeMethod(bytes); } これにて一件落着…あれ、動かない。いや動く?…動いたり動かなかったりする。プログラミングをやってて一番イヤなパターンの不具合発生。 実はDLL形式のライブラリで使われているC言語インタフェースでは文字列は NULL終端 というルールが採用されており、文字列を表すバイト列の最後尾に1バイト NULL を付け足して終わることになっている。(終わることになっている、というかあちらの世界には str.Length bytes.Length のような贅沢なプロパティは存在しないので、NULLが来たら終わる のようなルールがないと文字列を読み終えることすらできないのである ) …とこのような前提知識を習得してしまえば答えは簡単で、渡すバイト列の末尾に更に1要素の 0 を付け加えればいいのである。やり方はなんでもいいが、 Array.resize でサイズを増やすと増やした分は0埋めなので、これが一番カンタン。というわけで完成コードは以下の通り。 // C# dll呼び出し(P/Invoke)でShift-Jisの文字列を渡す using System.Runtime.InteropServices; public class Program { // ... [DllImport("lib.dll")] static extern void SomeMethod(byte[] hoge); void Use() { var str = "そのままstringを渡せないので、Shift-Jisバイト列に変換!"; var bytes = System.Text.Encoding.GetEncoding("Shift_JIS").GetBytes(str); System.Array.Resize(ref bytes, bytes.Length + 1); // このNULL終端が大事!! SomeMethod(bytes); } } もちろん、Shift-JIS以外でも(EUCなど) GetEncoding() の中身だけ書き換えれば応用が効く。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む