none
VC DLL 給 VB 引用 RRS feed

  • 問題

  • 大家好。

    我以前曾經用 Visual C++ 6 (VC6) 封裝過 DLL ,
    而這個 VC6 DLL 可以被 VB6, VB .net, Java...... 等任何語言呼叫使用。

    如今我在 Visual C++ 2013 上想要建立一個 DLL 一樣可以被 VB6, VB.Net 使用,
    卻怎麼試都試不成功,在 VB2013一直出現「方法的型別簽章與interop不相容」的錯誤。
    而VB6因為2013還沒搞定而還沒機會試。

    < VC2013 DLL >

    建立新專案時,設定為 Win32 DLL 空專案。

    ------MyProject.h------

    #include <cstdlib>
    #include <windows.h>
    
    ULONG WINAIP GetVersion( BYTE* baSWVersion, BYTE* baFWVersion );

    ------MyProject.cpp------

    #include "MyProject.h"
    
    #define d_SWVER "V1.0.0.0"
    #define d_FWVER "V1.0"
    
    BOOL APIENTRY DllMain( HANDLE hinstDLL,
    					   DWORD  fdwReason,
    					   LPVOID lpReserved
    					   )
    {
    	return TRUE;
    }
    
    ULONG WINAPI GetVersion( BYTE* baSWVersion, BYTE* baFWVersion )
    {
    	memcpy( baSWVersion, d_SWVER, sizeof( d_SWVER ) );
    	memcpy( baFWVersion, d_FWVER, sizeof( d_FWVER ) );
    	return 0;
    }

    ------MyProject.def------

    LIBRARY MyProject.dll
    EXPORTS
        GetVersion         @1

    < VB2013 AP >

    建立新專案時,設定為 Win32 Windows Form 應用程式。

    ------Main.vb------

    一個 Rich Text Box , rtbMsg ,用來顯示訊息。
    一個按鈕, btnCallDLL ,用來呼叫DLL。

        Private Sub btnCallDLL_Click(sender As Object, e As EventArgs) Handles btnCallDLL.Click
    
            Dim bytSWAPI(0 To 100) As Byte
            Dim bytFWVER(0 To 100) As Byte
    
            Erase bytSWVER
            Erase bytFWVER
    
            If GetVersion(bytSWVER, bytFWVER) <> 0 Then
                rtbMsg.Text = "Calling for GetVersion error."
                Exit Sub
            End If
    
            rtbMsg.Text = "SWAPI = " & System.Text.Encoding.Unicode.GetString(bytSWVER) & vbCrLf & _
                          "FWAPI = " & System.Text.Encoding.Unicode.GetString(bytFWVER)
        End Sub

    ------MyProject.vb------

    Module MyProject
    
        Public Declare Function GetVersion Lib "MyProject.dll" (ByRef baSWVersion As Byte(), ByRef baFWVersion As Byte()) As UInt32
    
    End Module

    <狀況>

    兩個專案編譯都沒有發生問題,
    VB 編出來的 .exe 也可以執行,
    但只要一呼叫到 GetVersion 這個 DLL 函數,
    就會出現「方法的型別簽章與interop不相容」的錯誤。

    這個狀況在以前 VC6, VB6 搭配時不曾發生,
    而現在 VC2013, VB2013 卻一直沒辦法如遇期的執行。

    找了好多網路資料,VC2013 DLL 編譯成功而 VB 可用的是有含 C++ Class 的寫法,
    這種 DLL 在 VB6 可能不能用而被我放棄了。
    另一種是不使用 C++ Class 的寫法,但他的步驟是 VC 2005 ,我照著做做出來是 VB2013 不能用的。

    難道 VC2013 封裝出來的 DLL 就一定要 interop 才能讓 VB6, VB.Net 使用嗎?
    能不能像以前一樣只要 DLL 與 EXE 放在相同路徑, VB6, VB2013 只要 Declare 就可以呼叫的方法呢?

    還請前輩指點迷津,謝謝。
    ~smallchou


    • 已編輯 smallchou 2017年5月16日 上午 08:50 修正錯字與刪除多餘字
    2017年5月16日 上午 08:44

