5

私の VB6 アプリケーションでは、私のチームが (GNATCOM を使用して) Ada プロジェクトから作成した COM サーバーに対していくつかの呼び出しを行います。COM サーバーでは、基本的に 2 つの方法を使用できます。VB でのプロトタイプは次のとおりです。

Sub PutParam(Param As Parameter_Type, Value)
Function GetParam(Param As Parameter_Type)

ここで、Parameter_Typeは列挙型で、COM サーバーとやり取りできる多くのパラメーターを区別し、「値」はバリアント型変数です。PutParam() はバリアントを受け取り、GetParam() はバリアントを返します。(VB6 オブジェクト ブラウザで、COM サーバー インターフェイスのバリアント型への参照がない理由がよくわかりません...)。

このプロジェクトの製品は、Windows XP SP2 を搭載したコンピューター上で、このインターフェイスで何の問題もなく、この方法で何年も継続して使用されてきました。WinXP SP3 を搭載したコンピューターでは、「Long」タイプのパラメーターを入力しようとすると、エラー 0x800706F7「スタブが不正なデータを受信しました」が発生します。

何がこれを引き起こしているのか、誰にも手がかりがありますか? COM サーバーは、SP2 を適用したシステムにまだ組み込まれています。SP3 を使用してシステム上にビルドすると、違いが生じるはずですか? (X64 システムで X64 用にビルドするときのように)。

問題の原因となっている呼び出しの 1 つは次のとおりです (一部の変数名を変更)。

Dim StructData As StructData_Type

StructData.FirstLong = 1234567
StructData.SecondLong = 8901234
StructData.Status = True

ComServer.PutParam(StructDataParamType, StructData)

StructData_Type の定義は次のとおりです。

Type StructData_Type
    FirstLong As Long
    SecondLong As Long
    Status As Boolean
End Type

(質問が最初に投稿された後に以下が追加されました)

IDL の COM サーバーのインターフェイスでのプリミティブ呼び出しの定義を以下に示します。

// Service to receive data
HRESULT PutParam([in] Parameter_Type Param, [in] VARIANT *Value);

//Service to send requested data
HRESULT GetParam([in] Parameter_Type Param, [out, retval] VARIANT *Value);

私が渡そうとしている構造の定義は次のとおりです。

struct StructData_Type
{
   int FirstLong;
   int SecondLong;
   VARIANT_BOOL Status;
} StructData_Type;

ここでの定義が FirstLong と SeconLong の型として「int」を使用していて、VB6 オブジェクト エクスプローラーを確認すると、「Long」と型付けされているのが奇妙であることがわかりました。ところで、(特定のユーティリティを使用して) COM サーバーから IDL を抽出すると、これらのパラメータは Long として定義されます。

アップデート:

Windows 7 用にコンパイルされたバージョンの COM サーバー (異なるバージョンの GNAT、同じ GNATCOM バージョン) で同じコードをテストしたところ、動作しました。ここで何が起こったのかよくわかりません。WinXP SP3 での問題の特定を続けますが、Win7 でも動作することを知ってよかったです。同様の問題がある場合は、Win7 への移行をお試しください。

4

4 に答える 4

8

エラーの意味を説明することに焦点を当てます。質問のヒントが少なすぎて、簡単な答えを出すことができません。

「スタブ」は、実行境界を越えて呼び出しを行うときに COM で使用されます。質問では明示的に述べられていませんでしたが、Ada プログラムはおそらく EXE であり、アウトプロセス COM サーバーを実装しています。Windows では、プロセスが強力に分離されているため、プロセス間の境界を越えることは困難です。これは、Windows では RPC (リモート プロシージャ コール) によって行われます。これは、そのような境界を越えて呼び出しを行うためのプロトコルであり、ネットワークが典型的なケースです。

RPC 呼び出しを行うには、関数の引数をネットワーク パケットにシリアル化する必要があります。COM は、関数への実際の引数について十分に認識していないため、これを行う方法を知りません。プロキシの助けが必要です。引数の型が何であるかを知っているコードの一部。受信側には、プロキシが行うことと正反対のことを行う非常によく似たコードがあります。引数を逆シリアル化し、内部呼び出しを行います。これがスタブです。

これが失敗する原因の 1 つは、スタブがネットワーク パケットを受信し、そこに含まれるデータが関数の引数値に必要な量よりも多いか少ない場合です。明らかに、そのパケットをどう処理すればよいかわかりません。それを StructData_Type 値に変換する賢明な方法はなく、「スタブが不正なデータを受信しました」というエラーで失敗します。

したがって、このエラーについて考慮すべき最初の説明は、DLL Hell の問題です。プロキシとスタブの間の不一致。このアプリが長い間安定している場合、これは満足のいく説明ではありません。

