質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
COM

COM(Component Object Model)はMicrosoftによるコンポーネントテクノロジーであり、 ソフトウェアの再利用を目的とした技術を指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

3回答

1190閲覧

C# C++呼出 VARIANT型出力引数の取得

OhaseO

総合スコア5

COM

COM(Component Object Model)はMicrosoftによるコンポーネントテクノロジーであり、 ソフトウェアの再利用を目的とした技術を指します。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

2グッド

1クリップ

投稿2024/09/10 07:41

編集2025/05/27 16:27

実現したいこと

C#から、COM登録したC++のメソッドを呼び出しています。
VARIANT型の出力引数を取得したいです。

発生している問題

VARIANT型をレイアウトした構造体を引数としていますが、メソッドの呼び出し前後で出力引数が変化していません。

該当のソースコード

C++のメソッドの定義

1bool FooClass::GetField( 2 _bstr_t foo, 3 _bstr_t bar, 4 _bstr_t baz, 5 short foobar, 6 VARIANT *pvarData 7)

C++のVARIANT型の定義

1typedef /* [wire_marshal] */ struct tagVARIANT VARIANT; 2 3struct tagVARIANT 4 { 5 union 6 { 7 struct __tagVARIANT 8 { 9 VARTYPE vt; 10 WORD wReserved1; 11 WORD wReserved2; 12 WORD wReserved3; 13 union 14 { 15 LONGLONG llVal; 16 LONG lVal; 17 BYTE bVal; 18 SHORT iVal; 19 FLOAT fltVal; 20 DOUBLE dblVal; 21 VARIANT_BOOL boolVal; 22 _VARIANT_BOOL bool; 23 SCODE scode; 24 CY cyVal; 25 DATE date; 26 BSTR bstrVal; 27 IUnknown *punkVal; 28 IDispatch *pdispVal; 29 SAFEARRAY *parray; 30 BYTE *pbVal; 31 SHORT *piVal; 32 LONG *plVal; 33 LONGLONG *pllVal; 34 FLOAT *pfltVal; 35 DOUBLE *pdblVal; 36 VARIANT_BOOL *pboolVal; 37 _VARIANT_BOOL *pbool; 38 SCODE *pscode; 39 CY *pcyVal; 40 DATE *pdate; 41 BSTR *pbstrVal; 42 IUnknown **ppunkVal; 43 IDispatch **ppdispVal; 44 SAFEARRAY **pparray; 45 VARIANT *pvarVal; 46 PVOID byref; 47 CHAR cVal; 48 USHORT uiVal; 49 ULONG ulVal; 50 ULONGLONG ullVal; 51 INT intVal; 52 UINT uintVal; 53 DECIMAL *pdecVal; 54 CHAR *pcVal; 55 USHORT *puiVal; 56 ULONG *pulVal; 57 ULONGLONG *pullVal; 58 INT *pintVal; 59 UINT *puintVal; 60 struct __tagBRECORD 61 { 62 PVOID pvRecord; 63 IRecordInfo *pRecInfo; 64 } __VARIANT_NAME_4; 65 } __VARIANT_NAME_3; 66 } __VARIANT_NAME_2; 67 DECIMAL decVal; 68 } __VARIANT_NAME_1; 69 } ;

C++のメソッドの抜粋

1if ( ( pvarData->vt & VT_BSTR ) && ( pvarData->vt & VT_BYREF ) ) { 2 *(pvarData->pbstrVal) = Copy(); 3} 4 5※VT_BSTR=8、VT_BYREF=16384

C#、VisualStudioでカーソルを当てたときに表示されるポップアップから飛んだC++のメソッドの定義

1bool GetField([In][MarshalAs(UnmanagedType.BStr)] string Foo, 2 [In][MarshalAs(UnmanagedType.BStr)] string Bar = "", 3 [In][MarshalAs(UnmanagedType.BStr)] string Baz = "", 4 [In] int FooBar = 0, 5 [In][Out][MarshalAs(UnmanagedType.Struct)] ref object pData);

C#、現在のコードの抜粋

1var data = new VARIANT { vt = (ushort)(VarEnum.VT_BSTR | VarEnum.VT_BYREF) }; 2var pData = Marshal.AllocCoTaskMem(Marshal.SizeOf(data)); 3Marshal.StructureToPtr(data, pData, true); 4var result = foo.GetField(foo, bar, baz, 0, pData); 5 6 [StructLayout(LayoutKind.Sequential, Pack = 8)] 7 private struct VARIANT 8 { 9 public ushort vt; 10 public IntPtr pVal; 11 }

