20200711のC#に関する記事は11件です。

C#でショートカットキーで他のコントロールにフォーカスする

方法

  1. フォームのプロパティで this.KeyPreview = true; する
  2. ProcessCmdKey をオーバーライドするらしい
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    switch (keyData)
    {
        case Keys.Control | Keys.F:
            textBox1.Focus();
            break;
        case Keys.Control | Keys.R:
            dataGridView1.Focus();
            break;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

MenuStrip はプロパティから設定するだけでいい。


色々と頓挫してから2年も経過しているのでC#がわからなくなっているし、色々変わっているので、もっと良い方法があるのかも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[.NET Core 3]ライブラリの動的ロード(NativeLibrary)を使ってみた

.NET Core 3から、標準(System.Runtime.InteropServices.NativeLibrary クラス)でライブラリの動的ロード、およびエクスポートされた関数のアドレスを取得できるようになったらしいです。

このツイートで知りました。

つまり、WindowsでいうとLoadLibraryGetProcAddressのP/Invokeを書く必要がなくなります。
(Linuxでのdlopendlsymは試してません)

動的ロード以外にも、DLLImportしたメソッドのライブラリの解決も制御できるようになっているようですが、この記事では動的ロードしたライブラリの関数を呼び出すだけです。

動的ロードと関数の呼び出し例

using System;
using System.Runtime.InteropServices;

class Program
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    delegate int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    static void Main(string[] args)
    {
        IntPtr user32 = NativeLibrary.Load("User32.dll");
        var fpMessageBox = NativeLibrary.GetExport(user32, "MessageBoxW");
        var messagebox = Marshal.GetDelegateForFunctionPointer<MessageBoxW>(fpMessageBox);
        messagebox(default, "NativeLibrarySample", "Caption", 0);
        NativeLibrary.Free(user32);
    }
}

なんというか、LoadLibraryGetProcAddressそのままですね。
P/Invokeの様に末尾のA/Wは自動で検索してくれません。

ポインターが得られるので、GetDelegateForFunctionPointerでデリゲート経由で呼び出す必要があります。

NativeLibrary.Loadの戻り値はせめてSafeLibraryHandleとかにくるんで欲しかった。
NativeLibrary.GetExportも型引数でデリゲートを指定させて欲しい。

C# 9.0 Preview

C# 9.0には関数ポインターの構文が入るので、同様のことをしてみました。

//    <LangVersion>preview</LangVersion>
//    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
using System;
using System.Runtime.InteropServices;

unsafe class Program
{
    static void Main(string[] args)
    {
        IntPtr user32 = NativeLibrary.Load("User32.dll");
        var fpMessageBox = NativeLibrary.GetExport(user32, "MessageBoxW");
        var messagebox = (delegate* stdcall<IntPtr, char*, char*, uint, int>)fpMessageBox;
        fixed (char* text = "NativeLibrarySample")
        fixed (char* caption = "Caption")
            messagebox(default, text, caption, 0);
        NativeLibrary.Free(user32);
    }
}

マーシャリングされないので、文字列も固定しないと行けないのが面倒ですね。
(正式版では関数ポインターの構文は少し変わるようですが。)

文字列の渡し方

文字列の渡し方が煩わしいので、下記のようのな方法を考えたが、(「安全」ではなく)動作として保証できるのかちょっとわからない…

static class Extention
{
    public static StringHolder Hold(this string s) => new StringHolder(s);

}

unsafe readonly ref struct StringHolder
{
    readonly ReadOnlySpan<char> span;
    public StringHolder(string s) => span = s;

    public char* Pointer => (char*)Unsafe.AsPointer(ref Unsafe.AsRef(in MemoryMarshal.GetReference(span)));
}

...
    messagebox(default, "NativeLibrarySample".Hold().Pointer, caption, 0);
...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET 系の DI コンテナ

やっとコードよりの話になれる!!過去の 2 記事は言語ごとの事情や、その人の経験などで色々ちょっとずつ異なることがあるので「〇〇の場合は違う」とか「こういう側面もある」とか色々コメントしやすい感じだったのですが、そのおかげで初めての Qiita のデイリーで No1 取れました。やったね!

ということで、自分の主戦場の C# での DI コンテナ事情について書いてみたいと思います。

Microsoft.Extensions.DependencyInjection

ASP.NET Core などで何も考えないと使うことになる、事実上の標準の DI コンテナです。

https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection

非常にシンプルで DI コンテナとして最低限これくらいは持ってるだろうと思われる機能だけ持ってます。

例えば、以下のようなクラスがあったとします。

interface IMyService
{
    void Greet();
}

class MyService : IMyService
{
    private readonly IMessagePrinter _messagePrinter;
    private readonly IMessageGenerator _messageGenerator;

    public MyService(IMessagePrinter messagePrinter, 
        IMessageGenerator messageGenerator)
    {
        _messagePrinter = messagePrinter;
        _messageGenerator = messageGenerator;
    }
    public void Greet() => _messagePrinter.Print(_messageGenerator.Generate());
}

interface IMessagePrinter
{
    void Print(string message);
}

class ConsoleMessagePrinter : IMessagePrinter
{
    public void Print(string message) => Console.WriteLine(message);
}

interface IMessageGenerator
{
    string Generate();
}

class MyMessageGenerator : IMessageGenerator
{
    public string Generate() => "Hello world";
}

Microsoft.Extensions.DependencyInjection を使うと上記のクラスを組み立て可能なコンテナを作って IMyService を取得して Greet を呼び出すコードは以下のような感じになります。

class Program
{
    static void Main(string[] args)
    {
        // 型の登録
        var services = new ServiceCollection();
        services.AddTransient<IMyService, MyService>();
        services.AddTransient<IMessagePrinter, ConsoleMessagePrinter>();
        services.AddTransient<IMessageGenerator, MyMessageGenerator>();

        // インスタンスを提供してくれる人を作る
        using var provider = services.BuildServiceProvider();

        var myService = provider.GetService<IMyService>();
        myService.Greet();
    }
}

実行結果は Hello world と表示されるだけです。

インスタンス管理

AddTransient で登録するとコンテナから取得するたびに別のインスタンスを返します。AddSingleton で登録すると毎回同じインスタンスになります。AddScoped で登録すると同じスコープ内だと同じインスタンスになります。
スコープを作るには ServiceCollection に BuildServiceProvider をした結果の ServiceProvider の CreateScope メソッドを使います。各クラスのコンストラクタが呼ばれたときにわかりやすいように標準出力にメッセージを出すように手を加えた後に以下のようにコードを書き替えてみました。

class Program
{
    static void Main(string[] args)
    {
        // 型の登録
        var services = new ServiceCollection();
        services.AddScoped<IMyService, MyService>();
        services.AddSingleton<IMessagePrinter, ConsoleMessagePrinter>();
        services.AddSingleton<IMessageGenerator, MyMessageGenerator>();

        // インスタンスを提供してくれる人を作る
        using var provider = services.BuildServiceProvider();

        Console.WriteLine("Scope1");
        using (var scope = provider.CreateScope())
        {
            var s = scope.ServiceProvider.GetService<IMyService>();
            s.Greet();
        }

        Console.WriteLine("Scope2");
        using (var scope = provider.CreateScope())
        {
            var s = scope.ServiceProvider.GetService<IMyService>();
            s.Greet();
        }
    }
}

MyService が AddScoped で残りは AddSingleton にしてみました。
実行すると以下のようになります。

Scope1
ConsoleMessagePrinter のコンストラクタ
MyMessageGenerator のコンストラクタ
MyService のコンストラクタ
Hello world
Scope2
MyService のコンストラクタ
Hello world

Singleton のものはスコープが変わってもインスタンスは新たに作られなくて、AddScoped で登録したものはスコープが変わると再生成されてることがわかります。

生成処理をカスタマイズしたい

AddScoped や AddTransient や AddSingleton はラムダ式を受け取るオーバーライドがあって、それを使うとオブジェクトの生成処理をカスタマイズできるようになっています。

例えば MyService の生成ロジックを自前のものに置き換えたコードを以下に示します。ちなみに、このコードの場合は別に生成処理を変えたところで意味はありません。単純に new してるだけなので。

class Program
{
    static void Main(string[] args)
    {
        // 型の登録
        var services = new ServiceCollection();
        services.AddScoped<IMyService, MyService>(provider =>
        {
            // ここで任意の生成ロジックを入れることが出来る
            var printer = provider.GetRequiredService<IMessagePrinter>();
            var generator = provider.GetRequiredService<IMessageGenerator>();
            return new MyService(printer, generator);
        });
        services.AddSingleton<IMessagePrinter, ConsoleMessagePrinter>();
        services.AddSingleton<IMessageGenerator, MyMessageGenerator>();

        // インスタンスを提供してくれる人を作る
        using var provider = services.BuildServiceProvider();

        Console.WriteLine("Scope1");
        using (var scope = provider.CreateScope())
        {
            var s = scope.ServiceProvider.GetService<IMyService>();
            s.Greet();
        }

        Console.WriteLine("Scope2");
        using (var scope = provider.CreateScope())
        {
            var s = scope.ServiceProvider.GetService<IMyService>();
            s.Greet();
        }
    }
}

実行結果は同じです。

Microsoft.Extensions.DependencyInjection について深く知りたい人は、Microsoft.Extensions.DependencyInjection Deep Dive を見てみるといいと思います。

他の DI コンテナと使いたい

とまぁ、こんな感じで必要最低限の機能セット(登録と取得とシンプルなライフサイクル管理とインスタンス生成のカスタマイズ)がある程度なのですが、もうちょっと高度な機能を持った DI コンテナを使いたいという要望に応えられるようになっています。

以下にリストがあります。

https://github.com/dotnet/runtime/tree/master/src/libraries/Microsoft.Extensions.DependencyInjection

試しに Unity を使ってみましょう。Unity は昔は Microsoft がメンテナンスしてた OSS の DI コンテナで、今は完全に Microsoft から離れてメンテナンスされています。

