- 投稿日:2019-07-11T23:55:36+09:00
Dapperを使う時、毎回ConnectionのOpenとかをしなくて済ます為に作ったメソッド
THE 備忘録
dapperを使う時、毎回ConnectionのOpenとかTransactionのBeginをしなくて済ます為に作ったメソッド。
MessageBoxでいちいち表示するのは、ヘタクソなクエリでどんな例外が出るのか見当すらつかない為。//SELECT public IEnumerable<dynamic> SELECT<dynamic>(string sql) { IEnumerable<dynamic> modelEnum = null; using (var con = new NpgsqlConnection(ConnectionString)) { con.Open(); using (var trans = con.BeginTransaction()) { try { modelEnum = con.Query<dynamic>(sql); trans.Commit(); return modelEnum; } catch (NpgsqlException ex) { MessageBox.Show(ex.Message,""); trans.Rollback(); } catch (Exception ex) { MessageBox.Show(ex.Message, ""); trans.Rollback(); } } } return modelEnum; }//INSERTとかUPDATE(複数テーブル更新する時等、いわゆるaffectedrowsの数がカウント出来ない) public void Execute(List<string> sql) { using(var con = new NpgsqlConnection(ConnectionString)) { con.Open(); using(var trans = con.BeginTransaction()) { try { sql.ForEach(s => con.Execute(s)); trans.Commit(); } catch(NpgsqlException ex) { MessageBox.Show(ex.Message, ""); trans.Rollback(); } catch (Exception ex) { MessageBox.Show(ex.Message, ""); trans.Rollback(); } } } }
- 投稿日:2019-07-11T17:17:13+09:00
シリアル(RS232C), Ethernet で MELSEC Q PLC とパソコン通信
MELSEC PLCに関するライブラリにはPLC製造元が販売している「MX Component」がもっとも利用されています。フリーのライブラリは2019年7月11日の時点ではない(と思う)ので作成しました。,NET DLLです。
・形式2-5(RS232C), 3E,4E(Ethernet)対応
・スレッド通信
・一括読書(ワード単位、ダブルワード単位、ビット単位)、ワード単位ランダム読書き
・XMLインテリセンスもついている。
・デバクはシングルCPU単一ネットワーク内環境のみだが、PLC構成パラメータとしては某社製品に順ずるのを用意している。http://urx.space/V6Jg
https://www.vector.co.jp/soft/winnt/hardware/se519997.html【機器の設定など】https://www.youtube.com/watch?v=AM8FuK_eI-0&feature=youtu.be
インスタンスの生成Dim ins As New QEther.QEther設定(共通)ins.TIMEOUT = 100 '10msec単位 (Etherなら10(100msec), RS232Cなら100(1sec)程度が目安) 'タイムアウト時間を長くしても通信成功時はただちに終了するので長めにとってもかまいません設定(Ethernetの場合)ins.ActHostAddress = "192.168.1.10" ins.ActPortNumber = 2000 ins.BinaryASCII = EnumBinaryASCII.Binary 'PLCに設定したもの ins.MCProtocol = EnumMCProtocol.フレーム3E '3Eか4E(通常はどちらでもよい)設定(シリアル(RS232C)の場合)ins.MCProtocol = EnumMCProtocol.形式2 'PLCにあわせてください ' ins.CFrame = EnumCFrame.フレーム4C 'フレームは通常は特に設定する必要はありません ins.ActBaudRate = 19200 'PLC, およびRS232Cポートにあわせてください ' ins.ActDatabits = 7 '形式2-4は7bit, 形式5は8bit固定ですので設定する必要はありません。 ' ins.ActDtrEnable = True 'RS232Cポートにあわせてください ins.ActParity = EnumParity.Even偶数 'PLCおよびRS232Cポートにあわせてください ins.ActCOMPort = "COM8" 'RS232Cポートにあわせてください ins.ActStopbits = EnumStopbits.One'RS232Cポートにあわせてくださいワード単位ランダム一括読書き(引数にデバイス、値を並べる場合)ins.WriteDeviceRandom2(3, "D200", &HCCCC, "D202", &HDDDD, "D204", &H2222, "D207", &H6666)ワード単位ランダム一括読書き(オブジェクト配列で渡す場合)Dim word(10) As UShort Dim objword(10, 1) As Object For i = 0 To word.Length - 1 word(i) = i + 10 objword(i, 0) = "D" & i objwordr(i, 0) = "D" & i objword(i, 1) = word(i) Next ins.WriteDeviceRandom2(3, objword)などとこんな感じで利用します。
あとは以下
サンプルImports QEther.QEther Public Class QEtherDLLSample Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Dim ins As New QEther.QEther ins.ActHostAddress = "192.168.1.10" ins.ActPortNumber = 2000 ins.BinaryASCII = EnumBinaryASCII.Binary ins.MCProtocol = EnumMCProtocol.形式2 'Ethernet(3E,4E)のバイナリ, 形式5(4Cフレームバイナリ)に対応しています。 ins.CFrame = EnumCFrame.フレーム4C '############################ RS232 通信のさいに必要な設定です #################################### ins.TIMEOUT = 700 ins.ActBaudRate = 19200 ins.ActDatabits = 7 ins.ActDtrEnable = True ins.ActParity = EnumParity.Even偶数 ins.ActCOMPort = "COM8" ins.ActStopbits = EnumStopbits.One '#################################################################################### Dim word(10) As UShort Dim word2(word.Length) As UShort Dim dword(10) As UInteger Dim dword2(word.Length) As UInteger Dim objword(10, 1) As Object Dim objwordr(10, 0) As Object Dim s As String For i = 0 To word.Length - 1 word(i) = i + 10 objword(i, 0) = "D" & i objwordr(i, 0) = "D" & i objword(i, 1) = word(i) Next MsgBox("ランダムワードD0-10点書込み") ' ins.WriteDeviceRandom2(3, objword) ' ins.WriteDeviceRandom2DWord(3, "D200", &HAAAABBBB, "D202", &HAAAABBBB, "D204", &HAAAABBBB, "D207", &HAAAABBBB) ' ins.WriteDeviceRandom2(3, "D200", &HCCCC, "D202", &HDDDD, "D204", &H2222, "D207", &H6666) ' ins.WriteDeviceRandom2ArrayBIT(3, "M100", 1, "M107", 0, "M105", 1) ins.WriteRandomBlock2(2, "D0", 2, 123, 234, "M0", 3, &HAAAA, &HBBBB, &HFFFF) Dim randomread As New RetrunRandomWORD MsgBox("ランダムワード読込") randomread = ins.ReadDeviceRandom2("D0", "D1", "D2") For i = 0 To randomread.UShortArray.Length - 1 s = s & randomread.UShortArray(i) Next MsgBox(s) s = "" For i = 0 To word.Length - 1 word(i) = i * 10 Next MsgBox("一括ワードD0-10点書込み") ins.WriteDeviceBlock2("D0", word, word.Length) MsgBox("一括ワード読込") word2 = ins.ReadDeviceBlock2("D0", word.Length).UShortArray For i = 0 To word2.Length - 1 s = s & randomread.UShortArray(i) Next MsgBox(s) s = "" For i = 0 To dword.Length - 1 dword(i) = i * &HFFFF + &HFFFF Next MsgBox("一括DワードD0-10点書込み") ins.WriteDeviceBlock2DWord("D0", dword, dword.Length) MsgBox("一括Dワード読込") dword2 = ins.ReadDeviceBlock2DWord("D0", dword.Length).UIntegerArray For i = 0 To dword2.Length - 1 s = s & dword2(i).ToString("X8") Next MsgBox(s) s = "" Dim bit(50) As Byte Dim bit2() As Byte For i = 0 To bit.Length - 1 bit(i) = i Mod 2 Next bit(0) = &H1 '############## スレッドを使っているので書込みのときは通信終了を待たずに次の命令を実行できて便利な反面があります。 MsgBox("一括ビットM0書込み") MsgBox("1点") ins.WriteDeviceBlock2ArrayBit("M0", bit, 1) MsgBox("2点") ins.WriteDeviceBlock2ArrayBit("M0", bit, 2) MsgBox("3点") ins.WriteDeviceBlock2ArrayBit("M0", bit, 3) MsgBox("16点") ins.WriteDeviceBlock2ArrayBit("M0", bit, 16) MsgBox("33点") ins.WriteDeviceBlock2ArrayBit("M0", bit, 33) '###### しかし読込のときは注意が必要です。通信終了を待たずに次にいくので、通信終了まで待ち、 '### かつ、DataOKなら次を実施するということが必要です。 MsgBox("一括ビットM0読込") Dim insbit As New ReturnBIT insbit = ins.ReadDeviceBlock2ArrayBit("M0", 1) While Not insbit.Finished : End While ' 通信が終わるまで(異常終了した場合も抜ける)待つ If insbit.DataOK Then '通信終了後、データOKフラグがたってたらデータ格納 bit2 = insbit.ByteArray For i = 0 To bit2.Length - 1 s = s & bit2(i) Next MsgBox(s) s = "" End If insbit = ins.ReadDeviceBlock2ArrayBit("M0", 16) While Not insbit.Finished : End While ' 通信が終わるまで(異常終了した場合も抜ける)待つ If insbit.DataOK Then bit2 = insbit.ByteArray For i = 0 To bit2.Length - 1 s = s & bit2(i) Next MsgBox(s) s = "" End If End Sub Private Sub Button3_Click(sender As Object, e As EventArgs) Dim ins As New QEther.QEther ins.ActHostAddress = "192.168.1.10" ins.ActPortNumber = 2000 ins.BinaryASCII = EnumBinaryASCII.ASCII ins.MCProtocol = EnumMCProtocol.形式2 'Ethernet(3E,4E)のバイナリ, 形式5(4Cフレームバイナリ)に対応しています。 ins.CFrame = EnumCFrame.フレーム4C '############################ RS232 通信のさいに必要な設定です #################################### ins.TIMEOUT = 700 ins.ActBaudRate = 19200 ins.ActDatabits = 7 ins.ActDtrEnable = True ins.ActParity = EnumParity.Even偶数 ins.ActCOMPort = "COM7" ins.ActStopbits = EnumStopbits.One '#################################################################################### Dim bit As Byte() Dim word As UShort() word = ins.ReadDeviceBlock2BIT("M0", 1).UShortArray MsgBox(word(0).ToString("X4")) End Sub End Class
- 投稿日:2019-07-11T11:56:43+09:00
SeleniumがCannot start the driver service on http://localhost:${port} を吐いて死ぬ
- 投稿日:2019-07-11T02:27:26+09:00
AngularでCSVをダウンロード
Angularの書き方が分からず悩んでいます。
どなたかお解りの方がいらっしゃいましたら力を貸してくださると幸いです。
よろしくお願いします。実現したい事
C#のWebAPIから、CSVファイルをダウンロードしたいです。
分からないこと
画面側の Angular の処理の書き方がわかりません。
(APIはFileContentResultを返しています)【呼び出し元】
getCSV(): Observable<object> { this.service.getCSV() .subscribe((apiResponse: any) => { });【サービスクラス】
getCSV(): Observable<object> { const apiURL = "api/getCSV"; return this.http.get(apiURL); }開発環境
下記の環境で開発しております。
フロント:Angular7
サーバー:C#(.NET Core 2.2)
- 投稿日:2019-07-11T02:19:18+09:00
AzurePlayFabのランキング機能を使ってみた&ついでにランキング報酬も配ってみた
まえがき
オンラインゲームにかかせないランキング機能、自分で作るのは少し面倒ですよね。
PlayFab にはユーザーのランキングを管理したり、ランキングに応じて報酬を配ったりできる機能があります。今回は以前作ったユニティちゃんの縄跳びゲームに PlayFab のランキング機能を組み込んでみましたが、簡単便利で素敵だったので記事に残しておこうと思います。
成果物
使用する PlayFab の API
- UpdateUserTitleDisplayName - ランキングなどに表示するユーザー名を登録します
- UpdatePlayerStatistics - ランキングの集計対象にするデータ(スコアなど)を登録します
- GetLeaderboard - ランキングを取得します(1位から10位のランキングを取得する場合などはこちら)
- GetLeaderboardAroundPlayer - ランキングを取得します(自分の前後のランキングを取得する場合などはこちら)
1. 事前準備
1.1. クライアントから PlayFab への統計情報のポストを許可しておく
PlayFab は統計情報(スコアなど)をクライアントから送信することをデフォルトでは禁止しています。
チート対策のために基本的にはサーバー側で処理してね、とのことですが今回は個人制作の趣味のゲームですし、クライアントからサクッと値を送信したいと思います。
そのために GameManager の設定画面で
クライアントにプレイヤー統計情報のポストを許可する
を有効にします。
これを忘れるとクライアントから統計情報を送信したときに下記のエラーがでるので注意してください。
1.2. ランキングの定義を作成しておく
統計情報名
わかりやすい名前ならなんでも大丈夫です。
一度決めると変更できないので慎重に。リセット頻度
今回は手動にしますが、毎時/毎日/毎週/毎月なども選べます。
ランキングリセットの際は自動で報酬を配布したりすることもできます。(後述)集計方法
今回はプレイヤーごとの最多ジャンプ成功数をランキング表示したいので最大
にしておきます。1.3. ユーザーの DisplayName を登録しておく
ランキングにユーザー名を表示するためにはユーザーの DisplayName を登録しておく必要があります。
ゲーム内でユーザー名を設定する画面を作り、以下のようなコードで PlayFab へ登録しておきましょう。private void SetUserName(string userName) { var request = new UpdateUserTitleDisplayNameRequest { DisplayName = userName }; PlayFabClientAPI.UpdateUserTitleDisplayName(request, OnSuccess, OnError); void OnSuccess(UpdateUserTitleDisplayNameResult result) { Debug.Log("success!"); } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }ちなみに登録した DisplayName は GameManager のプレイヤー一覧などにも自動的に表示されます。
地味に嬉しいですね!
2. ランキングの実装
2.1. 統計情報(スコア)の送信
以下のようなコードで統計情報を送信することができます。
private void SendPlayScore(int score) { var statisticUpdate = new StatisticUpdate { // 統計情報名を指定します。 StatisticName = "JumpCount", Value = score, }; var request = new UpdatePlayerStatisticsRequest { Statistics = new List<StatisticUpdate> { statisticUpdate } }; PlayFabClientAPI.UpdatePlayerStatistics(request, OnSuccess, OnError); void OnSuccess(UpdatePlayerStatisticsResult result) { Debug.Log("success!"); } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }※送信した統計情報がランキングに反映されるまで1~2秒かかる場合があります。
※ですので送信後すぐにランキングを取得するような設計は控えましょう。
※こちらの記事で詳しく解説されています。2.2. ランキングの取得
以下のようなコードでランキングを取得することができます。
private void GetRanking() { var request = new GetLeaderboardRequest { StatisticName = "JumpCount", // 統計情報名を指定します。 StartPosition = 0, // 何位以降のランキングを取得するか指定します。 MaxResultsCount = 100 // ランキングデータを何件取得するか指定します。最大が100です。 }; PlayFabClientAPI.GetLeaderboard(request, OnSuccess, OnError); void OnSuccess(GetLeaderboardResult leaderboardResult) { // 実際は良い感じのランキングを表示するコードにします。 foreach (var item in leaderboardResult.Leaderboard) { // Positionは順位です。0から始まるので+1して表示しています。 Debug.Log($"{item.Position + 1}位: {item.DisplayName} - {item.StatValue}回"); } } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }実行すると以下のような結果が得られます。
これを良い感じに UI に組み込むだけでランキングの完成です!
ちなみにランキングのデータは GameManager で確認することが可能です。便利ですね。
そういえば今回は1位からのランキングを取得/表示しましたが、実際は自分の+-5位のランキングを表示したかったりすると思います。
その場合は GetLeaderboard の代わりに GetLeaderboardAroundPlayer を使います。
コードは殆ど同じです。private void GetRanking() { var request = new GetLeaderboardAroundPlayerRequest { StatisticName = "JumpCount", // 統計情報名を指定します。 MaxResultsCount = 11 // 自分と+-5位をあわせて合計11件を取得します。 }; PlayFabClientAPI.GetLeaderboardAroundPlayer(request, OnSuccess, OnError); void OnSuccess(GetLeaderboardAroundPlayerResult leaderboardResult) { // 実際は良い感じのランキングを表示するコードにします。 foreach (var item in leaderboardResult.Leaderboard) { // Positionは順位です。0から始まるので+1して表示しています。 Debug.Log($"{item.Position + 1}位: {item.DisplayName} - {item.StatValue}回"); } } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }ゲーム内の実装の話はここまでです。
最後にランキング報酬を配ってみましょう。3. ランキング報酬の配布
プライズテーブルを開いて、新しいプライズテーブルを作成します。
ランキング1~5位には魔法石10個、6~10位には魔法石5個を配る設定を作ってみました。
画面が直感的に操作できて素敵です。(スクショが長くてごめんなさい)
※アイテムは複数種類を一度に配布することも可能です。
※無料プランではランキング10位までしか報酬を配れないので注意してください。
※$99.99課金すると1000位まで配れるようになります。
※報酬を配る以外にも、ランキングに応じてメールを送信する、プッシュ通知を送る、自分で書いたスクリプトを実行するなどの処理が可能です。JumpCount のランキング表示に戻って、今すぐリセットを押します。
イベントが終わったらポチッとリセットする感じの運用ですね。
確認画面が表示されるのでリセットしてください。
これでランキングがリセットされて、同時に報酬が配布されます。
先程1位だったNishiさんのインベントリを覗いてみると、ちゃんと魔法石が配布されていますね。
あとがき
ゲームにランキングを簡単に実装できると聞いて試してみましたが、想像以上に簡単で驚きました。
報酬の配布もとても便利で最高ではないでしょうか。
- 投稿日:2019-07-11T02:19:18+09:00
AzurePlayFabのとても便利なランキング機能を使ってみた&ついでにランキング報酬も配ってみた
まえがき
オンラインゲームにかかせないランキング機能、自分で作るのは少し面倒ですよね。
PlayFab にはユーザーのランキングを管理したり、ランキングに応じて報酬を配ったりできる機能があります。試しに使ってみたところ、とても便利な機能だったので記事に残しておこうと思います。
成果物
使用する PlayFab の API
- UpdateUserTitleDisplayName - ランキングなどに表示するユーザー名を登録します
- UpdatePlayerStatistics - ランキングの集計対象にするデータ(スコアなど)を登録します
- GetLeaderboard - ランキングを取得します(1位から10位のランキングを取得する場合などはこちら)
- GetLeaderboardAroundPlayer - ランキングを取得します(自分の前後のランキングを取得する場合などはこちら)
1. 事前準備
1.1. クライアントから PlayFab への統計情報のポストを許可しておく
PlayFab は統計情報(スコアなど)をクライアントから送信することをデフォルトでは禁止しています。
チート対策のために基本的にはサーバー側で処理してね、とのことですが今回は個人制作の趣味のゲームですし、クライアントからサクッと値を送信したいと思います。
そのために GameManager の設定画面で
クライアントにプレイヤー統計情報のポストを許可する
を有効にします。
これを忘れるとクライアントから統計情報を送信したときに下記のエラーがでるので注意してください。
1.2. ランキングの定義を作成しておく
統計情報名
わかりやすい名前ならなんでも大丈夫です。
一度決めると変更できないので慎重に。リセット頻度
今回は手動にしますが、毎時/毎日/毎週/毎月なども選べます。
ランキングリセットの際は自動で報酬を配布したりすることもできます。(後述)集計方法
今回はプレイヤーごとの最多ジャンプ成功数をランキング表示したいので最大
にしておきます。1.3. ユーザーの DisplayName を登録しておく
ランキングにユーザー名を表示するためにはユーザーの DisplayName を登録しておく必要があります。
ゲーム内でユーザー名を設定する画面を作り、以下のようなコードで PlayFab へ登録しておきましょう。private void SetUserName(string userName) { var request = new UpdateUserTitleDisplayNameRequest { DisplayName = userName }; PlayFabClientAPI.UpdateUserTitleDisplayName(request, OnSuccess, OnError); void OnSuccess(UpdateUserTitleDisplayNameResult result) { Debug.Log("success!"); } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }ちなみに登録した DisplayName は GameManager のプレイヤー一覧などにも自動的に表示されます。
地味に嬉しいですね!
2. ランキングの実装
2.1. 統計情報(スコア)の送信
以下のようなコードで統計情報を送信することができます。
private void SendPlayScore(int score) { var statisticUpdate = new StatisticUpdate { // 統計情報名を指定します。 StatisticName = "JumpCount", Value = score, }; var request = new UpdatePlayerStatisticsRequest { Statistics = new List<StatisticUpdate> { statisticUpdate } }; PlayFabClientAPI.UpdatePlayerStatistics(request, OnSuccess, OnError); void OnSuccess(UpdatePlayerStatisticsResult result) { Debug.Log("success!"); } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }※送信した統計情報がランキングに反映されるまで1~2秒かかる場合があります。
※ですので送信後すぐにランキングを取得するような設計は控えましょう。
※こちらの記事で詳しく解説されています。2.2. ランキングの取得
以下のようなコードでランキングを取得することができます。
private void GetRanking() { var request = new GetLeaderboardRequest { StatisticName = "JumpCount", // 統計情報名を指定します。 StartPosition = 0, // 何位以降のランキングを取得するか指定します。 MaxResultsCount = 100 // ランキングデータを何件取得するか指定します。最大が100です。 }; PlayFabClientAPI.GetLeaderboard(request, OnSuccess, OnError); void OnSuccess(GetLeaderboardResult leaderboardResult) { // 実際は良い感じのランキングを表示するコードにします。 foreach (var item in leaderboardResult.Leaderboard) { // Positionは順位です。0から始まるので+1して表示しています。 Debug.Log($"{item.Position + 1}位: {item.DisplayName} - {item.StatValue}回"); } } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }実行すると以下のような結果が得られます。
これを良い感じに UI に組み込むだけでランキングの完成です!
ちなみにランキングのデータは GameManager で確認することが可能です。便利ですね。
そういえば今回は1位からのランキングを取得/表示しましたが、実際は自分の+-5位のランキングを表示したかったりすると思います。
その場合は GetLeaderboard の代わりに GetLeaderboardAroundPlayer を使います。
コードは殆ど同じです。private void GetRanking() { var request = new GetLeaderboardAroundPlayerRequest { StatisticName = "JumpCount", // 統計情報名を指定します。 MaxResultsCount = 11 // 自分と+-5位をあわせて合計11件を取得します。 }; PlayFabClientAPI.GetLeaderboardAroundPlayer(request, OnSuccess, OnError); void OnSuccess(GetLeaderboardAroundPlayerResult leaderboardResult) { // 実際は良い感じのランキングを表示するコードにします。 foreach (var item in leaderboardResult.Leaderboard) { // Positionは順位です。0から始まるので+1して表示しています。 Debug.Log($"{item.Position + 1}位: {item.DisplayName} - {item.StatValue}回"); } } void OnError(PlayFabError error) { Debug.Log($"{error.Error}"); } }ゲーム内の実装の話はここまでです。
最後にランキング報酬を配ってみましょう。3. ランキング報酬の配布
プライズテーブルを開いて、新しいプライズテーブルを作成します。
ランキング1~5位には魔法石10個、6~10位には魔法石5個を配る設定を作ってみました。
画面が直感的に操作できて素敵です。(スクショが長くてごめんなさい)
※アイテムは複数種類を一度に配布することも可能です。
※無料プランではランキング10位までしか報酬を配れないので注意してください。
※$99.99課金すると1000位まで配れるようになります。
※報酬を配る以外にも、ランキングに応じてメールを送信する、プッシュ通知を送る、自分で書いたスクリプトを実行するなどの処理が可能です。JumpCount のランキング表示に戻って、今すぐリセットを押します。
イベントが終わったらポチッとリセットする感じの運用ですね。
確認画面が表示されるのでリセットしてください。
これでランキングがリセットされて、同時に報酬が配布されます。
先程1位だったNishiさんのインベントリを覗いてみると、ちゃんと魔法石が配布されていますね。
あとがき
ゲームにランキングを簡単に実装できると聞いて試してみましたが、想像以上に簡単で驚きました。
報酬の配布もとても便利で最高ではないでしょうか。
- 投稿日:2019-07-11T00:55:27+09:00
COMオブジェクトを含んだネイティブ関数のP/Invokeメソッド変種
COMオブジェクトを生成する、ファクトリー関数をP/Invokeメソッドとして宣言する場合、(COMオブジェクトを含まなくてもそうだけど)いろいろな宣言が可能なので、そのメモ。
ネイティブ関数
SHCreateDefaultContextMenu を例にする。
SHSTDAPI SHCreateDefaultContextMenu(_In_ const DEFCONTEXTMENU *pdcm, _In_ REFIID riid, _Outptr_ void **ppv);メソッドいろいろ
素直なP/Invokeメソッド
C# 7.2から使用できるin引数を使用している。
[DllImport("shell32.dll", PreserveSig = false)] void SHCreateDefaultContextMenu(in DEFCONTEXTMENU pdcm, in Guid riid, out IContextMenu ppv);C# 7.2より前
Guid
構造体には、UnmanagedType.LPStruct
が使えるらしいので、ref
を付けずにポインタを渡せる。[DllImport("shell32.dll", PreserveSig = false)] void SHCreateDefaultContextMenu(ref DEFCONTEXTMENU pdcm, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IContextMenu ppv);COMオブジェクトをinterfaceではなく、objectで受け取る
[DllImport("shell32.dll", PreserveSig = false)] void SHCreateDefaultContextMenu(ref DEFCONTEXTMENU pdcm, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)]out object ppv);戻り値をHRESULTのまま受け取る
DllImport
属性のPreserveSig
をtrue
(デフォルト)にしておけば、ネイティブ関数のHRESULT
の値をそのまま受け取れる。
その代わり、エラーチェックは自分でやる必要がある。[DllImport("shell32.dll", PreserveSig = true)] int SHCreateDefaultContextMenu(in DEFCONTEXTMENU pdcm, in Guid riid, out IContextMenu ppv);最後の引数を戻り値として受け取る
COMのIDLには
retval
属性があり、メソッドの戻り値があるかどうかは最後の引数の属性で決まる。
単なる属性なので、P/Invokeでもシグニチャ次第となる。
PreserveSig = false
が付与されている前提で、
- 戻り値を
void
にすれば戻り値なしになる- 最後の引数を戻り値に移動させればそれが戻り値になる
[DllImport("shell32.dll", PreserveSig = false)] IContextMenu SHCreateDefaultContextMenu(in DEFCONTEXTMENU pdcm, in Guid riid);付録:上記で使用している型の宣言
struct DEFCONTEXTMENU { public IntPtr hwnd; public IContextMenuCB pcmcb; public PCIDLIST_ABSOLUTE pidlFolder; public IShellFolder psf; public uint cidl; public PCUITEMID_CHILD_ARRAY apidl; [MarshalAs(UnmanagedType.IUnknown)] public object punkAssociationInfo; public uint cKeys; // const HKEY *aKeys; public IntPtr[] aKeys; } [ComImport] [SuppressUnmanagedCodeSecurity] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214e4-0000-0000-c000-000000000046")] internal interface IContextMenu { void QueryContextMenu( /* [annotation][in] */ IntPtr hmenu, /* [annotation][in] */ uint indexMenu, /* [annotation][in] */ uint idCmdFirst, /* [annotation][in] */ uint idCmdLast, /* [annotation][in] */ uint uFlags); void InvokeCommand( /* [annotation][in] */ in CMINVOKECOMMANDINFO pici); void GetCommandString( /* [annotation][in] */ UIntPtr idCmd, /* [annotation][in] */ uint uType, /* [annotation][in] */ UIntPtr pReserved, /* [annotation][out] */ StringBuilder pszName, /* [annotation][in] */ uint cchMax); }
- 投稿日:2019-07-11T00:32:43+09:00
C#で作ったCOMをExcel VBAから呼ぶ
事象
dllをregasmで登録。その後VBAエディタで参照設定すると、インテリセンスは効くがオブジェクト生成できない
→実行時エラー「ActiveXコンポーネントはオブジェクトを作成できません。」原因
32bitのregasmで登録していたから。
もしExcelが32bitなら、64bit版のregasm使わなくてもいけたのかもしれない。[NG] %WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm CCC.dll /codebase /tlb [OK] %WINDIR%\Microsoft.NET\Framework64\v4.0.30319\regasm CCC.dll /codebase /tlb [おまけ、登録解除] %WINDIR%\Microsoft.NET\Framework64\v4.0.30319\regasm /tlb /u CCC.dll発生環境
windows10 1803 64bit
VS2019 16.0.3
Excel 16.0.11727 64bit