20190521のC#に関する記事は6件です。

[C++/C#]C#をCOM参照可能にしてC++アプリから呼ぶ

やりたいこと

色々な事情があり、すでにC#で作ってあるライブラリを、C++のアプリから呼びたい。
(なので、タイトルは正確に言うと、C#のdllを、COM参照可能にしたC#dllでラップして、C++アプリから呼ぶが正しい)

やり方候補

こちらのページにあるように、いろいろなやり方があるっぽい。

やり方 特徴
1.作ってあるC#ライブラリをCOM参照可能なC#でラップする ラップC#を使う側のC++のコードが増える
2.C++のアプリをC++/CLIでビルドしなおす ・原則C++アプリの全テスト必要・ランタイム依存になる
3.有志の方が作ったものを使用しC#(DLL)側で関数をエクスポートする C#(DLL)側のソースコードが無い場合利用できない

今回は、1.の方法で実験をした。つまり、下記のような構成とする。

プロジェクト名 内容
DllCsプロジェクト 仮想[すでにC#で作ってあるライブラリ]
DllCsComWrapperプロジェクト 仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー]
ConsoleApplication1プロジェクト 仮想[C#ライブラリを呼びたいC++アプリ]

image.png

コードは、ほぼほぼこちらのを参考にさせていただきました。ありがとうございます。

ToDo

仮想[すでにC#で作ってあるライブラリ] を実装

DllCsプロジェクトに、下記ファイルを追加。
引数を2つ取ってそれを足した値を返すメソッドAddを持っている。

DllCs.cs
namespace DllCs
{
    public class DllCsClass
    {
        public static int Add(int a, int b) => a + b;
    }
}

仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー] を実装

DllCsComWrapperプロジェクトに、下記のファイルを追加。
上のファイルをラップしたメソッドを持っている。

DllCsComWrapper.cs
using DllCs;
using System;
using System.Runtime.InteropServices;

namespace DllCsComWrapper
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid("85555B74-E2E0-4493-9869-3CE95F13CB99")]
    public class DllCsComWrapperClass
    {
        public Int32 Add(Int32 param1, Int32 param2)
        {
            int ret = DllCsClass.Add(param1, param2);
            return (Int32)ret;
        }
    }
}

仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー] のプロジェクト設定をする

まず「アセンブリをCOM参照可能にする」にチェックを入れる。
image.png
image.png

次に「COM相互運用機能の登録」にチェックを入れる。
image.png

※「COM相互運用機能の登録」は、後で出てくるregasmによるレジストリ登録をビルド時に自動でやってくれる機能と思われる。そのため、作成中のデバッグ時はチェックあった方が便利だが、これにチェックが入っていると、デバッグ中はCOMの登録が行われるため動いていたが、本番機ではCOM登録が行われないため動かない、となってしまうため注意が必要。

クラスには、下記の属性をつける(とりあえずおまじないとして、動くものをつくる。後で詳細調べること。)
- ComVisible
- ClassInterface
- Guid →VisualStudioのGUID付与ツールを使用する([ツール] > [GUIDの作成]から。)

using DllCs;を追加し、DllCsのdllを参照に追加しておくこと。

仮想[C#ライブラリを呼びたいC++アプリ] を実装する

ConsoleApplication1に下記のファイルを追加。

ConsoleApplication1.cpp
#include "pch.h"
#include <iostream>
#include <Windows.h>//追加

//グローバル変数
IDispatch *pIDisp = NULL;
IUnknown *pIUnk = NULL;

//プロトタイプ宣言
long _Init(void);
long _Finalize(void);
long _Add(long p_Number1, long p_Number2);

int main()
{
    //変数宣言
    int l_Result = 0;

    //初期処理
    _Init();

    //合計処理
    l_Result = _Add(300, 500);

    //後処理
    _Finalize();

    printf("Calc Result : %d", l_Result);

    return l_Result;
}

//***************************************************************************//
//初期化関数
//***************************************************************************//
long _Init(void)
{
    CLSID clsid;

    //COM初期化
    ::CoInitialize(NULL);

    //ProcIDからCLSIDを取得(ネームスペース名.クラス名)
    HRESULT h_result = CLSIDFromProgID(L"DllCsComWrapper.DllCsComWrapperClass", &clsid);
    if (FAILED(h_result))
    {
        return -1;
    }

    //Instanceの生成
    h_result = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pIUnk);
    if (FAILED(h_result))
    {
        return -2;
    }

    //インターフェースの取得(pIDispは共通変数)
    h_result = pIUnk->QueryInterface(IID_IDispatch, (void**)&pIDisp);
    if (FAILED(h_result))
    {
        return -3;
    }

    //正常終了
    return 0;
}