Unity.Microsoft.DependencyInjection パッケージを追加することで Unity が使えるようになります。ただの DI コンテナとして使うだけなら別に Unity をあえて使う必要はないので、追加で Unity.Interception パッケージも追加してみようと思います。

ということでこんな感じで IMyService はログを出すような追加処理が入るようにしてみました。

using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using Unity;
using Unity.Interception;
using Unity.Interception.ContainerIntegration;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.Interceptors.InstanceInterceptors.InterfaceInterception;
using Unity.Interception.PolicyInjection.Pipeline;
using Unity.Lifetime;
using Unity.Microsoft.DependencyInjection;

namespace UnityLab
{
    public interface IMyService
    {
        void Greet();
    }

    public class MyService : IMyService
    {
        private readonly IMessagePrinter _messagePrinter;
        private readonly IMessageGenerator _messageGenerator;

        public MyService(IMessagePrinter messagePrinter, 
            IMessageGenerator messageGenerator)
        {
            _messagePrinter = messagePrinter;
            _messageGenerator = messageGenerator;
        }
        public void Greet() => _messagePrinter.Print(_messageGenerator.Generate());
    }

    public interface IMessagePrinter
    {
        void Print(string message);
    }

    public class ConsoleMessagePrinter : IMessagePrinter
    {
        public void Print(string message) => Console.WriteLine(message);
    }

    public interface IMessageGenerator
    {
        string Generate();
    }

    public class MyMessageGenerator : IMessageGenerator
    {
        public string Generate() => "Hello world";
    }


    public class LogBehavior : IInterceptionBehavior
    {
        private readonly IMessagePrinter _messagePrinter;

        public bool WillExecute => true;

        public LogBehavior(IMessagePrinter messagePrinter)
        {
            _messagePrinter = messagePrinter;
        }

        public IEnumerable<Type> GetRequiredInterfaces() => Type.EmptyTypes;

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            _messagePrinter.Print($"Begin: {input.MethodBase.Name}");
            try
            {
                var result = getNext()(input, getNext);
                _messagePrinter.Print($"End: {input.MethodBase.Name}");
                return result;
            }
            catch (Exception ex)
            {
                _messagePrinter.Print($"Exception: {input.MethodBase.Name}, {ex}");
                throw;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 型の登録
            var services = new ServiceCollection();
            services.AddSingleton<IMessagePrinter, ConsoleMessagePrinter>();
            services.AddSingleton<IMessageGenerator, MyMessageGenerator>();

            // Unity のコンテナに登録してログ機能も追加
            var container = new UnityContainer()
                .AddNewExtension<Interception>();
            container.RegisterType<IMyService, MyService>(
                new SingletonLifetimeManager(),
                new Interceptor<InterfaceInterceptor>(),
                new InterceptionBehavior<LogBehavior>());

            // インスタンスを提供してくれる人を作る
            var provider = services.BuildServiceProvider(container);
            var s = provider.GetService<IMyService>();
            s.Greet();
        }
    }
}

実行すると以下のような感じになります。

Begin: Greet
Hello world
End: Greet

内部的には ServiceCollection に登録されている情報を見て UnityContainer に登録処理をして、UnityContainer をラップする IServiceProvider が作られてる感じです。
なので、ServiceCollection で登録したやつも UnityContainer で登録したやつも同じコンテナにあるように(実際同じコンテナにあるので)インジェクション出来ます。

まとめ

ここら辺まで出来たら、あとは ASP.NET Core あたりのドキュメントを見ながらぽちぽちやってみるのがいいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blazorでログインページを実装する

この記事は2か月くらい前のものでずっと下書きにいれていたものです
続きに何を書くか忘れたので投下します。

ウチのおとんのWEBサイトを作ることに。
C#が元々好きでBlazorに興味があったのでこれで実装しようと考える。
※WebAssemblyの方(IE11未対応)

適当なかっちょいいレイアウトをCodePenから拝借して、ログインページを作ることにしたが
BlazorのサンプルプロジェクトはSPAを前提としているからか、左サイドバーメニュー、ヘッダが表示されてしまいなんかヤダ
ログインページを全画面に出したいのに・・・

を解決した時のメモ

ログインページのレイアウト

レイアウトはCodePenから拝借しました。
https://codepen.io/Lewitje/pen/BNNJjo

ライセンス表記はcss、razorと個別に行うのは面倒だったので、プロジェクト直下にLicense.txtを作成してそこに記述しました。

Lisence.txt
Copyright (c) 2020 by Lewi Hussey (https://codepen.io/Lewitje/pen/BNNJjo)
Released under the MIT license
https://opensource.org/licenses/mit-license.php

MITライセンスはライセンスが表記されているURLを記述すれば良いみたい。

でも唐突にライセンスの記載が始まる上記の書き方でいいのだろうか・・・。
「このアプリケーションはどこどこのコードで以下のライセンスに乗っ取ったコードを使用しています」や、「このアプリケーションは以下のOSSを含む。XXXは何々ライセンス~」みたいな感じで記載しなくてもよいのだろうか。

いまだにライセンス表記を何処にどういった記載をすれば良いか完全に理解できていない。

.net用のgitignoreを自動で作ってくれる
dotnet new gitignore

でも私はTortoiseGitでGUIで自分でやりましたw

画面全体にでない・・
それに左サイドメニューとヘッダが邪魔だぁ
image.png

以下の動画を参考
Blazor Tutorial : Layouts | Login Pages - EP13

理由はDefaultLayoutが@typeof(MainLayout)になっているからと思われ

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

そこで、別途ログイン用のレイアウトを用意する LoginLayout.razor
image.png

@inherits LayoutComponentBase

<div class="col-12">
    <br /><br />
    @Body
</div>

Login.razor側で使用するLayoutを指定する → @layout LoginLayout

Login.razor

@page "/login"
@layout LoginLayout
@inject IJSRuntime JSRuntime

<div class="wrapper">
    <div class="container">
        <h1 class="text-white">Welcome</h1>

        <form class="form">
            <input type="text" placeholder="Username" @bind="@UserId">
            <input type="password" placeholder="Password" @bind="@Password">
            <button type="button" id="login-button" @onclick="this.Logon">Login</button>
        </form>
    </div>

    <ul class="bg-bubbles">
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</div>

@code {
    /// <summary>
    /// ユーザーID
    /// </summary>
    [Parameter]
    public string UserId { get; set; }

    /// <summary>
    /// パスワード
    /// </summary>
    [Parameter]
    public string Password { get; set; }

    /// <summary>
    /// ログイン処理
    /// </summary>
    private void Logon()
    {
        JSRuntime.InvokeAsync<string>("console.log", this.UserId + this.Password);
    }
}

razorのシンタックスハイライトないんすか汗

この記事は2か月くらい前のものでずっと下書きにいれていたものです
今はログイン画面以外にも機能追加して、AWS EC2上(Amazon Linux 2)に乗っけてます。セキュアではありませんが・・・(http)

すんません。https化しようとして失敗して今アクセスできません
http://d.godphwng.net/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポケモン剣盾の乱数調整作業自動化のカスタマイズ

PokemonSWSH_SeedSearchSupport

ポケモン剣盾の乱数調整を手助けするためのツール作成をしました。乱数調整特化型のカスタマイズです。
使用や準備等におけるトラブルへの対応はできません。すべて自己責任のもと行ってください。
また、C#処女作のため、コーディングに不備があるかもしれません。ご指摘があれば勉強させていただきたいです。

ポケモン剣盾乱数調整作業自動化@chibi314さん)
をもとに作成しています。公開許可を下さったことと合わせてお礼申し上げます。

動機

  • ワンボタンで大抵のことをやりたかった
    • スマホやタブレットから操作ができるようにしたかった(リモートデスクトップ利用)
      • ディスプレイが1枚しかなくて困っていた
  • 下記のような地味に繰り返しの作業が多くて疲れた
    • 4日目(1体目)が目的のポケモンのレイドでない場合に、レポート・次の4日目へ進む
    • レイド後に経験飴を与えてレベル100にする
    • ステータスを表示する  など

やれること

コメント 2020-07-11 004418.jpg

  • 市販のコントローラでの操作(xInput\directInput)
  • 乱数調整に必要な操作・ボタンに書いてある通り

    • スマホ(リモートデスクトップ)からの操作が楽に
    • もちろんPCからもワンタッチで大体できる
  • 本体ごと、ソフトの種類ごと(DL、パッケージ版)で待機時間が異なるので、思うように動作しない場合があります

    • ソースを公開しているので、自分に合った待機時間に調整してください
    • とりあえずちょっと待機時間を長くすると、解決する場合が多いです
    • C#は初めて触ったので、変な箇所があるかもしれません 適宜修正して使ってください
  • 本ツールはゲームの操作を補助するものであり、seed特定ツールではありません

用意するもの

ポケモン剣盾乱数調整作業自動化 と同じです。
リンクは文中に。要望があれば詳しくかきます。

  • 各種プログラム
    • 本ツール(PokemonSWSH_SeedSearchSupport)
    • Arduinoに書き込むSwitch操作用ライブラリ(pokemon_automation_arduino_firmware)
    • Arduinoにプログラムを書き込むためのツール(Arduino IDE)
    • Arduinoをコントローラとして認識させるツール(ArduinoSTL)
  • .NET環境のあるWindowsPC (Windows 10以外は未検証)
  • マイコン(Arduino Leonard 若しくはその互換)
  • FTDI FT232 (USBシリアル変換)
  • PCとArduinoをつなぐUSBケーブル
  • ArduinoとFTDI FT232をつなぐジャンパ線
  • FTDI FT232とSwitchをつなぐUSBケーブル
  • USB接続のコントローラ(F310を使用しました)

導入

  1. ツールのダウンロード
  2. マイコン(Arduino Leonard)への書き込みと接続

