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

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

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

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

DLL

DLL(Dynamic Link Library)とは、他のモジュールからも使用する事が出来る、関数とデータが格納されているモジュールのことです。

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

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

Q&A

解決済

3回答

742閲覧

C# C++DLLメソッド呼出(COMコンポーネント) VARIANT型出力引数の取得

OhaseO

総合スコア1

COM

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

DLL

DLL(Dynamic Link Library)とは、他のモジュールからも使用する事が出来る、関数とデータが格納されているモジュールのことです。

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

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

2グッド

1クリップ

投稿2024/09/10 07:41

編集2024/09/12 11:13

実現したいこと

あるアプリケーションをVB6ベースからC#ベースへ移行しています。
その中で、COMコンポーネントとして登録したC++ベースのDLLのメソッドを呼び出しているのですが、そのメソッドのVARIANT型の出力引数を取得したいです。

発生している問題・分からないこと

VARIANT型の出力引数を取得できていません。
VARIANT型を模倣した構造体を引数として与えていますが、出力が代入されることを期待しているプロパティがメソッドの呼び出し前後で変化していません。

該当のソースコード

C++ベースのメソッドの定義

1bool SDBServiceClass::GetField( 2 _bstr_t bstrKey, // In : key 3 _bstr_t bstrDestinationNetworkName, // In : 要求先ネットワーク名 4 _bstr_t bstrFieldName, // In : フィールド名 5 short shFieldNo, // In : フィールド番号 6 VARIANT *pvarData // Out : データ 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++ベースのメソッドの抜粋

1 case 5: // char型 // 2 // ----------- ■ 文字列(char)型の場合 3 if ( ( pvarData->vt & VT_BSTR ) && 4 ( pvarData->vt & VT_BYREF ) ) { 5 *(pvarData->pbstrVal) = bstrData.copy(); 6 } 7 break; 8 9 default: 10 m_cscSDBService.Unlock(); // ロック解除(SDBMSデータサービスクラス用) 11 return false; // 異常終了 12 13※VT_BSTR=8、VT_BYREF=16384

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

1bool GetField([In][MarshalAs(UnmanagedType.BStr)] string Key, [In][MarshalAs(UnmanagedType.BStr)] string NetworkName = "", [In][MarshalAs(UnmanagedType.BStr)] string FieldName = "", [In] int FieldNo = 0, [In][Out][MarshalAs(UnmanagedType.Struct)] ref object pData);

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

1var data1 = new VARIANT { vt = (ushort)(VarEnum.VT_BSTR | VarEnum.VT_BYREF) }; 2var pData1 = Marshal.AllocCoTaskMem(Marshal.SizeOf(data1)); 3Marshal.StructureToPtr(data1, pData1, true); 4var result1 = _netMSDBService.GetField(key, networkName, "TAG", 0, pData1); 5 6 [StructLayout(LayoutKind.Sequential, Pack = 8)] 7 private struct VARIANT 8 { 9 public ushort vt; 10 public IntPtr pVal; 11 }

試したこと・調べたこと

①当該メソッドを通っているかを確認しました。
別の引数によりswitch文のルートが決まります(C++メソッドの抜粋を参照)。期待している値の場合は戻り値としてtrue、期待していない値の場合は戻り値としてfalseが返るようになっており、true/falseが切り替わることは確認済みです。
現在のC#コードの場合は、case 5を通ることも問題ありません。
②デバッガでメソッドの呼び出し前後の構造体の中身を確認しました。
初期化後:pVal=0x00000000、vt=16392
メソッドの呼び出し後:pVal=0x00000000、vt=16392
pData1にも逐次適当な値が設定されています(例えば、0x0b1fd248)。

補足

VisualStudio2022、.Net8.0、C#12.0を使用しています。
C++内部の状態を確認するための仕込みをし再コンパイルしようと思いましたが、optional parameters must come after required parameters等、多くのエラーが発生している状態です。
そもそも、代入する値(bstrData(C++メソッドの抜粋を参照))が正しく設定されていない可能性も否めないのですが、現在のC#コードには問題はないのか、何か勘違いをしていないか等を知り、少しでも原因を絞りたい次第です。
何か気づいたことがあれば、ご教授いただけると助かります。些細なことでも構いません。
よろしくお願いいたします。

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

総合スコア133

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

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

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

総合スコア1666

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

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

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

総合スコア2681

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

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

OhaseO

2024/09/12 05:50

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.39%

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

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

質問する

関連した質問