//***************************************************************************//
//修了処理
//***************************************************************************//
long _Finalize(void)
{
    pIDisp->Release();
    pIUnk->Release();
    ::CoUninitialize();
    return 0;
}

//***************************************************************************//
//合計処理呼出処理
//***************************************************************************//
long _Add(long p_Number1, long p_Number2)
{
    //DISPIDの取得(関数名の設定)
    DISPID dispid = 0;
    OLECHAR *Func_Name[] = { SysAllocString (L"Add") };
    HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if (FAILED(h_result))
    {
        return -1;
    }

    //パラメータ作成
    DISPPARAMS params;
    ::memset(&params, 0, sizeof(DISPPARAMS));

    params.cNamedArgs = 0;
    params.rgdispidNamedArgs = NULL;
    params.cArgs = 2; //呼び出す関数の引数の数

    //引数設定(順番に注意…逆になる)
    VARIANTARG* pVarg = new VARIANTARG[params.cArgs];
    pVarg[0].vt = VT_I4;
    pVarg[0].lVal = p_Number2;
    pVarg[1].vt = VT_I4;
    pVarg[1].lVal = p_Number1;
    params.rgvarg = pVarg;

    VARIANT vRet;
    VariantInit(&vRet);

    //呼び出し
    pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &vRet, NULL, NULL);

    delete[] pVarg;
    return vRet.lVal;
}

下記の処理を行っている。

  • 初期化処理
    • CoInitialize(NULL)を呼ぶ
    • ProcIDからCLSIDを取得
    • Instanceの生成
    • インターフェースの取得
  • 終了処理
    • Initで取得したものを解放
      • Instanceを開放
      • インターフェースを解放
      • CoUninitialize()を呼ぶ
  • Initと終了処理の間に、呼びたいC#のCOMラッパーを呼ぶ関数(ここでは合計処理呼出処理)を作る
    • メソッド名からIDを取得(GetIDsOfNames)
    • メソッドに渡すパラメータを作成(DISPPARAMS、VariantInitなど)
    • 呼び出し実施(pIDisp->Invoke)