使う前のSwitch側の設定

  • 本体設定の「インターネットで時間を合わせる」をOFFにする
  • Xメニューを下図のように配置する

    • Ecn-KzxUEAERt9B.jpg
  • バッグの道具を、けいけんアメが一番上に来るようにする(Lv100にできるだけの数があること)

    • EcoGKZ2U8AAy4RL.jpg
  • 手持ちのポケモンをレイド用1体のみにする

    • EcoGKZ1U0AMm6FQ.jpg

使い方

基本的に見たままの内容です。

  1. シリアルポートの指定と、コントローラ(PCに接続)があればその入力形式(xInput/directInput)を選択

    • コントローラを動かしてみて、右側のINPUTのテキストが想定通りに変わればOKです
    • 左十字キーの反応がどうにも悪いです、改善策あればご教示ください 1.jpg
  2. 日付を指定

    • ゲーム画面を表示した状態で変更ボタンを押すと、本体日付とツールの日付が変更されます
      • HOME⇒設定⇒本体⇒日付と時刻⇒日付変更⇒HOMEで戻ってきます 2.jpg
  3. 4日目のポケモンとレイドバトルする

    • レイド表示の状態で使用するボタンです
      • 日付調整 みんなで挑戦⇒HOME⇒設定⇒本体⇒日付と時刻⇒日付変更⇒HOMEで戻ってきます
      • レポート後次の4日目へ レイドキャンセル⇒Xメニュー⇒レポート⇒レイド表示(以下日付調整と同様)
      • レイド開始(みんなで) レイドを始める直前まで進みます
      • レイド開始(ひとりで) レイドを始めます
    • 3日進めて目的のポケモン(2V,3V)でない場合に、「レポート後次の4日目へ」を、目的のポケモンが出るまで繰り返します
      3.jpg
      • ↓この画面のときに使うボタンです
      • EcnpJbbU0AALCxZ.jpg
    • レイドが開始後、ダイマックスして戦うボタン
      • 押したあとは基本Aボタン連打を押しておけば大抵は勝てます 6.jpg
  4. 目的のポケモンを捕まえたらLv100にする

    • レイド終了後、バッグを開いてけいけんアメをMAXまで与えます
      4.jpg
    • フィールド上で押してください
      • EcnppxgU8AI9YJQ.jpg
  5. ステータスを確認する

    • 最初に入力するであろう性格を表示します
    • バッグを開いた状態でも、フィールド上でも使えます 5.jpg
  6. 4日目(1体目)の確認後、次のポケモンを捕獲する

    • N日目の個体 HOME⇒ゲームの終了⇒ゲームの起動⇒レイド表示⇒本体日付の変更⇒HOME で戻ってきます
    • 進めたい日数に合わせてボタンを選んで下さい
    • ゲームの再起動をするので、ゲーム内であればどの画面からでも使用できます 7.jpg
  7. seed特定後、乱数を消費する(ランクマ後

    • N日進める HOME⇒設定⇒本体⇒日付と時刻⇒2000/1/1にしてから、指定値を消費(日付設定画面のまま終了します)
      • 2060年以降はうまく動作しないそうなので、最大消費数をとれるよう日付を最小値にしてから開始しています
    • ワイルドエリアからポケモンセンターの中に移動した状態で使います
      • ワイルドエリアで行うとエラーが発生しやすいそうです 8.jpg EcoMcpLUYAIDx2H.jpg

最後に

  • @chibi314 様の日付すすめボタンとは少し仕様が違うので注意してください
    • 本ツールはゲーム画面の状態から始めます
  • C#でのコントローラ実装がうまくできなくて、なんだかんだやってるうちにdirectInputもxInputも対応できたんですが、左十字キーだけうまいこと動作してくれません(認識はしている)
    • 解決法あれば教えてください
  • 実はキーボードの十字キーとテンキーのエンターが使えるんです……内緒ですよ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

特許技術者の憂鬱・明細書の段落番号付与

これは、フリーソフト「段落ジェネレータ」の紹介記事です。

GitHub のURLはこちらです。Visual Studio 2019 でコンパイルしてください。
https://github.com/k-ayaki/danrakuG01

OSDN のURLはこちらです。インストーラがアップロードされています。
https://osdn.net/projects/danrakug/

ユースケース

或るブラック特許事務所の午後。

ワイ「あー、今日もワード、明日もワードで明細書書きや。コロナ禍いうても出社してるんや。」
ワイ「かといってテレワークやと画面の反応が遅くてストレスやしなあ。」
ブラック所長「今日が納品締切日の、A社さんへの特許明細書できたか?」
ワイ「はい、できました!」

ワイ君の明細書原稿
【書類名】明細書
【発明の名称】物質エネルギ変換装置
【技術分野】
 【0001】
 本発明は、物質をエネルギに変換する物質エネルギ変換装置である。
【背景技術】
 【0002】
 従来、エネルギを得るには、化学反応または核反応を用いる必要があり、効率が悪かっ
た。
 【0003】
 非特許文献1には、反物質が物質と合わさって厖大なエネルギを発することが記載され
ている。
【先行技術文献】
 【0004】
 【非特許文献】
 【0005】
  【非特許文献1】ダン・ブラウン、"天使と悪魔”、2000年初版
【発明の概要】
【発明が解決しようとする課題】
 【0006】
 非特許文献1では、反物質を生成する必要があった。反物質の生成には、この反物質が
物質と合わさってエネルギを生成する以上のエネルギが必要であった。
 【0006】
 そこで、本発明は、物質をそのままエネルギに変換することを課題とする。
【課題を解決するための手段】
 【0007】
 本発明の物質エネルギ変換装置は、水素を入力する水素入力手段と、当該水素を吸収す
る希土類金属結晶部と、当該希土類金属結晶部の所定の結晶方向に磁場を与える磁場形成
部とを有する。
【発明の効果】
 【0008】
 本発明によれば、高い効率で物質をエネルギーに変換することができる。
【図面の簡単な説明】
 【0009】
 【図1】本実施形態の物質エネルギ変換装置の構成図である。
 【図2】物質エネルギ変換装置の動作を説明する図である。
【発明を実施するための形態】
 【0010】
 図1は、本実施形態の物質エネルギ変換装置1の構成図である。
 【0010】
 物質エネルギ変換装置1は、水素入力部11と、希土類金属結晶12と、磁場形成部1
3と、これらを包み込む中性子遮蔽部14とを備える。この物質エネルギ変換装置1は、
水素原子を直接にエネルギに変換するものである。
 【0011】
 水素入力部11は、・・・・・・・・・・・・・・・・・・・・・・・・である。
 【0012】
 希土類金属結晶12は、・・・・・・・・・・・・・・・・・・・・・・・・であり、水
素入力部11から入力された水素を吸収して・・・・・・・・・・・・・・・・・・・・・
・・・する。この希土類金属結晶12は、・・・・・・・・・・・・・・・・・・・・・・
・することにより、・・・・・・・・・する。
 【0013】
 磁場形成部13は、・・・・・・・・・・・・・・・・・であり、希土類金属結晶の11
1方向の磁場を形成して、・・・・・・・・・、・・・・・・・・・・・・・・・・・・・
する。これにより、希土類金属結晶12に吸収されている水素原子は、・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・する。
 【0014】
 中性子遮蔽部14は、水素原子のエネルギ化に伴い、希土類金属結晶12から出射される
中性子線を遮蔽する。この中性子遮蔽部14は、例えば・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
・・・であり、・・・・・・・・・する。
 図2は、物質エネルギ変換装置の動作を説明する図である。
 【0015】
 水素は、水素入力部11によって高圧(例えば、10万メガパスカル)に圧縮されて、希
土類金属結晶12が位置する炉内に導かれる。希土類金属結晶12の水素吸収能力により、
水素は、希土類金属結晶12に吸収される。そして、磁場形成部13は、・・・・・・・・
・・・・・・・・・・・・・。これにより、希土類金属結晶12からエネルギが放出され、
中性子線が出射される。
 【0016】
 物質エネルギ変換装置1が変換するエネルギEは、以下の式(1)によって示される。
但し、Mは水素の質量である。Cは光速である。
 【数1】
    E=MC^2・・・(1)
 よって、本実施形態の物質エネルギ変換装置1によれば、反物質を生成することなく、
物質を直接的にエネルギに変換することができる。
【符号の説明】
 【0017】
1 物質エネルギ変換装置
11 水素入力部
12 希土類金属結晶
13 磁場形成部
14 中性子遮蔽部

ブラック所長「なんやこれは、段落番号がデタラメやないか!」
ブラック所長「なんで【先行技術文献】の直後に段落番号【0004】を入れてるんや! 段落番号を入れてもええ項目と、段落番号を入れてはいかん項目を覚えや。」
ブラック所長「ほんで、数式【数1】と文章との区切りには段落番号を入れんとな。」
ブラック所長「【0010】の次にまた【0010】があるぞ、連番で記載な!」
ブラック所長「図1の説明のあたりは段落番号を入れすぎ! 改行があるからいうて、1行ごとに機械的に入れたらあかん。クライアント企業から水増しやゆうてクレームくるぞ。」
ブラック所長「ここ、『図2は、物質エネルギ変換装置の動作を説明する図である。』ゆうて、違う図面のことを書き始めとるから、こういうところに段落番号を入れんとあかんのやが、なんでここに入っとらんのや。この次に段落番号【0015】が入っとるけど、ここは意味としての区切りやないやろ。」

ワイ「は、はい(白目)」
ブラック所長「段落番号の振り方がええ加減やと、クライアント企業に、中身もその程度の品質だって思われるぞ!」
ブラック所長「納品時に厳しくチェックされるし注意せなあかん!」
ワイ「はい(白目)」
ブラック所長「ほな、ワシはとりあえず帰るぞ。」
・・・
ワイ「あー、ブラック所長はとっとと帰ってしもうたか。なんや最近の所長は、どうでもいい重箱の隅つつきばっかやなあ。まあええわ、こんな重箱の隅なところは、プログラムで生成したるぞ。」

ワイ「段落番号を連番にするだけやったら、WORDのフィールドコードでもええんやがな。段落番号を入れてはいけない項目だとか、段落番号を入れなきゃいけない項目だとか、覚えるのも面倒だし。意味の区切りとかもあるしな。」

特許明細書の書式調査

ワイ 「特許明細書の書式について書かれた文献を見つけたで。根拠条文は、特許法施行規則第24条の様式第29やな。」

https://www.jpo.go.jp/system/laws/rule/guideline/document/syutugan_tetuzuki/02_03.pdf

「18 明細書(配列表は除く。)には、原則として、発明の詳細な説明の段落、図面の簡単な説明の図の説明若しくは符号の説明又は配列表フリーテキストの繰り返し記載の前に、それぞれ「【」及び「】」を付した4桁のアラビア数字で「【0001】」、「【0002】」のように連続した段落番号を付す。

ワイ 「つまり、段落番号は4桁の連番ということやな。」

この場合において、「【技術分野】」、「【背景技術】」、「【特許文献】」、「【非特許文献】」、「【発明の概要】」、「【発明が解決しようとする課題】」、「【課題を解決するための手段】」、「【発明の効果】」、「【図面の簡単な説明】」、「【発明を実施するための形態】」、「【実施例】」、「【産業上の利用可能性】」、「【符号の説明】」、「【受託番号】」又は「【配列表フリーテキスト】」の見出しの次に段落番号を付し、これらの見出しの前に段落番号を付してはならない。

ワイ 「【先行技術文献】は、ここに挙げた中にないのに段落番号を付けたからあかんかったのな。」

また、「【特許文献1】」、「【非特許文献1】」、「【化1】」、「【数1】」、「【表1】」、「【図1】」のような番号の次に段落番号を付してはならない。」

ワイ 「他に段落番号を付けたらあかんのは、「【書類名】」、「【発明の名称】」、「【発明の概要】」やな。」

従来技術調査

ワイ 「vectorとかで段落番号の自動付与や振り直し機能を持つものは色々あるなあ、DANRAKU, 特許明細書の段落番号自動置換マクロ, 特許出願支援プログラム 段落振り版, 明細書再付番マクロ・・・」
ワイ 「ユーザインタフェースは参考になるな、段落番号を自動付与する機能、段落番号を連番に振り直す機能、段落番号を削除する機能があればええんやな。」
ワイ 「でも、どのソフトウエアも、ブラック所長の好みにあいそうな段落番号の振り方じゃないなあ・・・。」

名称 概要 作者
DANRAKU 【段落番号】の自動付与・振り直しなどを1クリックで行うためのアドイン 英文明細書の形式にも対応 Clockahead
特許明細書の段落番号自動置換マクロ(Word用) 明細書中の段落番号を自動的に振り直す(新旧様式対応) 岡田稔
特許出願支援プログラム 段落振り版 特許明細書のWordファイルに対して新規段落振り・段落振り直しを行う ソフト研究会(ライフラボ株式会社)
特許明細書の段落番号等を操作するマクロ 番号付与や再付与をするマクロ 【化】【数】【表】や【化1-1】等、HTMLファイルにも再付与が可能 えの
明細書再付番マクロ(MS-Word) 特許明細書の各種項番を振り直す (有)DJソフト

仕様

ワイ 「Visual Studio 2019でWORD VSTO (Visual Studio Tools for Office)アドインとして開発しよ。」
同僚X「おお、ワイ君はなんかまた作り始めたなあ。」
ワイ 「ええ、段落番号の入力を自動化するんですよ。」
同僚X「ちゃんとできたら皆にも公開な。」
ワイ 「は、はい。・・・(公開を考えて、VBAじゃなくVSTOで作成して良かった・・・)」

セットアップ

ワイ 「はい、エクスさん使ってみてください。」
同僚X「おお、ちゃんとセットアップもあるなあ。」
ワイ 「・・・(Visual Studioの発行メニューをクリックしただけやけどな。)」
同僚X「なんやエラーが出てインストールできないぞ。」
01SmartScreen.png
ワイ 「これは、詳細情報をクリックしてください。」

02SmartScreen.png
ワイ 「この実行ボタンをクリックします。」
03vstoerror.png

同僚X「なんやエラーが出てインストールできないぞ。」
ワイ 「おかしいなあ」
・・・
ワイ 「わかりましたよ。インターネットからダウンロードしたファイルには、『ゾーン識別子』が付いてるんですけど、このせいです。対策しますから待ってください。」

VSTOを用いて作成したアドインの配布方法
https://wiki.clockahead.com/index.php?Coding%2FMSOffice%2FVSTO%2F%C7%DB%C9%DB%A4%CE%CA%FD%CB%A1

VSTOにおいてSecurityExceptionによってファイルがインストールできないとき
http://artfulplace.hatenablog.com/entry/2014/05/26/154251

セットアップ再び

ワイ 「はい、エクスさん使ってみてください。」
同僚X「Install.batをクリックするんやな。」

03vsto.install.png

同僚X「セットアップは無事終わり。そしたら、WordにAppLintとかいうリボンができるんだな。」

ワイ 「これが段落追加ボタンです。ボタンを押すと段落が自動で付きますよ。」
add1.png

ワイ 「これが段落振り直しボタンです。ボタンを押すと、既存の段落を追番に振り直します。」
renum1.png

ワイ 「これが段落削除ボタンです。最初はまっさらにした方がいいときもあります。」
del1.png

同僚X「クライアントさんから帰ってきたチェック済原稿を開いて、段落番号追加ボタンをクリック、と。」
変更履歴on.png

同僚X「おい、校閲の変更履歴が記録されまくって、グダグダになってしまっているぞ。」
ワイ 「ええっ、エクスさん、ワード文書の変更履歴の記録はオフにしていただかないと。」
同僚X「なんとかならんかなあ、他人から回されてきたワード原稿とかだと、変更履歴の記録がオンされているか否かなんて、
    ワードの校閲リボンから、「変更履歴の記録」をクリックせんとわからんしなあ。」
ワイ 「そ、そうですねえ。」
同僚X「それに、これっていつ始まって、いつ終わったかもわからんなあ。中断もできんし。」
ワイ 「短時間やからええと思ってましたが、気になるもんですねえ。」
同僚X「ほら、画面上に横棒が表示されて、それが100%になったときに終わるやつをつけてや。
    途中で止めるためのボタンもつけてな。」
ワイ 「は、はい。・・・(ブログレスバーとキャンセルボタンのことだな、)」

同僚X「僕の持っている明細書原稿を全部掛けてみようか。」
    ・・・
同僚X「ワイ君、なんか、この明細書原稿、ぜんぜん段落番号の削除が効かないけど。」
ワイ 「あ、あれっ変ですねえ。」
改行.png

ワイ 「この明細書原稿の改行マーク、なんかおかしくないですか。」

    ・・・・

ワイ 「エクスさん、わかりましたよ。この明細書原稿って、Enterじゃなくて、Shift+Enterで行の区切りが入力されているんですよ。」
同僚X「ふーん、なんか知らんけど、どういう風に入力してもちゃんと段落番号を付けるようにしてな。」
ワイ 「・・・(Enterで入力されるのが ^pで、Shift+Enterで入力されるのが ^l か、全部 ^pに置換するか・・・)」

改良版

ワイ 「はい、エクスさん、改良しましたので使ってみてください。」
同僚X「このあいだのクライアントさんのチェック済原稿を掛けてみよう。
    ちゃんと、「校閲の変更履歴の記録をオフしてください。」で出るなあ。」
ワイ 「はい、校閲の変更履歴の記録かオンのときには、処理せずに警告するようにしたんですよ。」
同僚X「校閲の変更記録をオフして、段落番号振直ボタンをクリック、と。
    進捗を示す棒グラフと、キャンセルボタンも表示されたなあ。」
ワイ 「はい」

同僚X「ぜんぜん段落番号を振ってくれなかった明細書原稿もチェック、と。
    おお、こんどはちゃんと段落番号を付けてくれるなあ。」
ワイ 「はい。対応しました。
    (こっそりと ^l を ^p に書き換えるのでOKだったか。)

ブラック所長「おお、ワイ君やないか。ほう、段落番号を入れてくれるWORDアドインか。」
ワイ 「は、はい。」
ブラック所長「なかなかええ感じに振られてるなあ。
       このあいだ苦言を呈させてもろた原稿を掛けてみよっか。」
ワイ 「はい」

ワイ君の明細書原稿
【書類名】明細書
【発明の名称】物質エネルギ変換装置
【技術分野】
 【0001】
 本発明は、物質をエネルギに変換する物質エネルギ変換装置である。
【背景技術】
 【0002】
 従来、エネルギを得るには、化学反応または核反応を用いる必要があり、効率が悪かっ
た。
 非特許文献1には、反物質が物質と合わさって厖大なエネルギを発することが記載され
ている。
【先行技術文献】
 【非特許文献】
 【0003】
  【非特許文献1】ダン・ブラウン、"天使と悪魔”、2000年初版
【発明の概要】
【発明が解決しようとする課題】
 【0004】
 非特許文献1では、反物質を生成する必要があった。反物質の生成には、この反物質が
物質と合わさってエネルギを生成する以上のエネルギが必要であった。
 そこで、本発明は、物質をそのままエネルギに変換することを課題とする。
【課題を解決するための手段】
 【0005】
 本発明の物質エネルギ変換装置は、水素を入力する水素入力手段と、当該水素を吸収す
る希土類金属結晶部と、当該希土類金属結晶部の所定の結晶方向に磁場を与える磁場形成
部とを有する。
【発明の効果】
 【0006】
 本発明によれば、高い効率で物質をエネルギーに変換することができる。
【図面の簡単な説明】
 【0007】
 【図1】本実施形態の物質エネルギ変換装置の構成図である。
 【図2】物質エネルギ変換装置の動作を説明する図である。
【発明を実施するための形態】
 【0008】
 図1は、本実施形態の物質エネルギ変換装置1の構成図である。
 物質エネルギ変換装置1は、水素入力部11と、希土類金属結晶12と、磁場形成部1
3と、これらを包み込む中性子遮蔽部14とを備える。この物質エネルギ変換装置1は、
水素原子を直接にエネルギに変換するものである。
 【0009】
 水素入力部11は、・・・・・・・・・・・・・・・・・・・・・・・・である。
 希土類金属結晶12は、・・・・・・・・・・・・・・・・・・・・・・・・であり、水
素入力部11から入力された水素を吸収して・・・・・・・・・・・・・・・・・・・・・
・・・する。この希土類金属結晶12は、・・・・・・・・・・・・・・・・・・・・・・
・することにより、・・・・・・・・・する。
 【0010】
 磁場形成部13は、・・・・・・・・・・・・・・・・・であり、希土類金属結晶の11
1方向の磁場を形成して、・・・・・・・・・、・・・・・・・・・・・・・・・・・・・
する。これにより、希土類金属結晶12に吸収されている水素原子は、・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・する。
 【0011】
 中性子遮蔽部14は、水素原子のエネルギ化に伴い、希土類金属結晶12から出射される
中性子線を遮蔽する。この中性子遮蔽部14は、例えば・・・・・・・・・・・・・・・・
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
・・・であり、・・・・・・・・・する。
 【0012】
 図2は、物質エネルギ変換装置の動作を説明する図である。
 水素は、水素入力部11によって高圧(例えば、10万メガパスカル)に圧縮されて、希
土類金属結晶12が位置する炉内に導かれる。希土類金属結晶12の水素吸収能力により、
水素は、希土類金属結晶12に吸収される。そして、磁場形成部13は、・・・・・・・・
・・・・・・・・・・・・・。これにより、希土類金属結晶12からエネルギが放出され、
中性子線が出射される。
 【0013】
 物質エネルギ変換装置1が変換するエネルギEは、以下の式(1)によって示される。
但し、Mは水素の質量である。Cは光速である。
 【数1】
    E=MC^2・・・(1)

 【0014】
 よって、本実施形態の物質エネルギ変換装置1によれば、反物質を生成することなく、
物質を直接的にエネルギに変換することができる。
【符号の説明】
 【0015】
1 物質エネルギ変換装置
11 水素入力部
12 希土類金属結晶
13 磁場形成部
14 中性子遮蔽部

ブラック所長「「【先行技術文献】」の次には段落番号を入れてないな。」
ワイ 「段落番号を入れていい項目と入れてはならない項目は、ちゃんと区別してます。」

ブラック所長「程よい感じで段落番号が入ってるな。」
ワイ  「はい。1行40文字で書いたとき、およそ3~5行で1つの段落番号が入るようにしたんです。」

ブラック所長「【数1】と数式の次にも入ってるな。」
ワイ 「ワードの数式・画像・表をちゃんと判定してます。」
    (・・・テキストで数式を書かれたらアウトやけどな。)

ブラック所長「実施形態の『図1は、・・。』、『図2は、・・。』の直前に段落番号が入ってるな。」
ワイ 「はい、『図(番号)は、・・・。』を検知して、その直前に段落番号を入れるようにしてます。」

ブラック所長「これなら段落番号は大丈夫や。さて、次は中身の問題やが・・・。」
ワイ ギクッ・・・  

(次回に続く・・・かもしれない。)

※ワイ記法(C)無職やめ太郎さん

コードとプロジェクトの説明

WORD のVSTOアドインについて書かれている文献は少ないので、この記事が少しでも開発の助けになればとおもいます。試しに、amazon で "VSTO" と入力して書籍を検索してみてください。"VBA"の書籍と比べて、その少なさに愕然とします。

 なお、VSTO(Visual Studio Tools for Office)とは、オフィスソリューションの一つであり、OfficeアプリケーションのアドインをC#やVB.netで記述できます。VBA(Visual Basic for Application)と比べると、Visual Studioが必須である点が、やや面倒くさいです。また、VSTOのアドインは、Officeのバージョンによっては互換性が問題になる場合があります。しかし、Officeのアドインを作成して公開し、かつ、そのソースコードを秘匿したい場合には、VSTOを選択肢とすることが必要とおもいます。

参考文献

オフィスソリューション開発の概要 (VSTO)
https://docs.microsoft.com/ja-jp/visualstudio/vsto/office-solutions-development-overview-vsto?view=vs-2019

GUIの説明

GUIは、WORDのリボンインタフェースとして実装いたしました。

リボン全体の属性は以下です。
2020-06-28.png

ボタンのグループの名前には「段落生成」と名付けました。
2020-06-28 (4).png

 段落付与ボタンは、縦横64ピクセルのアイコンを付けました。“【0001】”の段落番号を黒文字で入れています。なお、自前でアイコンを生成する代わりに、Officeに組み込まれているアイコンを使うこともできます。
 この段落付与ボタンをクリックしたとき、Ribbon1.cs の AddDanraku_Click関数が呼び出されます。
 これにより、段落が付与されたのち、段落番号が追番に振り直されます。

2020-06-28 (1).png

 段落の振直ボタンは、縦横64ピクセルのアイコンを付けました。“【0001】”と”【0002】”の段落番号がオーバーラップするよう青文字で入れています。振直ボタンをクリックしたとき、Ribbon1.cs の RenumDanraku_Click関数が呼び出されます。
 これにより、段落番号が追番に振り直されます。

2020-06-28 (2).png

 段落の削除ボタンは、縦横64ピクセルのアイコンを付けました。“【0001】”と取消線とを赤で入れています。削除ボタンをクリックしたとき、Ribbon1.cs の DelDanraku_Click関数が呼び出されます。
 これにより、段落が削除されます。

2020-06-28 (3).png
 

コード説明

段落の削除

 一番簡単な段落削除から説明します。

 最初に、WORDの校閲の変更履歴の記録がオンになっているか否かを判定します。変更履歴の記録がオンになっているときにアドインが文書を書き換えると、その書き換えが変更履歴として記録されてしまいます。よっで、これを防ぐために、変更履歴の記録がオンになっているときには、処理を実行せずに終了します。
 doc.TrackRevisionsがtrue のとき、変更履歴の記録がオンです。このときには、メッセージボックスを表示したのち、処理を終了します。

 次に、文書の垂直タブを改行に変換します。WORDのパラグラフは、文章の改行で区切られています。
 WORDの文章が垂直タブで区切られていると、そこは文章の切れ目であり、かつ、文章が改行されているかのように見えますが、垂直タブの前後は同一のパラグラフです。
 よって、本アドインでは文章の垂直タブを改行に変換して、文章の切れ目とパラグラフの区切りを一致させています。

 そして、プログレスパーの表示用ダイアログと、プログレスバーのワーカースレッドを起動します。なお ProgressDialog クラスは、プログレスバーの表示用ダイアログが終了すると、その結果に応じて、キャンセル、エラー、成功の何れかに分岐します。

DelDanraku_Click(段落削除ボタンのクリック)
        private void DelDanraku_Click(object sender, RibbonControlEventArgs e)
        {
            Document doc = danrakuG01.Globals.ThisAddIn.Application.ActiveDocument;
            var myPatDoc = new patDoc();

            if (doc.TrackRevisions == true)
            {
                System.Windows.Forms.MessageBox.Show("変更履歴の記録をオフしてくたさい");
                return;
            }
            myPatDoc.垂直タブを改行に(doc);
            ProgressDialog pd = new ProgressDialog("段落の削除",
                new DoWorkEventHandler(ProgressDialog_Del_DoWork),
                16);
            //進行状況ダイアログを表示する
            DialogResult result = pd.ShowDialog();
            //結果を取得する
            if (result == DialogResult.Cancel)
            {
                MessageBox.Show("キャンセルされました");
            }
            else if (result == DialogResult.Abort)
            {
                //エラー情報を取得する
                Exception ex = pd.Error;
                MessageBox.Show("エラー: " + ex.Message);
            }
            else if (result == DialogResult.OK)
            {
                //結果を取得する
                int stopTime = (int)pd.Result;
                //MessageBox.Show("成功しました: " + stopTime.ToString());
            }
            //後始末
            pd.Dispose();
        }

垂直タブを改行に

 垂直タブを改行に変換するコードは、マイクロソフトのVSTOのサンプルコードとほぼ同一です。

 プログラムによって文書内のテキストを検索および置換する
https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-programmatically-search-for-and-replace-text-in-documents?view=vs-2019

プログラムによって検索後に選択を復元する
https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-programmatically-restore-selections-after-searches?view=vs-2019

垂直タブを改行に
        // プログラムによって文書内のテキストを検索および置換する
        // https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-programmatically-search-for-and-replace-text-in-documents?view=vs-2019
        // プログラムによって検索後に選択を復元する
        //https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-programmatically-restore-selections-after-searches?view=vs-2019
        public void 垂直タブを改行に(Document doc)
        {
            object missing = null;

            Range rangeSave = doc.Application.Selection.Range;
            doc.Application.Selection.WholeStory();
            Find findObject = doc.Application.Selection.Find;
            findObject.ClearFormatting();
            findObject.Text = "^l";
            findObject.Replacement.ClearFormatting();
            findObject.Replacement.Text = "^p";
            findObject.MatchFuzzy = false;
            findObject.Forward = true;
            object findtext = "^l";
            object replacetext = "^p";
            object replaceAll = WdReplace.wdReplaceAll;
            findObject.Execute(ref findtext, ref missing, ref missing, ref missing, ref missing,
                ref missing, ref missing, ref missing, ref missing, ref replacetext,
                ref replaceAll, ref missing, ref missing, ref missing, ref missing);
            rangeSave.Select();
         }

 段落削除に係るプログレスバーのワーカースレッドです。
 このスレッド内では、WORDの文書のうち、書類名の範囲が“明細書”であるものを検索します。
 すなわち、“【書類名】明細書”が記載されたパラグラフから、次の“【書類名】”が記載されたパラグラフまたは文書の末尾までの範囲を取得します。
 そして、段落番号が記載されているパラグラフを順次検索すると、そのパラグラフを削除します。

ProgressDialog_Del_DoWork(段落削除のプログレスバーのワーカースレッド)
        // 段落の削除
        private void ProgressDialog_Del_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;

            //パラメータを取得する
            int stopTime = (int)e.Argument;

            Document doc = danrakuG01.Globals.ThisAddIn.Application.ActiveDocument;
            var myPatDoc = new patDoc();
            Range rng = myPatDoc.書類名の範囲("明細書", doc);
            if (rng == null)
            {
                System.Windows.Forms.MessageBox.Show("明細書が記載されていません。", "警告");
                e.Result = 0;
                return;
            }
            long endpos = rng.End;
            int counter = 0;
            int i = 0;
            rng.Find.MatchWildcards = true;

            int lastTick = Environment.TickCount;
            int currTick;
            bw.ReportProgress(i, i.ToString() + "% 終了しました");

            while (rng.Find.Execute("【[0-9]@】"))
            {
                counter++;

                rng.Text = "";
                rng.SetRange(rng.End, rng.End);
                if (myPatDoc.パラグラフが空白か判定(rng.Paragraphs[1]))
                {
                    rng.Paragraphs[1].Range.Delete();
                }
                //キャンセルされたか調べる
                if (bw.CancellationPending)
                {
                    //キャンセルされたとき
                    e.Cancel = true;
                    return;
                }
                currTick = Environment.TickCount;
                if( currTick - lastTick > 1000 )
                {
                    //指定された時間待機する
                    //System.Threading.Thread.Sleep(stopTime);

                    //ProgressChangedイベントハンドラを呼び出し、
                    //コントロールの表示を変更する
                    i = (int)(rng.End * 100 / endpos);
                    bw.ReportProgress(i, i.ToString() + "% 終了しました");
                    lastTick = currTick;
                }
            }
            i = 100;
            bw.ReportProgress(i, i.ToString() + "% 終了しました");
            System.Threading.Thread.Sleep(500);
            //結果を設定する
            e.Result = counter;
        }

 プログレスダイアログは、ProgressDialog.cs に記載された ProgressDialogクラスによって表示されます。このクラスは、BackgroundWorkerクラスを使用しており、以下のサンプルコードを参考にしています。

