DOBON.NET DOBON.NETプログラミング掲示板過去ログ

VB2008でAPIを使ったデバイス制御

環境/言語:[VB2008]
分類:[.NET]

お世話になります。
VBはまったくの初心者です。
紙幣識別機と通信する基盤を制御(シリアル通信?)するプログラムをしています。
付属のAPI関数を使い制御するのですが、データ送信の際、データフレームの作成(キャラクタコード?)を送信するらしいのですが、サンプルコードをもとに自分なりに作成してみましたが、配列データを送信する際
PutDt(ifHndl, BVCM_BILL, buff)
の部分でエラーになってしまいます。
下記のプログラムで間違っているところ、またはこうすべき、という部分をご指摘いただければ幸いです。

Private Declare Function ScanIF Lib "bvcm.dll" Alias "?BVCM_ScanInterface@@YGKPAI@Z" (ByRef puiNumber As Integer) As Integer
Private Declare Function OpenIF Lib "bvcm.dll" Alias "?BVCM_OpenInterface@@YGKIPAPAX@Z" (ByVal uiInterface As Integer, ByRef bvcmHandle As Integer) As Integer
Private Declare Function Stby Lib "bvcm.dll" Alias "?BVCM_Standby@@YGKPAXI@Z" (ByVal bvcmHandle As Long, ByVal iTarget As Integer) As Integer
Private Declare Function PutDt Lib "bvcm.dll" Alias "?BVCM_PutData@@YGKPAXIPAD@Z" (ByRef bvcmHandle As Long, ByVal iTarget As Integer, ByRef pcBuffer As Byte) As Integer


Dim ifNumber As Integer 'インターフェース数
Dim ifHndl As Long 'インターフェースハンドル
Dim stc As Integer 'ステータスコード
Dim buff(4) As Byte
Dim tmp As Byte


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
stc = ScanIF(ifNumber) 'インターフェースをスキャン
TextBox1.Text = ifNumber
If (ifNumber > 0) Then
stc = OpenIF(0, ifHndl) '最初のインターフェースをオープン
If (stc = BVCM_OK) And (ifHndl <> 0) Then
TextBox2.Text = "No.1 Inteface is Initialized."
stc = Stby(ifHndl, BVCM_BILL) 'ビルバリスタンバイ
stc = Stby(ifHndl, BVCM_COIN) 'コインメックスタンバイ

buff(0) = &H3
tmp = buff(0)
buff(1) = &H10
tmp = tmp Xor buff(1)
buff(2) = &H31
tmp = tmp Xor buff(2)
buff(3) = &H0
tmp = tmp Xor buff(3)
buff(4) = tmp

stc = PutDt(ifHndl, BVCM_BILL, buff)

End If
End If

長々とすみません。
以上よろしくお願いいたします
マルチポストなんですが・・・
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1024663730

● そのDLLは、MFCで作成されたものですので
  少々無理があるかナ〜

※ それに、VC 2003 で作成されたものですので
  ランタイムも必要になってます。
  (オープンはできているので、一応MFC関連DLLは存在する)

  しかしながら、バージョンは合わせた方が安全かも・・・

● で、Declare宣言間違ってます。

Private Declare Function PutDt Lib "bvcm.dll" Alias "?BVCM_PutData@@YGKPAXIPAD@Z" (ByVal bvcmHandle As Integer, ByVal iTarget As Integer, ByVal cBuffer() As Byte) As Integer

かな・・・

以上。参考まで
追伸

一応、ピコシステムズさんにVisualStudioのバージョンの件
問い合わせたら?
もしくは2008用を出す予定はあるか?とか・・・

※ コマンドリファレンス(ネイティブな)が別途もらえるなら
  USBデバイスに直接送受信するプログラムを組んだ方が、
  MFC経由DLLよりはましかも。

以上。
追伸2

試しにC++ CLIにてラップDLLを作成し、VB側から呼び出して
みましたが、エラーなく動作しました。

ただし、機器が無いので、bvcm.dll関係のみで試した結果で
す。