仮想[C#ライブラリを呼びたいC++アプリ] の注意点

初期化処理について

CLSIDFromProgID(L"DllCsComWrapper.DllCsComWrapperClass", &clsid);の一つ目の引数は、C#のラッパークラスの「<名前空間名>.<クラス名>」にすること。

合計処理呼出処理について

OLECHAR *Func_Name[] = { SysAllocString (L"Add") };で定義する名前は、C#のラッパークラスの中の、呼びたいメソッド名にすること。

型について

戻り値や引数をやり取りするときに、下記のような対応で型を決める必要がある。
こちらの表より。

image.png

regasmでdllを登録

regasmを使用し、下記のコマンドで作成したラッパーDLLを登録する。

regasm /codebase DllCsComWrapper.dll

※regasmのありかは、下記の通り(環境による?)
32bit版:C:\Windows\Microsoft.NET\Framework\v4.0.30319
64bit版:C:\Windows\Microsoft.NET\Framework64\v4.0.30319
dllのプラットフォーム(32or64bit)により、32/64bitのあったものを使用しないと、うまく登録できない。

今回は、ここからregasm.exeをコピーして、実行するConsoleApplication1.exeと同じ階層に置いた。

作ったC++アプリを実行

下記のようなbatを管理者権限で実行し、作成したアプリを実行。
(面倒だったので、ここではregasmも実行時に毎回実行している)

exerun.bat
@echo off
cd %~dp0
regasm /codebase DllCsComWrapper.dll
start /wait ConsoleApplication1.exe
echo exeからの戻り値は %ERRORLEVEL% です
pause

※ここで毎回regasmするなら、上の方でやっていた「COM相互運用機能の登録」のチェックはいらないっぽい。(デバッグするうえではあった方が便利)

実行結果

image.png

残項目

  • ラッパーC#の属性に何があるかとその効果(ComVisibleとか)

コード

https://github.com/tera1707/WPF-/tree/master/025_CallCsDllFromCpp

参考

コードのサンプル
https://knowledge.rinpress.com/index.php/%EF%BC%A3%EF%BC%8B%EF%BC%8B%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%8B%E3%82%89%EF%BC%A3%EF%BC%83%E3%81%AE%EF%BC%A4%EF%BC%AC%EF%BC%AC%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B

C++からC#を呼ぶいろんな方法
https://kagasu.hatenablog.com/entry/2017/12/31/220239#%E2%85%A1-CCLI%E3%82%92%E4%BD%BF%E3%81%86%E6%96%B9%E6%B3%95

コードのサンプル2
https://azulean.me/2009/03/08/c%E3%81%A7com%E3%82%92%E4%BD%9C%E3%82%8B%EF%BC%88%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E5%8B%95%E3%81%8F%E3%81%A8%E3%81%93%E3%82%8D%E3%81%BE%E3%81%A7%EF%BC%89/

C++からC# DLL 超超超入門(3.の方法)
https://qiita.com/Midori_co583826/items/58d56e202f104ebf867a

GUIDの登録
http://tech.nitoyon.com/ja/blog/2008/07/31/c-sharp-com/

あまり参考にならなかったが、コードのサンプル
https://www.84kure.com/blog/2014/07/17/c-c%E3%81%8B%E3%82%89c%E3%81%AEdll%E3%82%92%E5%91%BC%E3%81%B6%E6%96%B9%E6%B3%95/

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

[C++/C#]C#をCOM参照可能にしてC++アプリから呼ぶ1

やりたいこと

色々な事情があり、すでにC#で作ってあるライブラリを、C++のアプリから呼びたい。
(なので、タイトルは正確に言うと、C#のdllを、COM参照可能にしたC#dllでラップして、C++アプリから呼ぶが正しい)

関連項目目次

やり方候補

こちらのページにあるように、いろいろなやり方があるっぽい。

やり方 特徴
1.作ってあるC#ライブラリをCOM参照可能なC#でラップする ラップC#を使う側のC++のコードが増える
2.C++のアプリをC++/CLIでビルドしなおす ・原則C++アプリの全テスト必要・ランタイム依存になる
3.有志の方が作ったものを使用しC#(DLL)側で関数をエクスポートする C#(DLL)側のソースコードが無い場合利用できない

今回は、1.の方法で実験をした。つまり、下記のような構成とする。

プロジェクト名 内容
DllCsプロジェクト 仮想[すでにC#で作ってあるライブラリ]
DllCsComWrapperプロジェクト 仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー]
ConsoleApplication1プロジェクト 仮想[C#ライブラリを呼びたいC++アプリ]

image.png

コードは、ほぼほぼこちらのを参考にさせていただきました。ありがとうございます。

ToDo

仮想[すでにC#で作ってあるライブラリ] を実装

DllCsプロジェクトに、下記ファイルを追加。
引数を2つ取ってそれを足した値を返すメソッドAddを持っている。

DllCs.cs
namespace DllCs
{
    public class DllCsClass
    {
        public static int Add(int a, int b) => a + b;
    }
}

仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー] を実装

DllCsComWrapperプロジェクトに、下記のファイルを追加。
上のファイルをラップしたメソッドを持っている。

DllCsComWrapper.cs
using DllCs;
using System;
using System.Runtime.InteropServices;

namespace DllCsComWrapper
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid("85555B74-E2E0-4493-9869-3CE95F13CB99")]
    public class DllCsComWrapperClass
    {
        public Int32 Add(Int32 param1, Int32 param2)
        {
            int ret = DllCsClass.Add(param1, param2);
            return (Int32)ret;
        }
    }
}

仮想[すでにC#で作ってあるライブラリのC#のCOMラッパー] のプロジェクト設定をする

まず「アセンブリをCOM参照可能にする」にチェックを入れる。
image.png
image.png

次に「COM相互運用機能の登録」にチェックを入れる。
image.png

※「COM相互運用機能の登録」は、後で出てくるregasmによるレジストリ登録をビルド時に自動でやってくれる機能と思われる。そのため、作成中のデバッグ時はチェックあった方が便利だが、これにチェックが入っていると、デバッグ中はCOMの登録が行われるため動いていたが、本番機ではCOM登録が行われないため動かない、となってしまうため注意が必要。

クラスには、下記の属性をつける(とりあえずおまじないとして、動くものをつくる。後で詳細調べること。)
- ComVisible
- ClassInterface
- Guid →VisualStudioのGUID付与ツールを使用する([ツール] > [GUIDの作成]から。)

using DllCs;を追加し、DllCsのdllを参照に追加しておくこと。

仮想[C#ライブラリを呼びたいC++アプリ] を実装する

ConsoleApplication1に下記のファイルを追加。

ConsoleApplication1.cpp
#include "pch.h"
#include <iostream>
#include <Windows.h>//追加

//グローバル変数
IDispatch *pIDisp = NULL;
IUnknown *pIUnk = NULL;

//プロトタイプ宣言
long _Init(void);
long _Finalize(void);
long _Add(long p_Number1, long p_Number2);

int main()
{
    //変数宣言
    int l_Result = 0;

    //初期処理
    _Init();

    //合計処理
    l_Result = _Add(300, 500);

    //後処理
    _Finalize();

    printf("Calc Result : %d", l_Result);

    return l_Result;
}

//***************************************************************************//
//初期化関数
//***************************************************************************//
long _Init(void)
{
    CLSID clsid;

    //COM初期化
    ::CoInitialize(NULL);

    //ProcIDからCLSIDを取得(ネームスペース名.クラス名)
    HRESULT h_result = CLSIDFromProgID(L"DllCsComWrapper.DllCsComWrapperClass", &clsid);
    if (FAILED(h_result))
    {
        return -1;
    }

    //Instanceの生成
    h_result = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pIUnk);
    if (FAILED(h_result))
    {
        return -2;
    }

    //インターフェースの取得(pIDispは共通変数)
    h_result = pIUnk->QueryInterface(IID_IDispatch, (void**)&pIDisp);
    if (FAILED(h_result))
    {
        return -3;
    }

    //正常終了
    return 0;
}

//***************************************************************************//
//修了処理
//***************************************************************************//
long _Finalize(void)
{
    pIDisp->Release();
    pIUnk->Release();
    ::CoUninitialize();
    return 0;
}

//***************************************************************************//
//合計処理呼出処理
//***************************************************************************//
long _Add(long p_Number1, long p_Number2)
{
    //DISPIDの取得(関数名の設定)
    DISPID dispid = 0;
    OLECHAR *Func_Name[] = { SysAllocString (L"Add") };
    HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if (FAILED(h_result))
    {
        return -1;
    }

    //パラメータ作成
    DISPPARAMS params;
    ::memset(&params, 0, sizeof(DISPPARAMS));

    params.cNamedArgs = 0;
    params.rgdispidNamedArgs = NULL;
    params.cArgs = 2; //呼び出す関数の引数の数

    //引数設定(順番に注意…逆になる)
    VARIANTARG* pVarg = new VARIANTARG[params.cArgs];
    pVarg[0].vt = VT_I4;
    pVarg[0].lVal = p_Number2;
    pVarg[1].vt = VT_I4;
    pVarg[1].lVal = p_Number1;
    params.rgvarg = pVarg;

    VARIANT vRet;
    VariantInit(&vRet);

    //呼び出し
    pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &vRet, NULL, NULL);

    delete[] pVarg;
    return vRet.lVal;
}

下記の処理を行っている。

  • 初期化処理
    • CoInitialize(NULL)を呼ぶ
    • ProcIDからCLSIDを取得
    • Instanceの生成
    • インターフェースの取得
  • 終了処理
    • Initで取得したものを解放
      • Instanceを開放
      • インターフェースを解放
      • CoUninitialize()を呼ぶ
  • Initと終了処理の間に、呼びたいC#のCOMラッパーを呼ぶ関数(ここでは合計処理呼出処理)を作る
    • メソッド名からIDを取得(GetIDsOfNames)
    • メソッドに渡すパラメータを作成(DISPPARAMS、VariantInitなど)
    • 呼び出し実施(pIDisp->Invoke)

仮想[C#ライブラリを呼びたいC++アプリ] の注意点

初期化処理について

CLSIDFromProgID(L"DllCsComWrapper.DllCsComWrapperClass", &clsid);の一つ目の引数は、C#のラッパークラスの「<名前空間名>.<クラス名>」にすること。

合計処理呼出処理について

OLECHAR *Func_Name[] = { SysAllocString (L"Add") };で定義する名前は、C#のラッパークラスの中の、呼びたいメソッド名にすること。

型について

戻り値や引数をやり取りするときに、下記のような対応で型を決める必要がある。
こちらの表より。

image.png

regasmでdllを登録

regasmを使用し、下記のコマンドで作成したラッパーDLLを登録する。

regasm /codebase DllCsComWrapper.dll

※regasmのありかは、下記の通り(環境による?)
32bit版:C:\Windows\Microsoft.NET\Framework\v4.0.30319
64bit版:C:\Windows\Microsoft.NET\Framework64\v4.0.30319
dllのプラットフォーム(32or64bit)により、32/64bitのあったものを使用しないと、うまく登録できない。

今回は、ここからregasm.exeをコピーして、実行するConsoleApplication1.exeと同じ階層に置いた。

作ったC++アプリを実行

下記のようなbatを管理者権限で実行し、作成したアプリを実行。
(面倒だったので、ここではregasmも実行時に毎回実行している)

exerun.bat
@echo off
cd %~dp0
regasm /codebase DllCsComWrapper.dll
start /wait ConsoleApplication1.exe
echo exeからの戻り値は %ERRORLEVEL% です
pause

※ここで毎回regasmするなら、上の方でやっていた「COM相互運用機能の登録」のチェックはいらないっぽい。(デバッグするうえではあった方が便利)

実行結果

image.png

追記(190523)

C++からC#ラッパーに文字列を渡したいときは、下記のようにする。

DllCsComWrapper.cs
using DllCs;
using System;
using System.Runtime.InteropServices;

namespace DllCsComWrapper
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [Guid("85555B74-E2E0-4493-9869-3CE95F13CB99")]
    public class DllCsComWrapperClass
    {
        public Int32 Add([MarshalAs(UnmanagedType.BStr)]string str) // ★マーシャリングする!
        {
            Console.WriteLine(str);
            return (Int32)11;
        }
    }
}

受け取る文字列を、マーシャリングすることが必要。
※とりあえず文字列をラッパーに渡せることを見たいため、C#のライブラリを呼ぶ処理は割愛。

ConsoleApplication1.cpp
long _Add(long p_Number1, long p_Number2)
{
    //DISPIDの取得(関数名の設定)
    DISPID dispid = 0;
    OLECHAR *Func_Name[] = { SysAllocString (L"Add") };
    HRESULT h_result = pIDisp->GetIDsOfNames(IID_NULL, Func_Name, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if (FAILED(h_result))
    {
        return -1;
    }

    //パラメータ作成
    DISPPARAMS params;
    ::memset(&params, 0, sizeof(DISPPARAMS));

    params.cNamedArgs = 0;
    params.rgdispidNamedArgs = NULL;
    params.cArgs = 2; //呼び出す関数の引数の数

    //引数設定
    VARIANT var;
    DISPPARAMS dispParams;
    var.vt = VT_BSTR;// ★渡すデータの種別をBSTRにする
    var.bstrVal = SysAllocString(L"mojiretsu");// ★渡す文字列
    dispParams.cArgs = 1;
    dispParams.rgvarg = &var;
    dispParams.cNamedArgs = 0;
    dispParams.rgdispidNamedArgs = NULL;

    VARIANT vRet;
    VariantInit(&vRet);

    //呼び出し
    printf("[OK] Invoke start\r\n");
    h_result = pIDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispParams, &vRet, NULL, NULL);
    if (FAILED(h_result))
    {
        printf("[NG] Invoke failed\r\n");
        return -2;
    }

    return vRet.lVal;
}

残項目

  • ラッパーC#の属性に何があるかとその効果(ComVisibleとか)

コード

https://github.com/tera1707/WPF-/tree/master/025_CallCsDllFromCpp

参考

コードのサンプル
https://knowledge.rinpress.com/index.php/%EF%BC%A3%EF%BC%8B%EF%BC%8B%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%8B%E3%82%89%EF%BC%A3%EF%BC%83%E3%81%AE%EF%BC%A4%EF%BC%AC%EF%BC%AC%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B

C++からC#を呼ぶいろんな方法
https://kagasu.hatenablog.com/entry/2017/12/31/220239#%E2%85%A1-CCLI%E3%82%92%E4%BD%BF%E3%81%86%E6%96%B9%E6%B3%95

コードのサンプル2
https://azulean.me/2009/03/08/c%E3%81%A7com%E3%82%92%E4%BD%9C%E3%82%8B%EF%BC%88%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E5%8B%95%E3%81%8F%E3%81%A8%E3%81%93%E3%82%8D%E3%81%BE%E3%81%A7%EF%BC%89/

C++からC# DLL 超超超入門(3.の方法)
https://qiita.com/Midori_co583826/items/58d56e202f104ebf867a

GUIDの登録
http://tech.nitoyon.com/ja/blog/2008/07/31/c-sharp-com/

あまり参考にならなかったが、コードのサンプル
https://www.84kure.com/blog/2014/07/17/c-c%E3%81%8B%E3%82%89c%E3%81%AEdll%E3%82%92%E5%91%BC%E3%81%B6%E6%96%B9%E6%B3%95/

文字列を渡すときのマーシャリング(BSTR ⇒ string)
https://docs.microsoft.com/ja-jp/dotnet/framework/interop/default-marshaling-for-strings

COMラッパーに文字列を渡したいときに参考にしたサンプル
https://www.acot.net/WMI/sample4.html

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

.NET Core版WPFでもEdgeのWebViewが使いたい!

EdgeベースWebViewとは

Microsoft.Toolkit.Wpf.UI.Controls.WebViewのこと。

IEベースではなく、Windows10で追加されたEdgeレンダリングのWebViewを、アプリに追加できる。

落とし穴

Edge版WebViewの現在の正式リリース版は .NET Frameworkのみ対応のバージョン5です。

.NET Core版WPF対応のver 6は現在previewなので、VisualStudioからいつものように入れることができません。(バージョン5が入る。)

どうすればいいのか

とっても簡単。パッケージマネージャーからコマンド叩いて入れます。

手順

  1. VisualStudioでツール -> NuGetパッケージマネージャ -> パッケージマネージャコンソール を起動。
  2. 以下のコマンドを入力(執筆時点2019/05/21での最新版です)
    Install-Package Microsoft.Toolkit.Wpf.UI.Controls.WebView -Version 6.0.0-preview5
  3. Enterをたたく。

とっても簡単。

余談

Chromium Edge版が開発されてるっぽい!

現在はWin32 C++のみ対応のようです。

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

LINQ to SQL の where で NTEXT 型のカラムを扱う方法

問題

次のコードのように LINQ to SQL で NTEXT 型のカラムを where で扱おうとしても

using(var dc = new DataContext())
{
    var NoDescriptionApples = dc.Apples
        .Where(i => "" == i.description).ToList(); // NG
    ...
}

そもそも素の SQL 文上で NTEXT 型の文字列比較はサポートされていない為、実行時エラーになります。

対処方法

NTEXT 型のまま比較しようするから弾かれるので、 Substring() で比較対象となる文字列より1文字だけ多く切り出して比較します。

using(var dc = new DataContext())
{
    var NoDescriptionApples = dc.Apples
        .Where(i => "" == i.description.Substring(0, 1)).ToList(); // OK
    ...
}

C# 上での Substring() は SQL の SUBSTRING に翻訳され、 SUBSTRINGNTEXT 型のデータを与えて実行した場合の戻り型は NVARCHAR 型になる為、普通に文字列比較ができるようになります。

その他の対処方法

今回のようなケースではそもそも NTEXT 型の使用を諦めて NVARCHAR 型にしてしまうのが一般的なようです。

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

Azure Speech Servicesを使って議事録Appを作成する①

この記事では、Azure Speech Servicesを使って議事録Appを作成する方法を試してみます!

議事録app作成まで以下二つを試してみました。
1) Speech SDKを使って.NET Frameworkベースで音声を認識するプログラムを作成する(今回の記事)
2) Javascript ベースで作成し、Web AppとしてDeployしてみる(次回の記事)