解答

  • Hi, 剛剛用 vb6 試一下, 是可以正常 run 的,

    這是 h 檔裡的宣告:

    ULONG WINAPI GetVersion1(BYTE* baSWVersion, BYTE* baFWVersion);


    這是我在 vb6 模組裡的宣告,

    Declare Function GetVersion1 Lib "Win32Project1.dll" (ByRef baSWVersion As Byte, ByRef baFWVersion As Byte) As Integer


    這是 vb6 裡呼叫的地方,

     
    Private Sub Command1_Click()
        Dim b1(0 To 100) As Byte
        Dim b2(0 To 100) As Byte
        
        Dim res As Integer
        
        res = GetVersion1(b1(0), b2(0))
        
        Me.Caption = CStr(res)
        
        Dim s As String
        s = ""
        
        Dim i As Integer
        For i = 0 To UBound(b1)
            If b1(i) <= 0 Then Exit For
            s = s + Right("00" + Hex(b1(i)), 2)
          s = s + " "
        Next i
        
        Text1(0).Text = s
        s = ""
        For i = 0 To UBound(b2)
            If b2(i) <= 0 Then Exit For
            s = s + Right("00" + Hex(b2(i)), 2)
          s = s + " "
        Next i
        Text1(1).Text = s
    End Sub
    


    以下是執行後兩個 textbox 的內容,

    56 31 2E 30 2E 30 2E 30 
    
    
    56 31 2E 30 


    • 已標示為解答 smallchou 2017年5月26日 上午 09:45
    2017年5月17日 上午 06:30