参考文献:
BackgroundWorkerクラスを使用して進行状況ダイアログを作成する
https://dobon.net/vb/dotnet/programing/progressdialogbw.html

 “書類名の範囲”メソッドは、docname で記載された名前に一致する書類名の範囲を返すものです。
 こういう風に、メソッド名を日本語で書くと嫌がる方がおられますが、自分としては日本人ならば可読性が上がるからよいとおもって書いています。

書類名の範囲
        public Range 書類名の範囲(
            string docname,
            Document doc,
            long spos = 0,
            long epos = -1
            )
        {
            Range rng;
            if (epos == -1)
            {
                epos = doc.Content.End;
            }
            rng = null;
            while (書類名の範囲を選択(doc, ref rng, spos, epos) == true)
            {
                if (rng == null)
                {
                    break;
                }
                string para1 = rng.Paragraphs[1].Range.Text;

                if (para1.IndexOf(docname) > 0)
                {
                    領域始端のトリム(ref rng);
                    領域終端のトリム(ref rng);
                    領域拡張(ref rng);
                    break;
                }
                spos = rng.End;
            }
            return rng;
        }

 書類名の範囲を選択メソッドは、“【書類名】・・・・” から次の “【書類名】・・・・” までを選択するものです。
 領域始端のトリムメソッドとは、始端から空白・改行などを取り去るものです。これにより領域の始端を調整します。
 領域終端のトリムメソッドとは、終端から空白・改行などを取り去るものです。これにより領域の終端を調整します。
 領域拡張メソッドは、トリムした始端・終端を、WORD文書のパラグラフ単位で拡張します。