Speech SDKを使って.NET Frameworkベースで音声を認識するプログラムを作成する

以下の手順で音声認識をするプログラムを作成しました。
①Visual Studio 2019をインストール
②.NET FrameworkのConsole App プロジェクトを作成
③Speech SDK NuGet パッケージのインストール
④Debug設定
④Azure Portalで Speech ServicesのDeploy
⑤コード作成
⑥Debug/音声認識

①Visual Studio 2019をインストール

以下よりVisual Studioのインストールを実施します(せっかくなので2019を使ってみましょう!)
https://visualstudio.microsoft.com/downloads/?utm_medium=microsoft&utm_source=docs.microsoft.com&utm_campaign=button+cta&utm_content=download+vs2019

.NET FrameworkのConsole App プロジェクトを作成

新規プロジェクトの作成より、下記のようにコンソールアプリ(.NET Framework)を選択します。
azs01.PNG

Speech SDK NuGet パッケージのインストール

右上のソリューションパッケージの管理からNugetをソリューションを検索し、[Microsoft.CognitiveServices.Speech]をプロジェクトにインストールします。
azs02.PNG

Debug設定

正しくBuildするためにご自身の環境にあわせてビルドの構成をします。
(ビルド/構成マネジャー より設定)
64bit環境→x64
32bit環境→x84
azs03.PNG

