JVRTOpen→JVRead 初回呼び出し時に SEHException (RPC_E_SERVERFAULT) が発生する件について

お世話になっております。

JV-Link の速報系データ取得について、
複数環境で同一の現象が発生しており、
原因確認をお願いしたく投稿いたします。

■ 発生している事象

C# (.NET Framework) から JV-Link を呼び出しており、
最小構成のコンソールアプリで以下の挙動になります。

JVInit(“UNKNOWN”) → 戻り値 0

JVRTOpen(“0B31”, “2025113005050808”) → 戻り値 0、JVStatus=0

続けて最初の JVRead(out buff, out size, out fileName) 呼び出しで例外発生

例外の内容は次の通りです。

System.Runtime.InteropServices.SEHException「外部コンポーネントが例外をスローしました。」HRESULT: 0x80010105 (RPC_E_SERVERFAULT)

※ 例外は「最初の1回目の JVRead」で必ず発生します。
※ 2台の Windows PC(どちらも x86 / .NET Framework / JV-Link 正常インストール済み)で
同じ最小コードを実行しても同じ現象になります。

■ 使用している JVRead のシグネチャ

C# 側で参照している JVRead のシグネチャは以下の通りです。

int JVRead(out string buff, out int size, out string fileName);

buff / size / fileName はすべて out パラメータとなっており、
呼び出し側では事前にバッファを確保せず、
そのまま JVRead(out buff, out size, out fileName) を1回だけ呼び出しています。

■ 前提条件

JVInit、JVRTOpen の戻り値はいずれも 0(正常)

dataspec=“0B31”、key=“2025113005050808” の組み合わせは、仕様上有効なレースID指定という認識です

C# 側では COM 参照「JRA-VAN Data Lab. タイプライブラリ (JVDTLabLib)」を使用

プロジェクト設定は x86 / [STAThread]

他の dataspec(蓄積系)では JVOpen → JVRead が正常に動作しており、同様の SEHException は発生していません

■ お伺いしたい点

上記条件において、

JVRTOpen 正常終了後の「最初の」 JVRead のタイミングで SEHException (0x80010105, RPC_E_SERVERFAULT) が発生する要因として、
JV-Link 側で想定されるケースはありますでしょうか。

対象データが存在しない場合や、内部ファイル未生成などの状態で、
JVRead が SEHException / RPC_E_SERVERFAULT になることは仕様上あり得ますか。

JVRead が .NET 側で「out string buff, out int size, out string fileName」というシグネチャになっている場合、
C# 側で事前に buff にダミーの文字列(例:new string(‘\0’, 110000))や size を設定しておく必要はありますでしょうか。

それとも、現在のように out パラメータのまま呼び出す形で問題ないでしょうか。

お手数をおかけしますが、ご確認のほどよろしくお願いいたします。

meso_butaさん、おはようございます。

蓄積系で正常に動作させられている様なので、それと同じ感じで使えば行けるんじゃないかと思われますが、

これってJOpenでもJRTOpenでも同じはず。自分はJVReadではなくJVGets使っているので既にJVReadの使い方覚えていませんが、インターフェース仕様書にはbuffは「呼出側で用意したデータ格納バッファを指定します。1行単位で読み出しますので改行コード (0x0d,0x0a)を含めたレコードデータが収容可能なサイズ+1(ストリング終端文字NULL)を用 意します。」と書かれていますし、sizeは「呼出側で用意したデータ格納バッファにコピーするデータの長さを指定します。この値がレコー ド長よりも小さい場合には残りのデータは切り捨てられ、データ格納バッファの最後の1バイト がストリング終端文字NULLとなります。」となっていますので、そのようにするべきでは?

SameNoEsaさま

丁寧なご回答とアドバイス、
ありがとうございました。

教えていただいたポイントをもとに、
JVGets(バイト配列版)を使った 0B31 の
最小実験コードを作成したところ、
こちらでは正常に O1 レコードを取得することができました。

同じ条件で JVRead(string 版)を呼ぶと
引き続き RPC_E_SERVERFAULT になりますが、
JVGets では問題なく読み込めているためこちらで進めます。

今回の切り分けの方向性や、
仕様書の読み方についても大変参考になりました。
お忙しいところ、ご助言いただき本当にありがとうございました。

meso_butaさん、こんばんは。

参考になったのなら良かったですが、JVGetsは若干気を付ける点があるので十分に仕様を理解してご利用下さい。インターフェース仕様書にもありますが、「また、JVGets ではメモリの解放を行わないので、アプリケーション側で読み出しの度に解放する必 要があります。」という事に対処する必要があります。

ここの前の板でやり取りがあり、C#での(Visual Basicでの方法はインターフェース仕様書に書かれてます)実現方法はちょっと手間が掛かります。

[DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint SafeArrayDestroy(IntPtr pSafeArray);
[DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint SafeArrayAccessData(IntPtr pSafeArray, ref IntPtr ppvData);
[DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint SafeArrayUnaccessData(IntPtr pSafeArray);

この一連のSafeArray関連を利用してC#では実現するって話があり、自分もしてますが、これが単に猿真似で全く理解しないまま利用している為、あまりうまく説明が出来ないのはお許しください。

参考になるのか不明ですが、

byte[] bBuff = new byte[0];     // JVGets用データ格納バッファ
// バッファ作成
object objBuff = (object)(IntPtr)0;
// JVGets で1行読み込み
iReturnCode = SaraD.clsJVLink.JVGets(ref objBuff, iBuffSize, out strFileName);
if (iReturnCode > 0)
{
    bBuff = new byte[iReturnCode];
    IntPtr pSafeArray = (IntPtr)(int)objBuff;
    try
    {
        // byte[] buff = new byte[iReturnCode];
        uint hr;

        IntPtr ppvData = (IntPtr)0;
        hr = SafeArrayAccessData(pSafeArray, ref ppvData);
        if (hr != 0) throw new System.Exception(hr.ToString());

        Marshal.Copy(ppvData, bBuff, 0, iReturnCode);

        SafeArrayUnaccessData(pSafeArray);
    }
    finally
    {
        SafeArrayDestroy(pSafeArray);
    }
}

って感じにしてます。この部分が猿真似なので説明が出来ませんが、これで現状特にメモリリークとか無い感じでうまく行ってるつもりでいます(笑)

コードが若干微妙に感じられると思いますが、自分はそもそもVisual Basicで開発初めて途中でC#にコンバートしたので変数名の定義がC#っぽくないのはそれですm(__)m

「いいね!」 1

SameNoEsaさま

こんばんは。

たびたび丁寧なご回答をいただき、
本当にありがとうございます。

教えていただいた
JVGets(バイト配列版)と文字列化の手順を試したところ、
0B31についても O1レコードを問題なく取得できることを確認できました。

また、実際のコード例を含めて詳しく説明していただいたおかげで、
仕様書の読み方や、
自前でパース処理を実装していく際の
イメージがかなりクリアになりました。

今回の切り分けから実装方針の決定まで、
とても参考になるアドバイスばかりで大変助かりました。

お忙しい中、
丁寧にご対応いただきありがとうございました。