段落の生成

段落の生成を説明します。

 最初に、WORDの校閲の変更履歴の記録がオンになっているか否かを判定します。変更履歴の記録がオンになっているときにアドインが文書を書き換えると、その書き換えが変更履歴として記録されてしまいます。よっで、これを防ぐために、変更履歴の記録がオンになっているときには、処理を実行せずに終了します。
 doc.TrackRevisionsがtrue のとき、変更履歴の記録がオンです。このときには、メッセージボックスを表示したのち、処理を終了します。
 次に、文書の垂直タブを改行に変換します。

 そして、段落生成のプログレスパーの表示用ダイアログと、段落生成のプログレスバーのワーカースレッドを起動します。
 プログレスバーの表示用ダイアログが終了すると、その結果に応じて、キャンセル、エラー、成功の何れかに分岐します。

 次に、段落番号の振り直しのプログレスパーの表示用ダイアログと、段落番号の振り直しのプログレスバーのワーカースレッドを起動します。
 これは、既に段落番号が付与されているとき、新たに段落番号を追加すると、段落番号の振り直しが必要になるためです。
 プログレスバーの表示用ダイアログが終了すると、その結果に応じて、キャンセル、エラー、成功の何れかに分岐します。

AddDanraku_Click(段落生成ボタンのクリック)
        private void AddDanraku_Click(object sender, RibbonControlEventArgs e)
        {
            Document doc = danrakuG01.Globals.ThisAddIn.Application.ActiveDocument;

            var myPatDoc = new patDoc();
            //myPatDoc.G_段落番号付与(doc);
            if (doc.TrackRevisions == true)
            {
                System.Windows.Forms.MessageBox.Show("変更履歴の記録をオフしてくたさい");
                return;
            }
            myPatDoc.垂直タブを改行に(doc);
            //ProgressDialogオブジェクトを作成する
            ProgressDialog pd = new ProgressDialog("段落番号の付与",
                new DoWorkEventHandler(ProgressDialog_Add_DoWork),
                16);
            //進行状況ダイアログを表示する
            DialogResult result = pd.ShowDialog();
            //結果を取得する
            if (result == DialogResult.Cancel)
            {
                MessageBox.Show("キャンセルされました");
                //後始末
                pd.Dispose();
                return;
            }
            else if (result == DialogResult.Abort)
            {
                //エラー情報を取得する
                Exception ex = pd.Error;
                MessageBox.Show("エラー: " + ex.Message);
                //後始末
                pd.Dispose();
                return;
            }
            else if (result == DialogResult.OK)
            {
                //結果を取得する
                int stopTime = (int)pd.Result;
                //MessageBox.Show("成功しました: " + stopTime.ToString());
            }
            //後始末
            pd.Dispose();

            //ProgressDialogオブジェクトを作成する
            pd = new ProgressDialog("段落番号の振り直し",
                new DoWorkEventHandler(ProgressDialog_Renum_DoWork),
                16);
            //進行状況ダイアログを表示する
            result = pd.ShowDialog();
            //結果を取得する
            if (result == DialogResult.Cancel)
            {
                MessageBox.Show("キャンセルされました");
            }
            else if (result == DialogResult.Abort)
            {
                //エラー情報を取得する
                Exception ex = pd.Error;
                MessageBox.Show("エラー: " + ex.Message);
            }
            else if (result == DialogResult.OK)
            {
                //結果を取得する
                int stopTime = (int)pd.Result;
                //MessageBox.Show("成功しました: " + stopTime.ToString());
            }
            //後始末
            pd.Dispose();
        }

 段落生成に係るプログレスバーのワーカースレッドでは、WORDの文書のうち、書類名の範囲が“明細書”であるものを検索します。すなわち、“【書類名】明細書”が記載されたパラグラフから、次の“【書類名】”が記載されたパラグラフの直前のパラグラフまたは文書の末尾までの範囲を取得します。
 そして、範囲内のパラグラフを順次検索すると、そのパラグラフの直後または直前が段落番号の生成対象であるか否かを判定します。段落番号の生成対象であると判定したならば、そのパラグラフの直後または直前に、段落番号が記載されたパラグラフを挿入します。

 “項目の判定”メソッドとは、現在のパラグラフに隅付括弧【】があるか否かを判定するものです。
 そして、“パラグラフが段落番号付与対象項目か判定”メソッドがtrueならば、この直後に段落番号のパラグラフを挿入します。そして、現在のパラグラフが”【符号の説明】”ならば、処理を終了します。
 