Azure Portalで Speech ServicesのDeploy

AzureポータルからSpeechを検索しデプロイします。KeyとRegionはメモっておきます。
azs04.PNG
後程コードの中に書き込むKeyとRegionですが、
Regionは以下のガイドラインに従って正しい値を入れます。
https://docs.microsoft.com/ja-jp/azure/cognitive-services/speech-service/regions
私は中央アメリカでやりましたので、「centralus」と入力します。

コード作成

MS Docsのサンプルコードベタ張りですが、下記のコードを「Priogram.cs」にコピペしてください。
(この際、KeyとRegionをご自身のものに設定します。)

using System;
using System.Threading.Tasks;
using Microsoft.CognitiveServices.Speech;

namespace helloworld
{
    class Program
    {
        public static async Task RecognizeSpeechAsync()
        {
            // Creates an instance of a speech config with specified subscription key and service region.
            // Replace with your own subscription key and service region (e.g., "westus").
            var config = SpeechConfig.FromSubscription("YourSubscriptionKey", "YourServiceRegion");

            // Creates a speech recognizer.
            using (var recognizer = new SpeechRecognizer(config))
            {
                Console.WriteLine("Say something...");

                // Starts speech recognition, and returns after a single utterance is recognized. The end of a
                // single utterance is determined by listening for silence at the end or until a maximum of 15
                // seconds of audio is processed.  The task returns the recognition text as result. 
                // Note: Since RecognizeOnceAsync() returns only a single utterance, it is suitable only for single
                // shot recognition like command or query. 
                // For long-running multi-utterance recognition, use StartContinuousRecognitionAsync() instead.
                var result = await recognizer.RecognizeOnceAsync();

                // Checks result.
                if (result.Reason == ResultReason.RecognizedSpeech)
                {
                    Console.WriteLine($"We recognized: {result.Text}");
                }
                else if (result.Reason == ResultReason.NoMatch)
                {
                    Console.WriteLine($"NOMATCH: Speech could not be recognized.");
                }
                else if (result.Reason == ResultReason.Canceled)
                {
                    var cancellation = CancellationDetails.FromResult(result);
                    Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");

                    if (cancellation.Reason == CancellationReason.Error)
                    {
                        Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
                        Console.WriteLine($"CANCELED: ErrorDetails={cancellation.ErrorDetails}");
                        Console.WriteLine($"CANCELED: Did you update the subscription info?");
                    }
                }
            }
        }