環境は、Vista Ultimate SP1 で、VS.NET2005/2008 Proが、
フルでインストールしてあります。

※ ラップDLLとは・・・
  MFC で作成された、bvcm.dllを呼び出すだけのマネージドDLL
  です。

  よって、VB側でDeclareしなくても、C++ CLI側でbvcm.h/bvcm.lib
  で作成されていますので、クラスのパブリックな関数でそのまま
  呼べます。

以上。参考まで。
■No24309に返信(オショウさんの記事)
> ● そのDLLは、MFCで作成されたものですので
>   少々無理があるかナ〜
なぜでしょうか?
外部に公開されている引数、戻り値がMFCに由来した型だったり、C++クラス単位での公開だったりすると無理がありますが、今回のような単純な型で構成された静的な関数であれば支障はないと思います。

> ※ それに、VC 2003 で作成されたものですので
>   ランタイムも必要になってます。
いらないのでは?
スタティックリンクされているように見受けられました。
(Dependecy Walkerで調査済み)

>   しかしながら、バージョンは合わせた方が安全かも・・・
何と合わせるんですか?
VC2003のランタイムとVB2005(.NET 2.0)、VB2008(.NET 3.5)の組み合わせは特に問題ありません。
Managed C++のDLL(つまり、.NET 1.1使用)だと、少し問題がありますが、今回のDLLはネイティブなのですよね?
なお、下記のダウンロードページからダウンロードできる「BVCM-USB-01 (Rev2.02): (zip/69,688Bytes)」を元に上記の発言をしました。

http://www.picosystems.net/dl/index.html
■No24313に返信(Azuleanさんの記事)
> なお、下記のダウンロードページからダウンロードできる「BVCM-USB-01 (Rev2.02): (zip/69,688Bytes)」を元に上記の発言をしました。
>
> http://www.picosystems.net/dl/index.html

  動作的にも仕様的にも問題は無いように思いますが、
  提供されているものが、VC2003でのMFC DLLなので
  仮にVB2008から使って問題が出た場合、大抵メーカ
  はバージョンの相違と言ってサポートしないように
  思いましたので。

※ 尚且つVBからの使用も認めていなさそう・・・
  と見受けられる。

※ 私的には、問題なし。と考えます。

以上。
2009/04/01(Wed) 07:58:52 編集(投稿者)

■No24314に返信(オショウさんの記事)
>   提供されているものが、VC2003でのMFC DLLなので
VC2003なのはサンプルと同じバージョンを使っているだろうと言うところから推測はできますし、この部分の主題である"VC2003という古いバージョンで作られたDLLだから"ということに対しては、異議はありません。

# 主題から外れますが、MFCを使っていると断定された根拠は何なのでしょうか?
# DLLのバイナリをざっくりと眺めても白黒つけられませんでした。
# サンプルプロジェクトはMFCですが、DLLのソースコードがついているわけではないので分かりません。

■No24314に返信(オショウさんの記事)
>   仮にVB2008から使って問題が出た場合、大抵メーカ
>   はバージョンの相違と言ってサポートしないように
>   思いましたので。
取説やAPIリファレンスをざっと見る限りは、どのバージョンをサポートしているとは書いていないですが、言語によるトラブルの場合、メーカーサポートは受けられない可能性があります。
また、XPまでしかサポートしていないような記述は見受けられるので、そろそろ危ないという雰囲気は感じられます。

■No24314に返信(オショウさんの記事)
> ※ 尚且つVBからの使用も認めていなさそう・・・
>   と見受けられる。

バージョンが当時よりも新しいという点はありますが、下記のような記載はあるので、VC以外からの利用は想定内だとみられます。

http://www.picosystems.net/prod/bvcm_usb01.html
> ■ 専用APIにより、各種言語(VB, VC, Delphi等)から利用可能
質問から横道にそれていますが、私の回答としては下記のところです。