次に、”パラグラフが数化表項目か判定”メソッドにより、現在のパラグラフが数式、化学式、表の項目であるか否かを判定します。現在のパラグラフが数式、化学式、表の項目ならば、直前に文章が記載されているか否かを判定し、その直前に段落番号が記載されたパラグラフを挿入します。

 次に、”パラグラフが段落番号付与非対象項目か判定”メソッドがtrueであり、かつ“パラグラフが不正な段落番号か判定”メソッドがtrueならば、不正な段落番号が付与されたパラグラフとして、このパラグラフを削除します。

ProgressDialog_Add_DoWork(段落生成のプログレスバーのワーカースレッドの要部)
            int counter = 0;
            int i = 0;
            int lastTick = Environment.TickCount;
            int currTick;
            bw.ReportProgress(i, i.ToString() + "% 終了しました");

            foreach (Paragraph paraCurr in rng.Paragraphs)
            {
                string tmpdebug = paraCurr.Range.Text;
                if (myPatDoc.項目の判定(paraCurr))
                {
                    if (myPatDoc.パラグラフが段落番号付与対象項目か判定(paraCurr))
                    {
                        Range rng2 = myPatDoc.直後への段落番号の挿入(paraCurr);
                        if (paraCurr.Range.Text.IndexOf("【符号の説明】") >= 0)
                        {
                            break;
                        }
                    }
                    else if (myPatDoc.パラグラフが数化表項目か判定(paraCurr))
                    {
                        Paragraph paraPrev = myPatDoc.テキスト記載パラグラフ取得(paraCurr, -1);
                        if (myPatDoc.パラグラフが数化表項目か判定(paraPrev))
                        {
                            myPatDoc.直前への段落番号挿入(paraCurr);
                        }
                        paraPrev = null;
                    }
                    else if (myPatDoc.パラグラフが段落番号付与非対象項目か判定(paraCurr))
                    {
                        if (myPatDoc.パラグラフが不正な段落番号か判定(paraCurr))
                        {
                            paraCurr.Range.Delete();
                        }
                    }
                    else
                    {
                        Range rng2 = myPatDoc.直後への段落番号の挿入(paraCurr);
                    }
                }
                else
                {
                    myPatDoc.段落番号付与判定(paraCurr);
                }
                counter++;
                //キャンセルされたか調べる
                if (bw.CancellationPending)
                {
                    //キャンセルされたとき
                    e.Cancel = true;
                    return;
                }
                //指定された時間待機する
                //System.Threading.Thread.Sleep(stopTime);

                currTick = Environment.TickCount;
                if (currTick - lastTick > 1000)
                {
                    //ProgressChangedイベントハンドラを呼び出し、
                    //コントロールの表示を変更する
                    i = (int)(paraCurr.Range.End * 100 / rng.End);
                    bw.ReportProgress(i, i.ToString() + "% 終了しました");
                    lastTick = currTick;
                }
            }