コード スニペットには、この問題を引き起こす可能性のある別の側面があります。構造体はソフトウェアでは非常に厄介な獣であり、そのメンバーは整列されています自然なストレージ境界に配置され、アラインメント規則はそれぞれのコンパイラによる解釈の対象となります。これは確かに、引用した構造の場合に当てはまります。4 + 4 + 2 のフィールドを格納するには 10 バイトが必要で、自然に配置されます。しかし、構造体は実際には 12 バイトの長さです。構造体が配列に格納されるときに int が整列するように、最後に 2 バイトがパディングされます。また、COM は実装の詳細を隠し、構造の調整は非常に詳細であるため、COM の仕事を非常に困難にします。IRecordInfo インターフェイスのジョブである構造体をコピーするには、助けが必要です。スタブは、そのインターフェイスの実装が見つからない場合にも失敗します。

プロキシ、スタブ、および IRecordInfo について少し説明します。プロキシ/スタブのペアを生成する基本的な方法は 2 つあります。1 つの方法は、IDL (Interface Description Language) と呼ばれる言語でインターフェイスを記述し、それを MIDL でコンパイルすることです。そのコンパイラは、関数の引数の型を認識しているため、プロキシ/スタブ コードを自動生成できます。クライアントとサーバーの両方に登録する必要がある DLL を取得します。サーバーがそれを使用している可能性がありますが、わかりません。

2 番目の方法は VB6 が使用するもので、Windows に組み込まれているユニバーサル プロキシを利用します。FactoryBuffer と呼ばれ、その CLSID は {00000320-0000-0000-C000-000000000046} です。タイプ ライブラリを使用して動作します。タイプ ライブラリは、COM サーバー内の関数の機械可読な記述であり、FactoryBuffer が関数の引数をシリアル化する方法を理解するのに十分です。このタイプ ライブラリは、IRecordInfo が構造体のメンバーがどのように配置されているかを把握するために必要な情報を提供するものでもあります。サーバー側でそれがどのように行われるかはわかりません。GNATCOM については聞いたことがありません。

したがって、この問題の強力な説明は、タイプ ライブラリに問題があるということです。VB6 では、使用する GUID を直接制御できないため、特に注意が必要です。ささいな変更を加えると、新しいものを生成するのが好きです。これを回避する唯一の方法は、バイナリ互換性オプションを選択することです。タイプ ライブラリの古いコピーを使用し、新しいライブラリとの互換性をできるだけ維持しようとします。そのオプションを有効にしていない場合は、特に構造の GUID で問題が発生することが予想されます。変更され、もう一方の端がまだ古い GUID を使用している場合は Kaboom。

どこから探し始めるかについてのヒントです。これが SP3 によって引き起こされた問題であると想定しないでください。この COM インフラストラクチャは長い間変更されていません。しかし、新しいバージョンのオペレーティング システムがインストールされ、すべてを再登録する必要があるため、この種の問題が発生することは間違いありません。SysInternals の ProcMon は、プログラムがレジストリを使用してプロキシ、スタブ、およびタイプ ライブラリを見つけるのを確認するのに適したユーティリティです。また、最近では見つけるのが非常に難しくなっていますが、COM Spy のようなユーティリティの助けが得られることは間違いありません。

于 2013-04-10T09:49:24.493 に答える
0

XPで突然正常に動作しなくなった場合、私が探す最初の原因は型の不一致です。そのようなシステムの "long" は現在 64 ビットである可能性がありますが、Ada COM コード (および/または C の int) は 32 ビットを期待しています。従来の方法でコンパイルされたシステムでは、これはコンパイラによってチェックされていたはずですが、COM では余分な間接性があるため、それが難しくなっています。

「64ビットシステム用にコンパイルするとき」についてあなたがそこに書いたビットは、私を特に不安にさせます。64 ビット コンパイルでは、多くの C 型のサイズが変更される可能性があります。

于 2013-04-09T13:46:53.417 に答える
0

This Related Postは、マーシャリング コードが実際に送信するよりも多くのデータを予期する可能性があるため、構造体にパディングが必要であることを示唆しています (これはもちろんバグです)。構造体には 9 バイトが含まれています (int/long のそれぞれに 4 バイト、ブール値に 1 バイトと仮定)。構造体に 4 バイトの倍数が含まれるようにパディングを追加してみてください (または、投稿が予想されるサイズを明確にしていないため、失敗した場合は 8 の倍数)。

于 2013-04-10T08:52:12.117 に答える
0

また、問題は構造のパディングの問題が原因であることを示唆しています。#pragma を使用してこれを制御できるかどうかはわかりませんが、ドキュメントを参照する価値があるかもしれません。

結果のタイプ ライブラリ構造体が 4 (または 8) の倍数になるように、構造体にパッチを適用することをお勧めします。Status メンバーは 2 バイトを使用するため、Status の前または後に同じタイプのダミー値を挿入する必要があります。これにより、最大 12 バイトになります (8 バイトにパックする場合、これは 3 つのダミー変数にする必要があります)。 .

于 2013-04-12T12:29:01.777 に答える