・BVCM_HANDLEのところはIntPtrであるべき。
・PCHAR pcBufferに対応するのはbyteの配列か、IntPtrであるべき。
・BVCM_HANDLE *はByRefで構わないと思いますが、BVCM_HANDLEであるBVCM_PutData関数はByValでは?
> # 主題から外れますが、MFCを使っていると断定された根拠は何なのでしょうか?
> # DLLのバイナリをざっくりと眺めても白黒つけられませんでした。
> # サンプルプロジェクトはMFCですが、DLLのソースコードがついているわけではないので分かりません。

  をを!〜
  私の勘違いです。

●  その通りです。提供されているbvcm.dll/FTD2XX.dll ともにMFC DLL
  ではありません。

  ただ単にDEFファイルで関数のEXPORTSを宣言していないだけですネ!

  訂正して修正します。

以上。
皆様、ありがとうございます。
返信遅くなり申し訳ございません。
私はVBもまったくといっていいほど理解していない初心者なのですが、かなり専門的なところまで検証していただき感謝しております。
問題はプログラムもそうですが、それ以前のところに問題があるということのようですね。
仮にDLLが動作するとして
はじめに申し上げた宣言部のByRefについてですが、Byvalにすると
「保護されたメモリに書き込みを行おうとしました。メモリが壊れている」とのエラーで操作しませんでした。
私的な疑問としては、配列を単体のByte型変数に入れることは可能なのでしょうか?
可能であれば例などを教えていただけるとありがたいです。
よろしくお願いいたします
> はじめに申し上げた宣言部のByRefについてですが、Byvalにすると
> 「保護されたメモリに書き込みを行おうとしました。メモリが壊れている」とのエラーで操作しませんでした。
> 私的な疑問としては、配列を単体のByte型変数に入れることは可能なのでしょうか?
> 可能であれば例などを教えていただけるとありがたいです。

  ByValだけではなく、バイト配列の渡しの宣言部分も間違っていると
  思いますので、Declare宣言部分全てをコピペで入れ替えてみて下さ
  い。

● それと『マルチポスト』に対するマナーを熟知して下さい。

  尚・・・
  http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1024663730

  こちらに既に1件回答が投げられてます。

  このまま双方で各々対応できないので、マルチポストの対応をしてから
  対処して下さい。

● 私は本来FA関係専門のソフト屋なので、機器との通信は得意分野
  ですのでカキコしてますが、マルチポストの場合、通常、回答はつ
  きません。(マナー違反)

以上。
大変失礼いたしました。こういった質問は初めてなもので、マルチポストとはなにか、それがまわりに与える影響も考えず、マナー違反を犯しておりました。
以後気をつけたいと思います。どうかお許しいただければ幸いです。
アドバイスの通り宣言を変えたところ、エラーなくデバッグできました。
よくアドバイスを見ればお手数をおかけしなくともよかったのですが、大変失礼いたしました。
実際にプログラムを走らせて見ると、ACK1は返ってきているようなのですが、識別機は紙幣を受け入れる動作はしませんでした。やはりデータフレーム作成の段階で間違っているのでしょうか、以下はC++での受け入れ動作開始のサンプルです。
    char buf[5];
char tmp;

// TODO : ここにコントロール通知ハンドラ コードを追加します。
if(m_BvEnable == false)
{
buf[0] = 3; // bytes
tmp = buf[0];
buf[1] = 0x10; // data command
tmp = tmp ^ buf[1];
buf[2] = 0x31; // data1
tmp = tmp ^ buf[2];
buf[3] = 0x00; // data2
tmp = tmp ^ buf[3];
buf[4] = tmp; // fcc
gbsBvcmStatus = BVCM_PutData(gbhBvcmHandle, BVCM_BILL, buf);
if(gbsBvcmStatus == BVCM_ACK1)
{
m_BvEnable = true;
DispStr2Control(IDC_BUTTON1, "受入停止");
GetDlgItem(IDC_BUTTON2)->EnableWindow(false);
GetDlgItem(IDC_BUTTON3)->EnableWindow(false);

}
}