段落の振り直し

段落の振り直しは、追番として記載されていない段落番号を、追番に振り直すものです。具体的には、WORDの検索機能で、段落番号のパターンに当て嵌まる文字列を順次検索して、これを追番の段落番号の文字列で書き換えています。

ProgressDialog_Renum_DoWork(段落の振り直しのプログレスバーのワーカースレッド)
            rng.Find.MatchWildcards = true;
            while (rng.Find.Execute("【[0-9]@】"))
            {
                counter++;
                rng.Text = myPatDoc.段落番号文字列の生成(counter);
                rng.SetRange(rng.End, rng.End);

                //キャンセルされたか調べる
                if (bw.CancellationPending)
                {
                    //キャンセルされたとき
                    e.Cancel = true;
                    return;
                }
                //指定された時間待機する
                //System.Threading.Thread.Sleep(16);

                currTick = Environment.TickCount;
                if (currTick - lastTick > 1000)
                {
                    //ProgressChangedイベントハンドラを呼び出し、
                    //コントロールの表示を変更する
                    i = (int)(rng.End * 100 / endpos);
                    bw.ReportProgress(i, i.ToString() + "% 終了しました");
                    lastTick = currTick;
                }
            }

インストーラ

 最近のVSTOで特筆すべきは、インストーラの作成の簡単さだとおもいます。VisualStudio2019のビルドメニューから「・・・の発行」をクリックするだけですから。
 なお、CDやUSBメモリからインストールする従来のインストーラに限られず、FTPサーバにアップロードすることも可能だそうですが、未検証です。

 出来上がったセットアップファイルをインターネットに置くと、ダウンロードの際にゾーン識別子が付きます。vstoのsetup.exeは、*.vstoと *.dll.manifest にゾーン識別子が着いているとエラー終了します。よって、install.bat によりゾーン識別子を削除して、setup.exeを起動させます。

install.bat
echo .>danrakuG01.vsto:Zone.identifier
echo .>".\Application Files\danrakuG01_1_0_0_4\danrakuG01.dll.manifest:Zone.identifier"
echo .>".\Application Files\danrakuG01_1_0_0_4\danrakuG01.vsto:Zone.identifier"
setup.exe

参考文献:
チュートリアル: Word 用の最初の VSTO アドインの作成します。
https://docs.microsoft.com/ja-jp/visualstudio/vsto/walkthrough-creating-your-first-vsto-add-in-for-word?view=vs-2019
【ワレコの講座】VS2017でEXCEL VSTOアドイン作成 – リボン編(1/10)
https://www.wareko.jp/blog/creating-excel-vsto-addin-with-vs2017-ribbon-edition-1of10
【ワレコの講座】EXCEL VSTOアドインで選択セル情報取得(2/10)
https://www.wareko.jp/blog/acquire-selected-cell-information-with-excel-vsto-add-in-2of10
【ワレコの講座】EXCEL VSTOアドインを他のPCに配布する(3/10)
https://www.wareko.jp/blog/publish-excel-vsto-add-in-to-another-pc-3of10
ダウンロードしたファイルの「ブロック解除」をコマンドで
https://qiita.com/gentaro/items/3beb65a8f2f89089a042

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CUIによる C# Managed DLL の作成・ Global Assembly Cache へのインストール・利用手順

概要

Visual Studio が利用できない環境で C# Managed DLL を Global Assembly Cache (以下「GAC」)にインストールし、
実行ファイルから参照したい機会があったので、手順をまとめておきます。

今時そんな機会はほぼ無いと思われますが、もしかしたらあるところにはあるかも?多分。


手順

1. C# Managed DLL を作成

概要に記載の通り「 Visual Studio が利用できない環境」想定のため、手動で作成します。
本記事の主目的では無いため、詳細は省略。
拾ってきた C# Managed DLL を GAC にインストール・利用したい場合は、手順ごと省略可能です。

SampleDll.cs
[assembly: System.Reflection.AssemblyVersion("1.0.0.0")]

public static class SampleDll {
  public static void WriteLine() {
    System.Console.WriteLine("WriteLine from SampleDll.");
  }
}
C# Managed DLL ビルドコマンド
sn -k KeyFile.snk
csc /o+ /t:library  /keyfile:KeyFile.snk SampleDll.cs

2. C# Managed DLL の GAC へのインストール

Gacutil.exe を使用することで C# Managed DLL の GAC へのインストールが可能です。

Microsoft Docs には「このツールは、Visual Studio と共に自動的にインストールされます。」と記載があり Visual Studio が必要そうですが、
(恐らく)標準でProgram Filesの「Microsoft SDKs」に入っているため
Visual Studio は必要ありません。
(手元の環境では「C:/Program Files (x86)/Microsoft SDKs/Windows/v10.0A/bin/NETFX 4.8 Tools/gacutil.exe」にありました。)

インストールコマンド
gacutil /i SampleDll.dll

3. GAC の利用

ビルド時の利用

GAC へインストールした C# Managed DLL は .NET Framework
のバージョンにより
「%windir%\Assembly」または「%windir%\Microsoft.NET\Assembly」に格納されています。
しかし、そのパスを csc.exe で簡単に参照することはできないため、
本記事では「実行時の参照」のみ「 GAC の利用」として扱い、ビルド時の利用方法については扱いません。
CUIによるビルド時に GAC を利用したい場合、素直に MSBuild を使用しましょう。
(Microsoft Docs に記載されている通り MSBuildVisual Studio に依存しません。)
( GAC にインストールされたファイルからリンク対象 DLL を検索するスクリプトを作成し、 csc.exe をラップするのも一つの方法です。)

SampleExe.cs
static class SampleExe {
  static void Main () {
    SampleDll.WriteLine();
  }
}
ビルドコマンド
csc /o+ SampleExe.cs /r:SampleDll.dll
実行時の利用

Microsoft Docs の ランタイムがアセンブリを検索する方法に記載されている通り、
アプリケーション構成ファイルの作成・編集等、実行時の参照先としての一切の指定は不要で、
その環境におけるあらゆる実行ファイルで(必要に応じて)自動で参照されます。

例として、「3. GAC の利用 > ビルド時の利用」で作成した実行ファイル「SampleExe.exe」は、
ビルド時に参照先と指定した同一ディレクトリにある「SampleDll.dll」を削除しても問題なく動作します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でランダムなint配列を生成する

Enumerable.Repeat(new Random(), 1000).Select(r => r.Next(200)).ToArray();

これで0以上200未満のintが1000個ある配列を作れます。

補足

Selectでは最初に用意したRandom型のインスタンスが毎回使われるのでランダムに作ってくれます。

参照型でEnumerable.Repeatを使うと同じインスタンスを返すことを利用しています。

この仕様は多次元配列をLINQで生成しようと、

Enumerable.Repeat(new int[10], 10).ToArray()

と書いてしまうようなバグの原因でもあるので要注意です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語別Stack処理速度比較

1. はじめに

Go言語にはStackが実装されていませんが、

Stackの処理自体は、どの言語でも、
配列や線形リストを使って自作で実装することができます。

Go言語向けに自作で作ったついでに、
Stackが実装されている他言語との処理速度(処理時間)を比較してみました。

実験で使用した言語は、

  • C#(.NET Core3.0)
  • go1.14.4
  • java 13.0.2
  • Python 3.8.3
  • C言語(MinGW.org GCC-8.2.0-3)(2020/07/12追記)

の5言語です。

各言語のソースは、githubでも公開しています。
https://github.com/NobuyukiInoue/StackSpeedTest

2. 言語別のメソッド比較

Stackといっても、言語によって、各種操作がメソッドで提供されていたり、
プロパティでチェックしなければならないなど、細かな処理の実装内容が異なります。

今回の実験で使用した各言語でのクラス/インタフェース/機能を一覧にすると、以下のようになります。

処理内容 C#(.NET Core3.x)
(Stackクラス)
Java 8
(クラスStack)
Java 8
(クラスArrayDeque)
Java 8
(クラスDeque)
Python3.8.3
(リスト)
Stackへの書き込み Pushメソッド pushメソッド pushメソッド addFirstメソッド append関数
Stackからの取り出し Popメソッド popメソッド popメソッド removeFirstメソッド pop関数
値の検索 Containsメソッド
(戻り値はbool型)
searchメソッド
(戻り値はindex番号)
Containsメソッド
(戻り値はbool型)
Containsメソッド
(戻り値はbool型)
index関数
Stackが空かどうか調べる Countプロパティを利用 emptyメソッド isEmptyメソッド isEmptyメソッド len関数を利用

3. 検証環境と実験結果(2020/07/11追記)

項目
OS MS-Windows 10
CPU Intel Core-i7 8559u
メモリ 16GB(LPDDR3/2133MHz)

メモリ容量を追記しました。(2020/07/11)

処理の内容は、

  • 1~1億までの数値のpush()
  • 最も深い位置にある値1の検索
  • 1億~1までのPop()

です。

1億個の数値をint64(8byte)で割り当てると、762MByteの容量が必要になります。
int32(4byte)なら、半分の381MByteですね。

生成するスタックの容量が小さいうちは、それほど処理時間に差はありませんが、
1億個のスタック生成となると、選択した方法によってはメモリ消費が4GBを超えます。