所有回覆

  • Hi,

    以下是我看到的幾個問題:

    1. 在 vb 宣告時, 是呼叫到系統裡的 GetVersion(), 因而導致錯誤,

    用 depends 看一下你的 dll 就會明白,

    這部份把 function name 改掉(ex: GetVersion1)就好了, 

    至於要怎麼可以同名, 

    用 __declspec(dllexport) 去匯出 function, 然後 WINAPI 改成宣告 __cdecl, 如下:

    __declspec(dllexport) ULONG __cdecl GetVersion(BYTE* baSWVersion, BYTE* baFWVersion);

    用 depends 去看, 會看到有一個 GetVersion 跟 ?GetVersion@@YAKPAE0@Z,

    ?GetVersion@@YAKPAE0@Z 就是你的 function,

    vb.net 不要用 delcare, 改用 dllimport 指定 callingconvention 為 cdecl, entrypoint 指定成 ?GetVersion@@YAKPAE0@Z,

    這部份我試一試是 ok , 至於詳細的原理, 我也是一知半解, 個人覺得把名字改掉比較簡單,

    vb6 也只能用 stdcall, (網路上有 vb6 用 cdecl 的範例, 我看不懂就是了)

    2. 跟 BYTE* 對應, vb6 是用 byref xxx as byte, 用 array 的第 0 個放進去, 

       .NET 的 pinvoke 後來在 x64 之後, 改成用 byval xxx() as byte 去接, 拿 byte array 的 reference 去對應 byte* 指標,

       (x64/x86 都通用), 

         也可以用 <MarshalAs(UnmanagedType.LPWStr)> ByVal xxx As StringBuilder 去接,

       不過你會發現, 你收到的不是 unicode,

       用 <MarshalAs(UnmanagedType.LPStr)> ByVal xxx As StringBuilder 去接才正確,

       個人覺得用 stringbuilder 去接比較方便, 不然還要 trim vbNullChar (就是 array 後面那堆 0),

    3. 你的 function 裡用 memcpy 把字串 copy 到指標去, 

    而你define 的字串不是 unicode, 要定義unicode 的話, 把 "V1.0.0.0" 改成 L"V1.0.0.0",

    這樣就是 unicode 了,

    以上我也是在一知半解的狀況下, 慢慢試到可以用, 看對你的問題有沒有什麼幫助


    2017年5月16日 下午 01:02
  • LonghairPan 您好:

    其實我沒有遇到函數名稱與系統裏相同的問題,
    因為給客戶的正式名稱不能放上來網路,
    所以只是我在這的舉例用的名稱不好,
    讓您多解了這個問題真是不好意思XD

    由於 VB6 與 VB.Net 兩邊要同時用這個 DLL 的關係,
    希望開發行為一致所以 VB.Net 故意不用 dllimport 的。
    因為以前的專案 VC6 DLL 拿去 VB6, VB .Net 都可以只用 delcare 便能正常使用,
    希望朝向的解法是「透過專案的編譯屬性調整,讓 VC2013 編譯出來的 DLL 可以像 VC6 編出來的那樣, VB6, VB2013, Java 都可以用」。
    而非「VC2013標準設定編出來的 DLL , VB6, VB2013 要以不同方式去調整開發及引用方式才可以使用」。

    我在VC2013的DLL專案屬性,有看到一個項目,把它改成「Visual Studio 2013 - Windows XP」,
    然後什麼前置標頭檔等改成以C編譯,或是把 Function 定義成
    extern "C" ULONG WINAIP GetVersion( BYTE* baSWVersion, BYTE* baFWVersion );
    許多方法都試過了,編出來的 DLL 在 VB2013 還是沒辦法直接 delcare 使用。
    而我一樓討論的新建專案精靈選了 Win32 DLL 空專案、不含 MFC 、不含版本控制後,
    就不改任何專案屬性直接以預設值下去寫。
    如果調整這些屬性可以讓它編譯出我想要的那種 DLL ,還希望高人指點~

    回來原討論。VB那邊,我 delcare 如果用 ByVal ,他會出現陣列不可使用 ByVal 參考的編譯錯誤。

    這個 GetVersion 這個函數的兩個 Output 參數必須是 byte array ,
    因為實際上傳送的資料不是 String ,更有可能不是正常 ASCII 文字。
    所以我舉例的 "V1.0" ,我實際上要它傳的是 {0x56, 0x31, 0x21, 0x30 } 這個陣列,
    然後 VB 用 byte array 去接收,再用 CStr() 等函數操作成字串。

    不知道有沒有機會解決這個客戶需求……
    感謝您回文。

    2017年5月17日 上午 03:30
  • Hi, 剛剛用 vb6 試一下, 是可以正常 run 的,

    這是 h 檔裡的宣告:

    ULONG WINAPI GetVersion1(BYTE* baSWVersion, BYTE* baFWVersion);


    這是我在 vb6 模組裡的宣告,

    Declare Function GetVersion1 Lib "Win32Project1.dll" (ByRef baSWVersion As Byte, ByRef baFWVersion As Byte) As Integer


    這是 vb6 裡呼叫的地方,

     
    Private Sub Command1_Click()
        Dim b1(0 To 100) As Byte
        Dim b2(0 To 100) As Byte
        
        Dim res As Integer
        
        res = GetVersion1(b1(0), b2(0))
        
        Me.Caption = CStr(res)
        
        Dim s As String
        s = ""
        
        Dim i As Integer
        For i = 0 To UBound(b1)
            If b1(i) <= 0 Then Exit For
            s = s + Right("00" + Hex(b1(i)), 2)
          s = s + " "
        Next i
        
        Text1(0).Text = s
        s = ""
        For i = 0 To UBound(b2)
            If b2(i) <= 0 Then Exit For
            s = s + Right("00" + Hex(b2(i)), 2)
          s = s + " "
        Next i
        Text1(1).Text = s
    End Sub
    


    以下是執行後兩個 textbox 的內容,

    56 31 2E 30 2E 30 2E 30 
    
    
    56 31 2E 30 


    • 已標示為解答 smallchou 2017年5月26日 上午 09:45
    2017年5月17日 上午 06:30
  • LonghairPan 您好:

    不好意思這麼晚回應。

    感謝您給了一個很大的幫助是 Delcare 用 ByRef 後,後面變數不要是陣列,
    而且呼叫時帶進去的參數是陣列第一個元素的位址。
    這點解決了我們 Response 回不來的問題,現在可以正常收 Response 了。

    原本一直出錯: ByRef baSWVersion As Byte(), ByRef baFWVersion As Byte()
       使用時:GetVersion(bytSWVER, bytFWVER)

    參考您的作法: ByRef baSWVersion As Byte, ByRef baFWVersion As Byte
       使用時:GetVersion(bytSWVER(0), bytFWVER(0))

    目前仍然還存在的另一個狀況是 VC2013 在 Win7 中編譯出來的 DLL ,
    沒辦法在 WinXP 中的 VB6 使用(可編譯成功,但無法在 WinXP 裏執行)。
    這個狀況是不是一定要在 WinXP 中安裝 VC2013 的轉散發套件才能解決呀?

    感謝您!

    2017年5月22日 上午 06:52
  • 是的, 用 vc2013 寫的, 就要裝 vcredist 2013,

    看 vcredist 2013 的需求, xp 是可以安裝的

    2017年5月23日 上午 02:51
  • 嗯,所以無解了。
    客戶電腦因為四散在各處,並且沒有辦法遠端安裝轉散發套件。
    看來只有降 VC 的版本來編 DLL ,才有辦法給舊版 Windows 用了。

    感謝您的解答!

    2017年5月26日 上午 09:45
  • 還是可以的,

    專案屬性 > 組態屬性 > C/C++ > 程式碼產生 > 執行階段程式庫

    預設 release 是 /MD , debug 是 /MDd,

    release 改成 /MT, debug 改成 /MTd,

    就是名字沒有 DLL 的, 你會發現你的 dll 長大很多,

    這樣是可以不用去 xp 裝 vcredist,

    只是有點納悶, 在把 dll 交到客戶手上時, 不能一併給他 vcredist 順便裝就好嗎?

    不過 xp 乾淨的好像沒有 windows installer , 要安裝, 然後 service 要啟動, 有點麻煩就是了

    2017年5月26日 上午 10:57