調べたこと

メソッドの呼び出し前後の構造体を確認しました。
初期化後:pVal=0x00000000、vt=16392
メソッドの呼び出し後:pVal=0x00000000、vt=16392
pDataにも適当な値が設定されています(例えば、0x0b1fd248)。

補足

VisualStudio2022、.NET8.0、C#12.0を使用しています。
何か気づいたことがあれば、ご教授いただけると助かります。
よろしくお願いいたします。

dodox86, TeraGackn👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2024/09/11 05:11

> ②デバッガでメソッドの呼び出し前後の構造体の中身を確認しました。 > 初期化後:pVal=0x00000000、vt=16392 > メソッドの呼び出し後:pVal=0x00000000、vt=16392 > pData1にも逐次適当な値が設定されています(例えば、0x0b1fd248)。 pData1はMarshal.StructureToPtr(data1, pData1, true) でVARIANTのStructureからアンマネージメモリに内容をコピーしたものですから、 pData1に何かしら値がセット(例では0x0b1fd248)されているのであれば、COMのメソッドからBSTRの値がセットされているのではないでしょうか? そこからBSTRの文字列を取り出す必要があるのでは? もとのdata1には作用していないと思いますがいかがでしょうか。 ※もし外している話でしたらすみません。
OhaseO

2024/09/11 05:49

コメントありがとうございます。 COMのメソッドからBSTRの値がセットされる領域がpVal(構造体の9バイト目以降)と認識しています。 間違いだったらごめんなさい。 試したこと・調べたことに記載していなかったのですが、最終的には下記のように値を取得しようとしていました。 var gotData1 = Marshal.PtrToStringBSTR(data1.pVal); しかし、pValに何も設定されていないためか、null例外が発生する状況です。
OhaseO

2024/09/12 05:42

コメントありがとうございました。 ご教示いただいた情報も読ませていただきました。 今回はdllimportではなくCOM参照により参照を追加しており、dllimport部以外は同じようなことをしていたにも関わらず期待通りに動作していなかったのですが、matukesoさんが仰るようにobject型は自動でマーシャリングされるため、手動でメソッドを宣言する必要がありました(object型にしない)。 今後は、ご教示いただいたリンクも参考にさせていだたきます。
guest

回答3

0

[In][Out][MarshalAs(UnmanagedType.Struct)] ref object pData

[In][Out]ref VARIANT pData
としたらどうでしょう?

投稿2024/09/11 01:51

nururi

総合スコア169

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OhaseO

2024/09/11 02:35

回答ありがとうございます。 現在は、プロジェクトを右クリック→追加→COM参照により参照を追加していますが、そのためか、ref object pDataは自動で設定されたものです。変更はできなさそうでしたので、任意の型を設定したいのであれば、KOZ6.0さんが仰るような方法が必要なのかもしれないと思っていたところです(まだ試せてはいないです)。
guest

0

ベストアンサー

variantはデフォルトマーシャリングでObject型にできるので
object o=“some”;
…GetField(…,ref o);
みたいなコードでいけないですか?

https://learn.microsoft.com/ja-jp/dotnet/framework/interop/default-marshalling-for-objects
VT_BSTR|VT_BYREFなVARIANTは、それを通すマーシャリングがないのでダメですね。。

なので、GetFieldで自動のobject->variantマーシャラが動かないように、IntPtrとかカスタムVARIANT構造体とかを渡せるようにpDataの型を変更する必要があって、自動生成の場合は一手間ですね。
まず_netMSDBServiceのクラスがtlbimpで自動生成されているばあい、VS2022なら「逆コンパイルされたソースへのナビゲーション機能」で、定義に移動すれば(逆コンパイルされた)ソースがでてくるので、これをベースにすればいいとおもいます。順序が重要なので、interfaceを全部コピーする必要があります。

csharp:自動ComInterop.cs

1[ComImport] 2[Guid("XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX")] 3public interface _SDBServiceClass 4{() 5 bool GetField([In][MarshalAs(UnmanagedType.BStr)] string Key, [In][MarshalAs(UnmanagedType.BStr)] string NetworkName = "", [In][MarshalAs(UnmanagedType.BStr)] string FieldName = "", [In] int FieldNo = 0, 6[In][Out][MarshalAs(UnmanagedType.Struct)] ref object pData); 7}

