ある機器をそのメーカーが公開している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;
この手のトラブルで一番気になるのは構造体のアライメントですね。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;
あ、あとDLLの関数を呼び出した *あとで* fatalなエラー(AVとか)になった場合は、
呼び出し規約が間違っていることが多いですね。
レスありがとうございます。
うまくいかない状況は、関数を実行したときの戻り値に「パラメータ値異常、もしくはNAK応答です。」というエラー値が返ってきて機器の日付時刻の設定がされません。
outRespには値が返ってきているので他のパラメータが原因のような感じなのですが...
>RESBASEだけpacked recordになっていますが、ほかのレコード型もpacked recordにしてみたらどうでしょうか?
>(この手のAPIでは一般的に構造体アライメントは1のことが多いと思います)
やってみましたが変わらないようです。(全てpackedを外してみても同じ結果でした。)
>あとDelphiは2009以降でターゲットのDLLはANSI版で間違いないですか?
DelphiはXE5です。
DLLがANSI版かどうかをメーカーに確認してみます。
(元の関数のパラメータがLPCSTRとなっていたのでDelphiでもそのまま書いて、関数にカーソルを合わせたときにパラメータの型がPAnsiCharと表示されていたのでそのまま書いていました。)
ちなみにPCharで指定してみても同じ結果でした。
>これ以外のレコード型の定義には問題はなさそうです。ただSetDateを呼び出すときに構造体の領域を
>GetMemで取得していますが、これは通常のローカル変数で構いません。
すっきり書けるんですね。ありがとうございます。このようにいたします。
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が正常を返すと思います。
うまくいきました!
ありがとうございました。