よろしくお願いいたします。
> 実際にプログラムを走らせて見ると、ACK1は返ってきているようなのですが、識別機は紙幣を受け入れる動作はしませんでした。やはりデータフレーム作成の段階で間違っているのでしょうか、以下はC++での受け入れ動作開始のサンプルです。

  機器のインターフェース番号が違っていませんか?
  それよりOpenInterfaceでBVCM_OKが返ってきているのか
  どうか・・・

  因みにメーカー提供サンプルでは動作するんですか?

※ そもそも電源オンしてUSB接続したらデバイスマネージャに
  その機器が出現していますか?

  正常に認識していて動作しないならば・・・
  こんなところで聞いてないで、メーカーにお聞きになるのが、
  先決かと。

以上。
OpenInterfaceではBVCM_OKが返ってきています。
またVC+のトライアル版をダウンロードしてみましたがメーカー提供サンプルでは動作確認とれました。(紙幣を認識します)
メーカーにも問い合わせて見たのですが専門がC++ということでVBでの実際の動作確認は有償ということでした。
実際のところVBのサンプルを部分的に頂いたのですがいろいろな言語が交錯していて、納得できる理解は得られず。やはり動作保証はできませんとのことでした。
しかしながら、皆様のおかげでなんとかデータ送信まではできるようになりました。
本当にありがとうございます。
自分なりに思考錯誤してみたいと思いますが、お気づきの点があれば、お手数をおかけしますが、ご教授お願い致します。
> メーカーにも問い合わせて見たのですが専門がC++ということでVBでの実際の動作確認は有償ということでした。
> 実際のところVBのサンプルを部分的に頂いたのですがいろいろな言語が交錯していて、納得できる理解は得られず。やはり動作保証はできませんとのことでした。
> しかしながら、皆様のおかげでなんとかデータ送信まではできるようになりました。

  そうでしょうネ!〜

  こういう機器の場合、ベースはC言語系での知識・動作実績がないと
  他言語から(昔はミックスドランゲッジと言いました)の使用では、
  言語間の渡しの部分でうまくいかないケースが多いです。

  メーカーもそういうマルチな人材を抱えておくにはコストがかかるの
  で、そこまでサポートしていないところが多い・・・

● VB側でPutDataに渡すバイト配列へのデータの代入部分を掲載して
  もらえませんか?

  C言語の・・・
  tmp = tmp ^ buf[1];

  は、VBでは
  tmp = tmp Xor buf(1)

  です。そこが間違っていればデータは通信上送信されますが
  データフォーマットが異常なので動作しないかと。

以上。
念のため。マルチポストの許可条件もご確認下さい。
http://dobon.net/vb/bbs/index.html#multipost

本来であれば、ルール違反の指摘も、ルール違反に抵触していると思われるスレッドへの書き込み(回答)も、ルール上はしないで欲しいと明示されています。
# かくいう私も同じことですが...

http://dobon.net/vb/bbs/index.html#advice
ご質問とは関係のない書き込みで申し訳ありません。

> 本来であれば、ルール違反の指摘も、ルール違反に抵触していると思われるスレッドへの書き込み(回答)も、ルール上はしないで欲しいと明示されています。

私の注意が行き届かないためにご迷惑をおかけして申し訳ありません。

今回のように質問者の方がご理解のある方であれば問題ないのですが(たぶん回答者の方もそのように感じたために回答しても大丈夫だと判断されたのだと思いますが)、あまりにひどい書き方をしている質問に対しては、絶対に回答をしないでいただきたいと切にお願いします。ただ、善意の回答については、注意をするとかえってややこしくなってしまいそうなので、黙認しているのが現状です。このあたりはどうするのがよいかもう少し考えてみます。
こちらこそ認識不足で皆様に不愉快な思いをさせて申し訳なく思っております。
管理人様、大変ご迷惑をおかけいたしました。

24326 についての返信ですが
当方のコードははじめに申し上げている部分と変わりありません。

