DLL内の関数を使用して機器を制御

解決


ezrayt  2018-01-05 00:25:59  No: 48902

ある機器をそのメーカーが公開しているDLLを使用して制御する必要があります。
ただ、DLLがVC++で作成されており、ドキュメントもC++の内容で書かれています。
お恥ずかしながらC++の知識が全くなく手探り状態でDelphiで書いてみたのですが、構造体やポインタについての理解がうすいためうまく書けず、動作しません。

テストしているものはDLL内の関数(SetDate)を使用して機器の日付時刻を設定するというものです。

どうかご助言をいただけますようお願い致します。

//// 日付をセットする関数 C++////
long WINAPI SetDate(long abcKind, LPCSTR datetime, LPRESBASE outResp);

//// outResp (関数を実行すると値がセットされる)////
typedef  struct _REBASE {
  BYTE Status[3];
  LANHEADER respHeader;
} REBASE, *LPREBASE;

//// LANHEADER構造体 /////
typedef struct _LANHEADER {
  BYTE  Fix;
  BYTE  MsgKind;
  BYTE  MsgType;
  BYTE  SerialNo[4];
  BYTE  Date[6];
  BYTE  MD;
  BYTE  MachineID[5];
  BYTE  Status[2];
  BYTE  Reserv;
  ABCHEAD AbcHeader;
} LANHEADER, *PTLANHEADER;

//// ABCHEAD構造体 ////
typedef  struct{
  BYTE  AbcSN;
  BYTE  AbcAPLID;
}ABCHEAD;

//// Delphiで手探りで書いてみたコード ////
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;

 type
    ABCHEAD = record
      AbcSN: Byte;
      AbcAPLID: Byte;
   end;

 type
    LANHEADER = record
      Fix: Byte;
      MsKind: Byte;
      MsgType: Byte;
      SerialNo: array[0..3] of Byte;
      Date: array[0..5] of Byte;
      MD: Byte;
      MachineID: array[0..4] of Byte;
      Status: array[0..1] of Byte;
      Reserv: Byte;
      AbcHeader: ABCHEAD;
   end;

 type
    LPRESBASE = ^RESBASE;
    RESBASE = packed record
      Status: array[0..2] of Byte;
      respHeader: LANHEADER;
   end;

var
  Form1: TForm1;
  function SetDate(abcKind: LongInt; datetime: LPCSTR; outResp: LPRESBASE): Integer; stdcall; external 'ABCIF.dll';

implementation

procedure TForm1.Button1Click(Sender: TObject);
var
  rslt,xabcKind: LongInt;
  pOutResp: LPRESBASE;
  xOutResp: RESBASE;
  xDateTime: String;
  pDatetime: PAnsiChar;
  xFix:Byte;
begin
  xabcKind := 10;                            //関数のパラメータに使用
  xDateTime := '201801011225';               //関数のパラメータに使用

  GetMem(pDatetime, length(xDateTime) + 1);  //pDatetimeのメモリを確保
  StrPCopy(pDatetime, xDateTime);            //pDatetimeに値をセット

  New(pOutResp);

  rslt := SetDate(xabcKind, pDatetime, pOutResp);  //関数実行

  showmessage(IntToStr(rslt));               //rslt=0で正常終了だが0以外が返る

  xFix := pOutResp^.respHeader.Fix;          //respHeaderの中身を確認

  FreeMem(pDatetime);
  Dispose(pOutResp);
end;


通りすがり  2018-01-05 06:02:26  No: 48903

この手のトラブルで一番気になるのは構造体のアライメントですね。RESBASEだけpacked recordに
なっていますが、ほかのレコード型もpacked recordにしてみたらどうでしょうか?
(この手のAPIでは一般的に構造体アライメントは1のことが多いと思います)
あとDelphiは2009以降でターゲットのDLLはANSI版で間違いないですか?

これ以外のレコード型の定義には問題はなさそうです。ただSetDateを呼び出すときに構造体の領域を
GetMemで取得していますが、これは通常のローカル変数で構いません。

var
  R: RESBASE;
begin
  ...
  FillChar(R,SizeOf(R),0);
  rslt := SetDate(10,PAnsiChar('201801011225'),@R);
  xFix := R.respHeader.Fix;


通りすがり  2018-01-05 06:04:28  No: 48904

あ、あとDLLの関数を呼び出した *あとで* fatalなエラー(AVとか)になった場合は、
呼び出し規約が間違っていることが多いですね。


ezra  2018-01-05 20:11:35  No: 48905

レスありがとうございます。

うまくいかない状況は、関数を実行したときの戻り値に「パラメータ値異常、もしくはNAK応答です。」というエラー値が返ってきて機器の日付時刻の設定がされません。
outRespには値が返ってきているので他のパラメータが原因のような感じなのですが...

>RESBASEだけpacked recordになっていますが、ほかのレコード型もpacked recordにしてみたらどうでしょうか? 
>(この手のAPIでは一般的に構造体アライメントは1のことが多いと思います) 

やってみましたが変わらないようです。(全てpackedを外してみても同じ結果でした。)

>あとDelphiは2009以降でターゲットのDLLはANSI版で間違いないですか? 

DelphiはXE5です。
DLLがANSI版かどうかをメーカーに確認してみます。
(元の関数のパラメータがLPCSTRとなっていたのでDelphiでもそのまま書いて、関数にカーソルを合わせたときにパラメータの型がPAnsiCharと表示されていたのでそのまま書いていました。)
ちなみにPCharで指定してみても同じ結果でした。

>これ以外のレコード型の定義には問題はなさそうです。ただSetDateを呼び出すときに構造体の領域を 
>GetMemで取得していますが、これは通常のローカル変数で構いません。 

すっきり書けるんですね。ありがとうございます。このようにいたします。


通りすがり  2018-01-06 00:01:44  No: 48906

SetDateがエラーになったあともプログラムが死んだりはしていないのですね?
そういうことであればエラーの原因そのものは入力パラメータであるdatetimeにあるような気がします。
datetimeがUnicodeを要求しているのであれば、
var
  R: RESBASE;
  S: String;
begin
  ...
  S := '201801011225';
  FillChar(R,SizeOf(R),0);
  rslt := SetDate(10,PChar(S),@R);
  xFix := R.respHeader.Fix;

Ansiを要求しているのであれば
var
  R: RESBASE;
  S: AnsiString;
begin
  ...
  S := '201801011225';
  FillChar(R,SizeOf(R),0);
  rslt := SetDate(10,PAnsiChar(S),@R);
  xFix := R.respHeader.Fix;

このどちらかでrsltが正常を返すと思います。


ezrayt  2018-01-07 01:43:02  No: 48907

うまくいきました!
ありがとうございました。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加