みたいな定義がでてくるとおもうので、これをコピーして、名前だけちょっとかえて、GetFieldの宣言をかえます。(Guidは同じ)

csharp:変造Interop.cs

1[ComImport] 2[Guid("XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX")] 3public interface _SDBServiceClass2 { 4() 5 bool GetField([In][MarshalAs(UnmanagedType.BStr)] string Key, [In][MarshalAs(UnmanagedType.BStr)] string NetworkName = "", [In][MarshalAs(UnmanagedType.BStr)] string FieldName = "", [In] int FieldNo = 0, 6 [In][Out] ref VARIANT pData); 7} 8[StructLayout(LayoutKind.Sequential)] 9public struct VARIANT 10{ 11 public ushort vt; 12 public ushort r0; 13 public ushort r1; 14 public ushort r2; 15 public IntPtr ptr0; 16 public IntPtr ptr1; 17}

呼び出しはこんなかんじ。解放処理は省略しますが、ちゃんとVariantInitとか呼んだ方がいいかもだけど。

csharp:

1VARIANT pData; 2pData.vt = 8 | 16384; //VT_BSTR | VT_BYREF 3pData.ptr0 =Marshal.AllocHGlobal( IntPtr.Size); 4_SDBServiceClass2 _netMSDBService2= (_SDBServiceClass2 )_netMSDBService ; 5_netMSDBService2.GetField(key, networkName, "TAG", 0, ref pData); 6string data = Marshal.PtrToStringBSTR( Marshal.ReadIntPtr(pData.ptr0) );

投稿2024/09/11 01:42

編集2024/09/11 12:30
matukeso

総合スコア1681

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OhaseO

2024/09/11 04:03

回答ありがとうございます。 試してみましたが、メソッドの呼び出し前後で変わらずでした(o=“some”のままでした)。 “some”の場合はVT_BSTR型のバリアントにマーシャリングされると理解しましたが、 当該メソッドでは( pvarData->vt & VT_BYREF )のようにVT_BYREFも条件に入っていますので、 このIF文を通らなくなるように感じました。 ご教示いただいたページを参考にしながらもう少し考えてみます。
OhaseO

2024/09/12 05:22

ご丁寧な説明ありがとうございました。 無事に出力引数を取得することができました。 マーシャラ周りの理解も深まったように感じます。 本当にありがとうございました!
guest

0

インターフェイスを定義して呼び出せば .NET の型としてキャストできるのでは?
Copilot に聞いてみたサンプルコードを載せておきます。

csharp

1using System; 2using System.Runtime.InteropServices; 3 4namespace COMInteropExample 5{ 6 [ComImport, Guid("YOUR-COM-CLASS-GUID"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 7 interface ISDBServiceClass 8 { 9 bool GetField( 10 [MarshalAs(UnmanagedType.BStr)] string bstrKey, 11 [MarshalAs(UnmanagedType.BStr)] string bstrDestinationNetworkName, 12 [MarshalAs(UnmanagedType.BStr)] string bstrFieldName, 13 short shFieldNo, 14 [MarshalAs(UnmanagedType.Struct)] out object pvarData 15 ); 16 } 17 18 class Program 19 { 20 static void Main(string[] args) 21 { 22 // COM オブジェクトの作成 23 Type comType = Type.GetTypeFromProgID("YourProgID"); 24 ISDBServiceClass comObject = (ISDBServiceClass)Activator.CreateInstance(comType); 25 26 // メソッドの呼び出し 27 object data; 28 bool result = comObject.GetField("key", "networkName", "fieldName", 1, out data); 29 30 // データの型チェックとキャスト 31 if (data is string) 32 { 33 string stringValue = (string)data; 34 Console.WriteLine($"String Data: {stringValue}"); 35 } 36 else if (data is int) 37 { 38 int intValue = (int)data; 39 Console.WriteLine($"Integer Data: {intValue}"); 40 } 41 else 42 { 43 Console.WriteLine("Unknown Data Type"); 44 } 45 46 Console.WriteLine($"Result: {result}"); 47 } 48 } 49}

投稿2024/09/11 02:01

KOZ6.0

総合スコア2721

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

OhaseO

2024/09/12 05:50

ComImport周りが少し難しいこともあり後回しにしてしまっていたのですが、matukesoさんの回答を参考にしながら解決することができました。 回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問