Dim ifNumber As Integer 'インターフェース数
Dim ifHndl As Long 'インターフェースハンドル
Dim stc As Integer 'ステータスコード
Dim buff(4) As Byte
Dim tmp As Byte


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
stc = ScanIF(ifNumber) 'インターフェースをスキャン
TextBox1.Text = ifNumber
If (ifNumber > 0) Then
stc = OpenIF(0, ifHndl) '最初のインターフェースをオープン
If (stc = BVCM_OK) And (ifHndl <> 0) Then
TextBox2.Text = "No.1 Inteface is Initialized."
stc = Stby(ifHndl, BVCM_BILL) 'ビルバリスタンバイ
stc = Stby(ifHndl, BVCM_COIN) 'コインメックスタンバイ

buff(0) = &H3
tmp = buff(0)
buff(1) = &H10
tmp = tmp Xor buff(1)
buff(2) = &H31
tmp = tmp Xor buff(2)
buff(3) = &H0
tmp = tmp Xor buff(3)
buff(4) = tmp

stc = PutDt(ifHndl, BVCM_BILL, buff)

です。
昨日は、動作環境の近くにいませんでしたので確認できませんでしたが、
OpenIFでもACK1は帰ってきているようです。
以上、よろしくお願いいたします
> 24326 についての返信ですが
> 当方のコードははじめに申し上げている部分と変わりありません。

  C++のサンプルでは・・・
  OnInitDialogで、OpenInterface後、正常な場合以下の操作を
  行っています。

  gbsBvcmStatus = BVCM_Standby(gbhBvcmHandle, BVCM_COIN);
  if(gbsBvcmStatus == BVCM_ACK1) gbExistsCM = true;
  else gbExistsCM = false;

  gbsBvcmStatus = BVCM_Standby(gbhBvcmHandle, BVCM_BILL);
  if(gbsBvcmStatus == BVCM_ACK1) gbExistsBV = true;
  else gbExistsBV = false;

● このスタンバイ操作がVBの場合抜けていますので、追加して
  みて下さい。

※ 私もピコシステムズに問い合わせてみましたが・・・
  C言語以外からの使用は、その力量がある方なら自己責任の
  範疇で・・・と言う意味あいの返答だったと認識しています。
  一応他言語からは可能。との一文はありましたので、C++の
  サンプルをよく解読してインプリする必要があると考えます。

  頑張って下さい!

以上。
追伸

OnInitDialogの後半、SetTimerの手前に
BVCM_GetAllDataがありますが、多分、バッファに残って
いるデータの読み捨てのように思います。

一応、これも追加しておけばよいと考えます。

以上。
追伸2

老婆心ながら・・・
Private Declare Function ScanIF Lib "bvcm.dll" Alias "?BVCM_ScanInterface@@YGKPAI@Z" (ByRef puiNumber As Integer) As Integer

Declare宣言で、Win32 DLL内の各関数が、ソースコード上でエキスポート
宣言が為されているか、DEFファイルで関数名・序数が設定されていれば・・・
"?BVCM_ScanInterface@@YGKPAI@Z" と言うような関数名にはならなく、
正しく"BVCM_ScanInterface"とかになります。

コード的には美しく?ないので・・・
この場合、序数による関数宣言にされた方がよいかも。です。

エクセルのVBAでの宣言サンプルは・・・
http://www.ne.jp/asahi/hishidama/home/tech/excel/dll.html
ここの最後の方に書かれてます。

bvcm.dll 内の各関数の序数を調べるには、VB6のみではインストール
されないツールだったと思いますが、Azuleanさんが書かれている通り
Dependency Walkerを使います。

各関数の序数は
BVCM_CloseInterface 1
BVCM_GetAllData 2
BVCM_GetData 3
BVCM_LineReset 4
BVCM_OpenInterface 5
BVCM_PutData 6
BVCM_ScanInterface 7
BVCM_Standby 8

です。

参考まで・・・
■No24337に返信(オショウさんの記事)
> コード的には美しく?ないので・・・
> この場合、序数による関数宣言にされた方がよいかも。です。

この辺は好みになると思います。

序数だと書き間違えていたときに気づけない可能性があり得ますので、多少の文字修飾がついたところでも、関数名が読み取れる方が安心感があると私は考えています。

