QA@IT

Cで作成されたDLLをVB6で使用したい

9504 PV

お世話になっております。
こちらへの書き込みは初めてですので至らない点などあると思いますが、よろしくお願いいたします。

現在、とあるプロジェクトにてCで生成されたDLLをVB6にて使用したいと思っています。
関連性のありそうなスレッドはいくつか拝見いたしましたが、
なかなか解決に結びつかず困っております。
お知恵を拝借いたしたいと思います。よろしくお願いいたします。

使用したいと考えているのがこちらのフリーライブラリです。
[http://felicalib.tmurakam.org/index.html>]

問題となっているのが、

[DLL(C言語)]
int pasori_init(pasori p)
{
if (!p->open_reader_writer_auto()) {
return -1;
}
return 0;
}
※pasoriは構造体(
がついてるのでポインタ参照?)

[VB6での呼び出し部分]
Private Declare Function pasori_open Lib "felicalib.dll" () As Long
Private Declare Function pasori_init Lib "felicalib.dll" (ByVal p As Long) As Long

Public p_ptr As Long 'Pasoriポインタ
:
Public Function Pasori_Connect() As Boolean
:
p_ptr = pasori_open()
:
If pasori_init(p_ptr) <> 0 Then
Pasori_Connect = False
End If
:

Vb6ではinteger型はLong型で扱うということで上記のように宣言いたしました。
ByValをByRefとするとVB6が強制終了します。
ByValのまま使用すると、「pasori_init(p_ptr) 」にてエラーが返ります。

エラー:DLLが正しく呼び出せません。(エラーNo.49)

単純に、呼び出し方が不正解なのだと思っておりますが、
どのようにしたら正解なのかがわかりません。

お手数をおかけしますが、
ご教授いただけたら幸いです。よろしくお願いします。

回答

http://msdn.microsoft.com/ja-jp/library/dt232c9t(v=vs.90).aspx

WINAPI や __stdcall など使われていません。
そもそも dllがそういう使い方ができるように作られていませんので、Declare Functionでは呼び出す事ができません。

作者のC#のサンプルのようにLoadLibrary, GetProcAddress といったWin32APIを経由して呼び出すしかないでしょうが、ポインタが絡んできているのでそれ相応の知識が必要ですので簡単に・・・とはいかないと思います(現在の知識では厳しいと思った方がいいでしょう)。

使いたい機能がライブラリのごく一部の機能だけであるなら、

  • Cでラッパを作る( 呼び出しを肩代わりしてくれる WINAPI付きのCライブラリを自作) CでDLLが書けるぐらいなら苦労していないと思いますので、あまり現実的ではないですね。
  • .NETでラッパを作る( COM オブジェクトなDLLを自作。VB.NETでも構わないと思います) C#が読めるなら、作者のC#でのサンプルを参考に作れますのでわりと現実的かと思います。

という手段もあります。

VB6にこだわる理由がないならばアプリケーションもろとも .NETで作るという手もあると思います。

編集 履歴 (0)
  • ご回答ありがとうございます。
    > ポインタが絡んできているのでそれ相応の知識が必要
    Cの知識が乏しいもので、LoadLibrary、GetProcAddress を使用したものは確かに自分には厳しいと思われます。
    .NETでラッパを作成する方向で対応してみようと思います。
    VB6のアプリケーションでも使えるようにとのことなので、
    すべてを。NETにするわけにもいかないのです;;
    -

最初のご回答の

WINAPI や __stdcall など使われていません。
そもそも dllがそういう使い方ができるように作られていませんので、Declare Functionでは呼び出す事ができません。

がようやくきちんと理解できました。

よく理解しないで、危険なことをしていたんですね。

cdeclとstdcallの違いも理解できました。
スタックメインで処理するものとWindowsAPIでは、
そもそもの呼び出し方が違うですね、
だから、「DLLが正しく呼び出せません。」になるわけでした。

そして、felicalibがstdcallでなく、cdecl(っぽい?)であることもわかりました。
stdcallで呼び出せるような作りではないですね。
そして、自分でstdcallな関数を作るには今の知識では難しいですね。

.NETコンポーネントの生成にて、
「CallingConvention」を指定し、DLLの呼び出しを行うように修正を行いたいと思います。

とても丁寧で、自分のような知識のないものにもわかりやすかったです。
勉強になりました。

本当にお世話になりました。
ありがとうございます!
また機会がありましたら、よろしくお願いいたします。

編集 履歴 (0)

stdcall と cdecl の続きです。

.NETで実行する

__stdcallなしの状態で、VB.NETで以下のコードを実行してみます。

Module1.vb

    Sub Main()
        Call (New Class1()).DoSomething()
    End Sub

Class1.vb

Public Class Class1
    Private Declare Function qait_sample Lib "felicalib.dll" (ByVal a As Integer, ByVal b As Integer) As Integer

    Sub DoSomething()
        Console.WriteLine(qait_sample(8, 2))
        Console.ReadKey()
    End Sub
End Class

LongがIntegerに直ってますが同じです。
この場合は実行できてしまいますね(ここの理由はちょっとわからないです、.NETだと自動で判別してくれるかもしれません)。

VB.NETでは別の呼び出し構文があり これを使うと CDECLであることを明示することができます。

Class1.vb

Imports System.Runtime.InteropServices

Public Class Class1
    <DllImport("felicalib.dll", CallingConvention:=CallingConvention.Cdecl)> _
    Private Shared Function qait_sample(ByVal a As Integer, ByVal b As Integer) As Integer
    End Function

    Sub DoSomething()
        Console.WriteLine(qait_sample(8, 2))
        Console.ReadKey()
    End Sub
End Class

長くなりましたが、気になるところです。
作者のサンプルでも、DllImportでCallingConvertionを指定していません。
現在でも指定せずに動いているかもしれませんが、指定したほうがいいんではないかと思います。

実際にはテストしてもらって決定してくれればいいと思いますが参考までに。

http://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.callingconvention(v=vs.110).aspx

Cdecl 呼び出し規約を適用する例を次に示します。呼び出し元によってスタックはクリーンアップされるため、これは必ず使用する必要があります。

編集 履歴 (0)

解決済みですが、少し気になっている点があるので追記します。

stdcall と cdecl

そもそも私がVB6から使えませんよと言ったところです。
前提の話としてこれを説明します。
(「こう書くとこうなる」を示すだけで理解する必要はありません)

felicalib 4.2のソースを元にお話しします。
もしVS2008があるならビルド可能だと思いますので実際に実行してみるとわかりやすいかもしれません。

あたらしい関数を追加してみる

いきなりですが、もともとの関数を使うのは面倒なので、簡単な関数を足してしまいます。
といっても追加するのは10行程度で済みますので心配しなくても大丈夫です。

なお、環境は32bit環境として書いています。

felicalib.h

まず関数の宣言を1行追加します。前後の行は省略してます。
数値を二つうけとって、数値を返す関数です。(intなのでVB6ではLong, VB.NETではIntegerです。)

/* APIs */
#ifdef __cplusplus
extern "C" {
#endif

int qait_sample(int a, int b);  /* この行を追加 */

pasori *pasori_open(char *);

felicalib.c

実際のコードを追加します。

#include <stdio.h>

int qait_sample(int a, int b){  /* この3行を追加 */
  return a - b;
}

/**
   @brief PaSoRi をオープンする

felicalib.def

最後に公開する関数にqait_sampleを追加します。
EXPORTSの次の行に追加されています。

LIBRARY "felicalib"

EXPORTS
    qait_sample
    pasori_open

実行する

これでソリューションをビルドすると正常にビルドされ、qait_sample入りのfelicalib.dllが出来上がります。
(たぶん src\debugディレクトリにいます。)

このDLLをパスが通るディレクトリに置き、VBAかVB6で以下を実行してみましょう。

Private Declare Function qait_sample Lib "felicalib.dll" (ByVal a As Long, ByVal b As Long) As Long

Private Sub Command1_Click()
  Debug.Print qait_sample(9,2)
End Sub

「エラー:DLLが正しく呼び出せません。」になりませんでしたか?

修正して再度実行する

エラーになることが分かったら、コードを 2か所修正します。
felicalib.h と felicalib.c の int qait_sample(int a, int b) の intと qait_sampleの間に __stdcallを追加します。

.h

int __stdcall qait_sample(int a, int b);  /* この行を追加 */

.c

int __stdcall qait_sample(int a, int b){  /* この3行を追加 */

これでビルドしなおして、dllファイルを置き換えて、再度VBA(VB6)を実行します。
今度は実行できたと思います。

これがとてもざっくりした呼出規約 cdeclとstdcallの違いです。
VB6のDeclare Functionはstdcallのものを呼び出すようになっています。

cdeclのものとはメモリ解放のルールが異なるので、仮に cdeclのものが呼べてしまった場合もスタックポインタがおかしくなるとか不都合があるので普通やってはいけません。
今回では __stdcallをつけてないのが cdeclで __stdcallを付けた方が stdcallですね。

細かい事言えばいろいろあるんですけど(別名とかもあるし)ここではとりあえず、
VB6で呼びたければ stdcallが必要でしたが、felicalibは cdeclっぽい(だからVB6から使えない)です。
という事です。

長くなってすいませんが続きの気になることはまた回答起こします。
もう残りはそんなに長くないです(笑)

編集 履歴 (2)

解決したのでご報告を!
.NETコンポーネントを生成し、それを呼び出すことでVB6から呼び出すことができました。

参照したサイトです。
http://www.sev.or.jp/ijupiter/world/dc_interrop/dotnet_com_interrop.html#d1

皆様ご回答ありがとうございました。

編集 履歴 (0)
  • ちょっと気になる事があるので後で(明日以降になるかも)回答を追記します。興味があれば読んでみてください。 -

しまった!!コメントに記入してしまった!
改めて書き込みます。

御二方、ご回答ありがとうございます。

ポインタが絡んできているのでそれ相応の知識が必要

確かに、Cの知識が乏しいもので、LoadLibrary、GetProcAddress を使用したものは確かに自分には厳しいと思われます。
felicalib.defにてEXPORTSされているためDeclare Functionで呼び出せるものと勘違いしてました。

pasori構造体の中身をType文できっちり作ってやれば行けると思いますが...?

pasori構造体を使用する予定はないのですが、やはり構造体の宣言が必要でしょうか?
自分としては、pasori構造体に対するポインタをpasori_openで取得し、
それを使用してfelica_portをOpenしているものと思っていました。

作者のサンプルソースを参考に.NETでラッパを作成する方向で対応してみようと思います。

VB6のアプリケーションでも使えるようにとのことなので、 すべてを。NETにするわけにもいかないのです;;

編集 履歴 (2)

pasori構造体の中身をType文できっちり作ってやれば行けると思いますが...?

編集 履歴 (0)
  • 呼出規約はstdcallだという事ですか? -
ウォッチ

この質問への回答やコメントをメールでお知らせします。