もくじ
https://tera1707.com/entry/2022/02/06/144447
COM系記事
https://tera1707.com/entry/2023/10/15/012134#COM
やりたいこと
仕事で、COMを扱う機会があった。
そこで、C++で書かれたCOMを使うコードを、C#に書き直す、ということをした。
その時のメモ。
やったこと
とにかく、「COM windows C#」とかで検索しても、それっぽい情報が出てこない。
今回、仕事では上記やりたいこと(COMでセンサーを扱うということ)が何とかできたのだが、復習のために自宅PCで仕事でやったことを思い出してやってみようとしても、自宅PCにセンサ-類がなにもついていないためか、思った動きに全然なってくれない。
(センサーが一つも見つからない)
で、それが、PCに本当にセンサーが1つもないから仕方ないことなのか、自分のコードの書き方が間違えているせいでセンサーをとれないのか、の切り分けがどうしてもできなかった。
いつまでもぐるぐる回る感じになったので、書いたことをちゃんと動かして確認はいったんあきらめて、COMのコードをC++→C#に直す部分を、覚えているうちにメモしようと思う。
使わせていただいたコード
下記の、COMでセンサーを扱うコードの一部を参考にさせて頂き、C#に直した。
C#でのCOMのコードの書き方は、下記ページの「手動でinterfaceを用意する方法」の部分を参考にした。
コード(C++)
C++で書いたコード(とりあえずなにかのセンサー(今回は加速度センサ)を見つけようとするところまで)
これをC#に直す。
#include <windows.h> #include <iostream> //#include <propvarutil.h> #include <SensorsApi.h> #include <sensors.h> #pragma comment(lib, "Sensorsapi.lib") int main() { std::cout << "Hello World!\n"; CoInitialize(NULL); ISensorManager *pSensorManager; ISensorCollection* pMotionSensorCollection; ISensor* pMotionSensor; if (!SUCCEEDED(::CoCreateInstance(CLSID_SensorManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSensorManager)))) { return 0; } if (!SUCCEEDED(pSensorManager->GetSensorsByCategory(SENSOR_TYPE_ACCELEROMETER_1D, &pMotionSensorCollection))) { pSensorManager->Release(); return 0; } if (!SUCCEEDED(pMotionSensorCollection->GetAt(0, &pMotionSensor))) { pMotionSensorCollection->Release(); pSensorManager->Release(); return 0; } CoUninitialize(); return 1; }
コード(C#)
C#に直したコード。
C++でやってるCoInitialize(NULL);
にあたるコードは、C#のほうにはいらない。
using System.Runtime.InteropServices; namespace ComJikkenNet6_ { internal class Program { const string SENSOR_TYPE_ACCELEROMETER_1D = "C04D2387-7340-4CC2-991E-3B18CB8EF2F4"; static void Main(string[] args) { Console.WriteLine("Hello, World!"); var sensorManager = new SensorManager(); if(sensorManager is ISensorManager sm) { sm.GetSensorsByCategory(new Guid(SENSOR_TYPE_ACCELEROMETER_1D), out var sc); sc.GetAt(0, out var sensor); } } } } // これがいわゆる「CoClass」だと思われる(たぶん) [ComImport] [Guid("77A1C827-FCD2-4689-8915-9D613CC5FA3E")] internal class SensorManager { } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("BD77DB67-45A8-42DC-8D00-6DCF15F8377A")] internal interface ISensorManager { [PreserveSig] public long GetSensorsByCategory(Guid sensorCategory, out ISensorCollection ppSensorsFound); // 以下略(使わないものは省略してOK) } [ComImport] [Guid("23571E11-E545-4DD8-A337-B89BF44B10DF")] internal interface ISensorCollection { [PreserveSig] public long GetAt(ulong ulIndex, out ISensor ppSensor); } [ComImport] [Guid("5FA08F80-2657-458E-AF75-46F73FA6AC5C")] internal interface ISensor { [PreserveSig] public long GetID(ulong ulIndex, out Guid pID); }
C#に直すときのポイント
CoClassの定義
CLSID_SensorManager
の定義を、C++のコード上で探す。
→CLSID_SensorManager の上でF12を押す。
こんな感じで、CLSID_SensorManagerのCLSID定義と、SensorManagerクラスの定義が出てくる。
そのクラスの定義を、下記のようにC#に持ってくる。 クラスの中身はいらない。
[ComImport] [Guid("77A1C827-FCD2-4689-8915-9D613CC5FA3E")] internal class SensorManager { }
CLSID_SensorManagerをCoCreateInstance()するところを、C#にするには、SensorManagerをnewするだけでいい。
var sensorManager = new SensorManager();
CoClassの定義 別解
作業中・・・
Interfaceの取り込み
次に、ISensorManager
のインターフェースを取り込む。
ISensorManagerは、C++ではF12を押すと、下記のように定義されてるのが見れる。
それをC#にもってくると、下記のようになる。
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("BD77DB67-45A8-42DC-8D00-6DCF15F8377A")] internal interface ISensorManager { [PreserveSig] public long GetSensorsByCategory(Guid sensorCategory, out ISensorCollection ppSensorsFound); // 以下略(使わないものは省略してOK) }
GUIDはそのまま持ってくる。
メソッドの並びについて
C++IFの定義の中で、関数が4つほどあるが、この順番は勝手に変えてはいけない。 順番が重要。
COMがどこからでも(C++でもC#でも、エクセルでもVBでもどこでも)呼べるようにする都合で、順番とか大きさ?が重要らしい。
なので、ISensorManagerの場合、一つ目の関数は必ずGetSensorsByCategoryにする。
また、自分のC#コードの中で、ISensorManagerの中で使う関数がGetSensorsByCategoryだけの場合、それ以降の関数はC#コードの方には書かなくてOK。 逆に、1つ目の関数は使わなくて、2つ目の関数だけ使うときは、1つ目の関数を真面目に書いてもいいが、「dummy()」とかにしてしまってもOK。(ただ今回ためさなかった)
今回のコードの中で、Interfaceを「ISensorManager」以外に「ISensorCollection」「ISensor」の2つを使うので、その2つも、ISensorManagerと同じようにC#コード側に定義する。
省略できるあれについて
void _VtblGap1_17();などと書く。
作業中・・・
PreserveSigについて
これを付けると、HRESULTを戻すシグネチャではなく、帰ってくるハンドルを戻り値とするメソッドに変換できる。
などなど作業中・・・
その他・・・
SensorManagerをnewしたものを、ISensorManagerでキャストして、ISensorManagerのもつメソッドを読んでやれば、C++のpSensorManager->GetSensorsByCategory(SENSOR_TYPE_ACCELEROMETER_1D, &pMotionSensorCollection)
と同じことができる。
あとは、同じ要領で、Interfaceを使って関数を読んでいく。
おわり
これで、C++と同じ動きをC#でさせることができたはずなのだが、自宅PCでその確認ができなかった。
どこかの時点で、仕事PCにこのコードを持って行って試してみるか、自宅でセンサのあるPCを入手できるかしたら、動作確認してみることにする。
参考
COMの基礎
http://chokuto.ifdef.jp/urawaza/com/com.html
サンプルCOMクライアント
https://ichigopack.net/win32com/com_base_2_samplecode.html
MediaFoundation
https://docs.microsoft.com/ja-jp/windows/win32/medfound/media-foundation-and-com
よさげなサンプル?
これの関数一つ一つが、GUIDをもつIFの中の関数なのか?→C#で書くとしたら、いろいろかかなあかんやつ?
https://komugi-com.hatenadiary.org/entry/20120128/1327701429
手動でinterfaceを用意する方法
https://ichigopack.net/win32com/com_csharp_2.html
C#でISensorCollectionとかを定義するときに参考になりそう
書きたいことメモ
- CoClassの取得の仕方
- newするパターン
- Activator.CreateInstance(CLSID.SensorManagerType) するパターン
- インターフェースのメソッドの宣言の仕方
- 前から順番に書いてく方法
- 使わないヤツは適当に書く場合
- _VtblGap1_17 などと書く場合 -メソッドの宣言の仕方
- PreserveSig =trueにして、そのままのシグネチャにする方法
- PreserveSig =falseにして、引数のoutを戻り値に持ってくる方法
- その他
, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)
は必ずつけましょう- なくてもIFの取得はできたが、outがintとかの場合(GetCountとか)に、うまく値が取れなかった