# 数字が違っても何となく呼び出せてるが、アクセス保護違反になったりとか、原因を掴みにくい形で現象が出てくるはず。
アドバイスのおかげで紙幣認識することが出来ました−!!
実際の認識にはタイマーでポーリングを0.5秒に一度かけてデータをGetAllDtによりチェックする必要がありました。

以下、動作したコードです

Public Class Form1
Const BVCM_BILL As Integer = 0
Const BVCM_COIN As Integer = 1

Const BVCM_OK As Integer = 0
Const BVCM_INVALID_HANDLE As Integer = 1
Const BVCM_IO_ERROR As Integer = 2
Const BVCM_INVALID_PARAMETER As Integer = 3
Const BVCM_NO_RESPONSE As Integer = 4
Const BVCM_OTHER_ERROR As Integer = 5
Const BVCM_ACK1 As Integer = 6
Const BVCM_ACK2 As Integer = 7
Const BVCM_ACK3 As Integer = 8
Const BVCM_ACK4 As Integer = 9
Const BVCM_NAK As Integer = 10
Const BVCM_BUSY As Integer = 11

Private Declare Function ScanIF Lib "bvcm.dll" Alias "?BVCM_ScanInterface@@YGKPAI@Z" (ByRef puiNumber As Integer) As Integer
Private Declare Function OpenIF Lib "bvcm.dll" Alias "?BVCM_OpenInterface@@YGKIPAPAX@Z" (ByVal uiInterface As Integer, ByRef bvcmHandle As Integer) As Integer
Private Declare Function Stby Lib "bvcm.dll" Alias "?BVCM_Standby@@YGKPAXI@Z" (ByVal bvcmHandle As Integer, ByVal iTarget As Integer) As Integer
Private Declare Function GetAllDt Lib "bvcm.dll" Alias "?BVCM_GetAllData@@YGKPAXIPADI@Z" (ByVal bvcmHandle As Integer, ByVal iTarget As Integer, ByVal pcBuffer As String, ByVal iBufferSize As Integer) As Integer
Private Declare Function PutDt Lib "bvcm.dll" Alias "?BVCM_PutData@@YGKPAXIPAD@Z" (ByVal bvcmHandle As Integer, ByVal iTarget As Integer, ByVal pcBuffer() As Byte) As Integer


Dim ifNumber As Integer 'インターフェース数
Dim ifHndl As Integer 'インターフェースハンドル
Dim stc As Integer 'ステータスコード
Dim buff(4) As Byte
Dim tmp As Byte


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
stc = ScanIF(ifNumber) 'インターフェースをスキャン
TextBox1.Text = ifNumber
TextBox2.Text = stc
If (ifNumber > 0) Then
stc = OpenIF(0, ifHndl) '最初のインターフェースをオープン
TextBox3.Text = ifHndl
TextBox4.Text = stc
If (stc = BVCM_OK) And (ifHndl <> 0) Then
TextBox5.Text = "No.1 Inteface is Initialized."
stc = Stby(ifHndl, BVCM_BILL) 'ビルバリスタンバイ
TextBox6.Text = stc



buff(0) = 3
tmp = buff(0)
buff(1) = &H10
tmp = tmp Xor buff(1)
buff(2) = &H31
tmp = tmp Xor buff(2)
buff(3) = &H0
tmp = tmp Xor buff(3)
buff(4) = tmp

stc = PutDt(ifHndl, BVCM_BILL, buff)



End If
End If



End Sub

Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Dim dtbuf(512) As String
stc = GetAllDt(ifHndl, BVCM_BILL, dtbuf(0), 512)

End Sub
End Class

オショウ様、Azulean様、初心者の私に懇切丁寧にご指導下さり、本当にありがとうございました。
また、実際の動作に関する知識以外にもいろいろ勉強になる部分がございました。
アドバイスを元に、さらにプログラムに励みたいと思います。
また、お世話になることもあろうかと思いますが、そのときはよろしくお願いいたします。
それでは、失礼いたします。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板