        static void Main()
        {
            RecognizeSpeechAsync().Wait();
            Console.WriteLine("Please press a key to continue.");
            Console.ReadLine();
        }
    }
}

Debug/音声認識

ここまで出来たらはデバッグします。
「Speach Something」がでるので、何か英語の音声を発すると認識してくれます。
(日本語でもイケルっぽいけど精度があまりよくないです)
azs05.PNG

次回は、Javascriptベースでモバイルで動くWeb Appを書いてみます。

参照記事

Speech Service がサポートされているリージョン
https://docs.microsoft.com/ja-jp/azure/cognitive-services/speech-service/regions

クイック スタート:.NET Framework (Windows) 用 Speech SDK を使用して音声を認識する
https://docs.microsoft.com/ja-jp/azure/cognitive-services/speech-service/quickstart-csharp-dotnet-windows

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

【Unity(C#)】Viveコントローラーの振動実装(SteamVR2.0)

Viveコントローラーの振動

SteamVR2.0を対象にViveコントローラーの振動実装を備忘録として記録します。

コントローラーにアタッチ
using UnityEngine;
using Valve.VR;

public class Hapic : MonoBehaviour
{
    public SteamVR_Input_Sources handType;

    public SteamVR_Action_Vibration hapicAction = SteamVR_Input.GetAction<SteamVR_Action_Vibration>("Hapic");

    [SerializeField]
    bool debug;

    //振動の強さ三段階
    public enum HapicPower
    {
        NONE = 0,
        WEEK = 1000,
        MIDDLE = 2000,
        STRONG = 4000
    }


    void Reset()
    {
        handType = this.gameObject.GetComponent<SteamVR_Behaviour_Pose>().inputSource;
    }

    void Update()
    {
        //デバッグ用 (どんぐらいの強さかな~?)
        if (debug)
        {
            ControllerHaptic(HapicPower.STRONG);
        }
    }

    //振動メソッドたち

    //パターン①
    public void ControllerHaptic()
    {
        float seconds = (float)HapicPower.MIDDLE / 1000000f;
        hapicAction.Execute(0, seconds, 1f / seconds, 1, handType);
    }

    //パターン②
    public void ControllerHaptic(HapicPower hapicPower)
    {
        float seconds = (float)hapicPower / 1000000f;
        hapicAction.Execute(0, seconds, 1f / seconds, 1, handType);
    }

    //パターン③
    public void ControllerHaptic(ushort microSecondsDuration)
    {
        float seconds = (float)microSecondsDuration / 1000000f;
        hapicAction.Execute(0, seconds, 1f / seconds, 1, handType);
    }

}

振動の設定

Executeの引数に振幅とやら振幅間隔やらいろいろ項目がありますが、
サンプルシーン内でも使用されていた
Execute(0, seconds, 1f / seconds, 1, handType)が一番使い勝手良いかと思います。

私は変更したい設定として、振動の強さくらいだったので
secondsの値を変えるだけで十分でした。

オーバーロード

オブジェクト指向の便利機能1だそうです。

オーバーロードとは、「引数や戻り値が異なるが名称が同一のメソッドを複数定義する」というオブジェクト指向プログラミングのテクニックである。

振動の強さに関して、
・決められた値
・決められた値の中から選択
・数字で指定
上記3パターン用意しました。

呼び出す際はこんな感じです。

//パターン①
ControllerHaptic();

//パターン②
ControllerHaptic(HapicPower.STRONG)

//パターン③
ControllerHaptic(3200);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む