搭載されているメモリが8GB以下だと、言語によっては(デフォルト設定では)、
ヒープメモリが足りないといったエラーがでます。

各言語での処理時間は以下の通りでした。

消費したメモリについても、Windows10のリソースモニター(コミット値)による表示の目視ではありますが、参考程度に掲載しています。(2020/07/11追記)

C# NET Core 3.0.100

Stackクラスを使用
https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.stack-1?view=netcore-3.1

メモリ消費は0.39GBでした。

times Execution time
1st 977 [ms]
2nd 1003 [ms]
3rd 1004 [ms]

メモリ消費からみて、内部ではint32で処理が行われているようです。

他言語と比べてかなりメモリ消費が少なく、
処理データの少なさが処理時間の短さに影響しているようです。

今回の比較では最速の数値です。

go1.14.4 windows/amd64(配列版)

メモリ消費は2.36GBでした。

times Execution time
1st 2089 [ms]
2nd 1853 [ms]
3rd 1737 [ms]

配列の再割り当てを繰り返しても、それほど遅くはないようです。
Javaより速い結果となりました。

go1.14.4 windows/amd64(線形リスト版)

メモリ消費は1.49GBでした。

times Execution time
1st 5617 [ms]
2nd 5400 [ms]
3rd 5569 [ms]

処理時間は配列版よりも遅くなりました。

線形リストでは次ノードへのポインタが必要なので、
直線的なメモリ配置の配列より不要なデータが増えてしまいますが、
配列版より消費が少ない結果となりました。

Go言語では、配列の再割り当てを行っても、
プログラムの終了までメモリの解放が行われていないのかもしれません。
(あくまで推測で、だれか検証してほしい)

java 13.0.2(クラスStack版)

クラスStackを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Stack.html

メモリ消費は4.21GBでした。

times Execution time
1st 6743 [ms]
2nd 6690 [ms]
3rd 6733 [ms]

java 13.0.2(クラスArrayDeque版)

クラスArrayDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/ArrayDeque.html

メモリ消費は3.96GBでした。

times Execution time
1st 4397 [ms]
2nd 4612 [ms]
3rd 4477 [ms]

クラスStackよりも処理時間が短くなりました。

java 13.0.2(インタフェースDeque版)

インタフェースDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Deque.html

メモリ消費は4.31GBでした。

times Execution time
1st 21416 [ms]
2nd 20187 [ms]
2nd 20341 [ms]

クラスStackよりも処理時間が増えてしまいました。

Python 3.8.3

リストを使用

メモリ消費は4.42GBでした。

times Execution time
1st 16957 [ms]
2nd 16561 [ms]
3rd 17558 [ms]

他言語では、一度確保したメモリは消費したままでしたが、
Python3では、1億個のPush直後がメモリ消費の最大値で、
Pop処理が進むにしたがって消費メモリが減っていく様子が目視できます。

C言語(64K個単位のセグメント(配列)でStackを実装)(2020/07/12追記)

メモリ消費は0.38GBでした。

さすがにC言語は速いですね。
といっても、C#に負けてしまいました。

確保したブロックは破棄せずにそのまま保持するようにすれば、もっと速くなるかもしれません。

times Execution time
1st 1591 [ms]
2nd 1554 [ms]
3rd 1558 [ms]

C言語(線形リストでStackを実装)(2020/07/12追記)

メモリ消費は1.55GBでした。

Push処理、Pop処理、それぞれ5秒程度ずつといったところでしょうか。
メモリの確保・解放の処理回数が多いので、それだけ処理時間が遅くなっています。

times Execution time
1st 10981 [ms]
2nd 11066 [ms]
3rd 10799 [ms]

関連(2020/07/11追記)

LeetCodeのProblemの中にも、Stackを自分で実装する問題があります。
いろんな言語での回答例が投稿されているので、処理時間を比較するのも面白いと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xamarin.Forms ガワネイティブアプリで、C# から JavaScript へのリクエスト~レスポンスを Task<T> にする

メモ書きなので雑。

シナリオ

  • Xamarin.Forms + WebView(HTML, JavaScript な SPA) のガワネイティブアプリである
  • Android アプリの BACK キーの有効無効を、WebView 内のページ状態によって切り替えたい
    • データ編集中(未保存)なので Back キーで history.back やアプリ終了されたら困る、とか

流れ

1. [Forms側] Action<TaskCompletionSource<bool>> OnRequestIsEnableBackKey { get; set; } を生やす

2. [Forms側] WebViewEx(WebViewを拡張したもの)に Task<bool> IsEnableBackKeyAsync() も生やす。実装は次のように。

public Task<bool> IsEnableBackKeyAsync()
{
    var comp = new TaskCompletionSource<bool>();

    if (OnRequestIsEnableBackKey != null)
    {
        OnRequestIsEnableBackKey.Invoke(comp);
    }
    else
    {
        comp.SetResult(false);
    }

    return comp.Task;
}

3. [Android側] WebViewRenderer を拡張した MyWebViewRenderer を作る。

4. Xamarin.Forms の WebView で JavaScript 連携を行う(with iOS/Android共通化) - Qiita を参考に JavaScriptHandler も作る。コンストラクタで Control に加え e.NewElement as WebViewEx も渡す。引数の名前は WebViewEx outer とする。AddJavascriptInterface の第2引数は GawaApp とでもしておく。

5.JavaScriptHandler のコンストラクタで OnRequestIsEnableBackKey を受信するコードを書く。

private TaskCompletionSource<bool> isEnableBackKeyComp = null;

public JavaScriptHandler(Android.Webkit.WebView webView, WebViewEx outer)
{
    outer.OnRequestIsEnableBackKey = comp => 
    {
        isEnableBackKeyComp = comp;

        // メインスレッドから呼ばないとエラー
        webView.Post(() =>
        {
            webView.EvaluateJavascript("window.requestIsEnableBackKey()", null);
            // webView.LoadUrl("javascript:window.requestIsEnableBackKey();"); ←これでもOKっぽい
        });
    };
}

ここまでの処理で、Forms 側で await webViewEx.IsEnableBackKeyAsync() が呼び出されたら、JavaScript の window.requestIsEnableBackKey() 関数が呼び出される。

6.JavaScriptHandler 内に onResultCanExitApp を生やす。JavaScript側 から結果が通知されるメソッドである。次のように。

[Export]
[Android.Webkit.JavascriptInterface]
public void onResultIsEnableBackKey(bool value)
{
    isEnableBackKeyComp?.SetResult(value);
    isEnableBackKeyComp = null; // one shot
}

JavaScript 側で GawaApp.onResultIsEnableBackKey(true or false) を呼び出すと、このメソッドがコールバックされる。
TaskCompletionSource である isEnableBackKeyComp に値を設定すれば、Forms 側の await webViewEx.IsEnableBackKeyAsync() の結果が返される。

7.JavaScript 側はきっとこんな感じ

window.requestIsEnableBackKey = () => {

  // 何かの処理

  GawaApp.onResultIsEnableBackKey(true or false);
}

途中まで実装したけど、面倒になって(JavaScript→C# に単方向の方がシンプルでいいやと思って)やめちゃったので、アイデアだけ残しておきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#とXamarinを使用したプログラム開発

「Xamarin」について書きたいと思います。

1.Xamarinとは?

 XamarinはXamarin社(現Microsoft社に買収)が開発したiOS、Mac、Androidで動作するアプリを開発ができるプラットフォームです。
 基本的にはC#で開発します。
 以前はiOSアプリを開発するためにMac端末でXcode上で開発する必要がありましたが、
 Xamarinは※Monoをベースに実装されているため、VisualStudio上での開発が可能になりました!(コンパイルにはMacが必要)
 ※Mono:.NET Frameworkの要素をLinuxやMacでも利用できるマルチプラットフォーム基盤のイメージ
 何と言っても1つの言語で複数のプラットフォームに対応したアプリを作成できることが 最大の利点です。
 iOSとAndroidに言語を分ける必要がないため、コードを共有できます。
 コードの共有については、UI画面と業務ロジック(BR)の2つに分けられています。
 1つ目はUIはiOS、Android固有のコードで実装し、業務BRのコードを共有する方法。
 これがいわゆる言われている、Xamarin.iOSやXamarin.Androidです。まとめてXamarin Nativeとも呼ばれます。
 2つ目はUI画面と業務BRの両方を共有する方法。これがXamarin.Formsです。

2.Xamarin Native(Xamarin.iOSとXamarin.Android)

 Xamarin.iOSとXamarin.Androidは業務BRはC#で実装し、UI画面は各々の開発コードで実装をします。iOSであればstoryboard、
 AndroidであればxmlでUI画面を作成します。UI画面は固有の開発コードで作成するため、すでに作成しているアプリケーションの
 移植を考えた場合、画面はそのまま移植ができるのが利点です。

f:id:Jaso:20190317194336j:plain

3.Xamarin.Forms

 Xamarin.Formsは、iOS・Android・Windows10デバイス(UWP)を実装することができるフレームワークです。UI画面はデバイス共通で「XAML(Extensible Application Markup Language)」(読んで「ざむる」)を利用します。
 このXAMLはもともとXMLをベースに開発された言語であり、C#・VBなどの.NET言語でも利用することができ、UIの初期レイアウトを定義するために使用されています。

f:id:Jaso:20190317194504j:plain

4.まとめ

 Xamarinと呼ばれるクロスプラットフォーム開発が可能なフレームワークについて記載しました。その中でUIコードをデバイス固有で実装し、業務BRは共有コード(C#)で実装できる既存アプリの移植性に便利な「Xamarin Native」と、UIコードも共有化(XAML)し、業務BR(C#)も共有化可能な「Xamarin.Forms」について記載しました。
 私は、Xamarin.Formsをでどこまでアプリの開発ができるかを勉強中ですが、正直、Xamarin.Fomrsだけでクロスプラットフォームに対応したアプリができるかわかりません。少しずつ実装していきながら自分自身で確かめたいと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む