将结构传递给从C#DLL到VB6的非托管代码

时间:2014-12-31 18:48:03

标签: c# dll vb6 marshalling

我有一些客户使用使用VB6和其他语言的应用程序。代码使用OLE(COM)工作正常,但客户更喜欢使用本机DLL来避免注册库并在现场部署它们。

当我注册DLL并在VB6(OLE)中测试时,它工作正常。当我调用一个返回Strutc的方法时,它可以正常使用OLE,但是,如果我使用Declare在VB6中访问,我在方法中会出现致命错误,该错误应返回相同类型的结构(方法' EchoTestData'见吼叫)。

代码在C#中编译,用于带有OLE的非托管代码或通过入口点>我用VB6进行了测试。

namespace TestLib
{
  [ClassInterface(ClassInterfaceType.AutoDual)]
  [ProgId("TestClass")]
  public class TestClass : System.EnterpriseServices.ServicedComponent
  {

    /* 
     * NOTE:
     * ExportDllAttribut: a library that I have used to publish the Entry Points,
     * I had modified that project and it works fine. After complile, the libray
     * make the entry points...
     * http://www.codeproject.com/Articles/16310/How-to-Automate-Exporting-NET-Function-to-Unmanage 
     */

    /* 
     * System.String: Converts to a string terminating in a null 
     * reference or to a BSTR 
     */
    StructLayout(LayoutKind.Sequential)]
    public struct StructEchoData
    {
        [MarshalAs(UnmanagedType.BStr)]
        public string Str1;
        [MarshalAs(UnmanagedType.BStr)]
        public string Str2;
    }

    /*
     * Method static: when I use this method, the Vb6 CRASH and the EVENT VIEWER
     * show only: System.Runtime.InteropServices.MarshalDirectiveException
     * HERE IS THE PROBLEM in VB6 with declare...
     * Return: struct of StructEchoData type
     */
    [ExportDllAttribute.ExportDll("EchoTestStructure", CallingConvention.StdCall)]
    public static StructEchoData EchoTestStructure(string echo1, string echo2)
    {
        var ws = new StructEchoData
        {
            Str1 = String.Concat("[EchoTestData] Retorno String[1]: ", echo1),
            Str2 = String.Concat("[EchoTestData] Retorno String[1]: ", echo2)
        };
        return ws;
    }

    /*
     * Method NOT static: it is used as COM (OLE) in VB6
     * In VB6 it returns very nice using with COM.
     * Note that returns the StructEchoData without problems...
     * Return: struct of StructEchoData 
     */
    [ExportDllAttribute.ExportDll("EchoTestStructureOle", CallingConvention.StdCall)]
    public StructEchoData EchoTestStructureOle(string echo1, string echo2)
    {
        var ws = new StructEchoData
        {
            Str1 = String.Concat("[EchoOle] Return StringOle[1]: ", echo1),
            Str2 = String.Concat("[EchoOle] Return StringOle[2]: ", echo2),
        };
        return ws;
    }

    /*
     * Method static: It works very nice using 'Declare in VB6'
     * Return: single string
     */
    [ExportDllAttribute.ExportDll("EchoS", CallingConvention.StdCall)]
    // [return: MarshalAs(UnmanagedType.LPStr)]
    public static string EchoS(string echo)
    {
        return "[TestClass::EchoS from TestLib.dll]" + echo;
    }


    /*
     * Method NOT static: it is used as COM (OLE) in VB6 
     * In VB6 it returns very nice
     * Return: single string
     */
    [ExportDllAttribute.ExportDll("EchoSOle", CallingConvention.StdCall)]
    // [return: MarshalAs(UnmanagedType.LPStr)]
    public string EchoSOle(string echo)
    {
        return "[TestClass::EchoS from TestLib.dll]: " + echo;
    }
  }
}

现在,在VB6中,我无法使用Declare进行测试或将TestLib.Dll注册为COM

在VB6中使用DECLARE:

Private Declare Function EchoS Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
     (ByVal echo As String) As String

Private Type StructEchoData
    Str1 As String
    Str2 As String
End Type

Private Declare Function EchoTestStructure Lib  "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
    (ByVal echo1 As String, ByVal echo2 As String) As StructEchoData

// ERROR - CRASH VB6
Private Sub EchoData_Click()
    Dim ret As StructEchoData
    ret = EchoTestStructure("echo1 Vb6", "echo2 vb6")
    TextBox.Text = ret.Str1
End Sub

// WORKS Fine, returns a string
Private Sub btRunEchoTestLib_Click()
    TextBox.Text = EchoS("{Run from VB6}")
End Sub

使用VB6与OLE:

1日。注册DLL:C:\ Windows \ Microsoft.NET \ Framework \ v4.0.30319 \ regsvcs.exe TestLib.dll /tlb:Test.tlb

第二。在项目中添加引用。程序运行,我得到一个字符串的响应,并在有结构时收到响应。

Private Sub Echo_Click()
    Dim ResStr As String
    Dim obj As TestLib.TestClass
    Set obj = New TestClass
    ResStr = obj.EchoSOle(" Test message")
    MsgBox "Msg Echo: " & ResStr, vbInformation, "ResStr"
    Beep
End Sub

Private Sub EchoDataOle_Click()
    Dim obj As TestLib.TestClass
    Set obj = New TestClass       
    // Here I define the struct and works fine!!
    Dim ret As TestLib.StructEchoData       
    ret = obj.EchoTestStructureOle("test msg1", "test msg2")       
    TextStr1.Text = ret.Str1
    TextStr2.Text = ret.Str2
    Debug.Print ret.Str1
    Debug.Print ret.Str2
   Beep
End Sub

因此,使用COM可以很好地包装StructEchoData,但是如果我想使用Declare并通过入口点获取访问权限,则无法正常工作。有人可以提出任何建议吗?

4 个答案:

答案 0 :(得分:3)

VB6 Declare Lib仅适用于未管理的DLL导出函数。由于C#是托管代码,因此C#不会将其作为非托管函数公开。从C#导出类的唯一受支持的方法是使用COM。因此,您无法使用Declare Lib从VB6访问C#方法。

有一个库应该从你的C#代码创建无人导出;罗伯特·吉塞克Unmanaged Exports。我个人从未使用它;我只是在Stack Overflow上看过它。

有一种支持的方法可以从.Net程序集中导出未管理的函数,并且使用C ++ / CLR,因为它允许混合托管代码和非托管代码。您可以创建一个C ++ / CLR包装器,用于导出调用C#DLL的非管理函数。这就是我要去的方式。

答案 1 :(得分:1)

您无法使用c#创建动态链接库。

但是,使用一点C ++,您可以通过利用CLR托管API为.Net dll创建一个引导程序。

CLR Hosting API

您可以使用名为“LoadPlugins”的方法在C ++中创建动态链接库。

编写LoadPlugins以加载CLR(或CLR的特定版本),然后使用反射加载一些.net DLL。

使用相同的C ++代码,您可以将C ++ dll中的.net方法公开为c ++中的导出本机函数,这些函数将与VB6的声明一起使用...

c ++中的每个函数都必须检查以确保加载了CLR,并且加载了被调用的.net代码,然后使用反射来调用它。

答案 2 :(得分:0)

感谢您的回复,

如果代码中有入口点,则C#仅适用于非托管DLL。使用语句'ExportDllAttribut'在代码中使用的声明生成了非管理代码必须使用的相应入口点。

问题是使用“数据结构”导出,这是我的问题。 我在没有问题的情况下使用了返回字符串或整数值的方法,比如本文中的EchoS。我在VB6中使用了这个例子(TestLib.DLL),它与VB6中的“Declare”一起正常工作:

Private Declare Function EchoS Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll"_
 (ByVal echo As String) As String

// WORKS Fine, returns a string
Private Sub btRunEchoTestLib_Click()
    TextBox.Text = EchoS("{Run from VB6}")
End Sub

我在开始编写C#代码时写了一个注释,但不清楚,对不起。解释了一下。编译完库后,我在“项目属性”,“构建事件”中使用以下命令:

"$(ProjectDir)libs\ExportDll.exe" "$(TargetPath)" /Debug

这个指令[ExportDllAttribute.ExportDll(“NameOfEntryPoint”)解析DLL(使用ilasm.exe和ildasm.exe)并编写exports指令来创建入口点,再次编译和生成DLL。我用了几年时间和工作细

如果我在DLL中应用dumpbin命令,结果是已发布的入口点,因此,可以使用非托管代码(如VB6),但在Vb6中使用正确的编组类型或语句。例如:

dumpbin.exe /exports TestLib.dll
   ordinal hint RVA      name
     2    0 0000A70E EchoC
     5    1 0000A73E EchoSOle
     3    2 0000A71E EchoTestStructure
     6    3 0000A74E EchoTestStructureOle

此代码使用EchoS(使用Declare)或EchoSOle(COM)方法进行测试,两种情况都很好。当DLL在app中用作OLE时,结构可以看作Type并且运行正常,但是,在VB6中使用declare,我得到错误MarshalDirectiveException只有Structure返回,单个类型如字符串C#或整数,我不有问题。

我认为问题是如何通过静态方法EchoTestStructure对结构进行编组或如何在VB6中声明。也许可能是VB6中使用的错误方式(我不是任何专家)或静态方法'EchoTestStructure'中的任何编组参数,这是真正的问题和帮助。

PS:如果我无法解决,我会看到回复中的链接尝试其他方法。

再次感谢,还有其他想法吗?

答案 3 :(得分:0)

它已经解决了,但从另一个方面来说......这可能不是最先进的技术,但它确实有效。

首先,需要更改struct中的Marshaling类型,从BStr更改为LPStr。

我不知道究竟是什么动机,因为一些Microsoft帮助和其他链接,他们说:“System.String:转换为终止于空引用或BSTR的字符串”。

如果我将“Hello”设置为结构中的Str1,我会在DLL中收到“效汬㉯映潲䉖⸶⸮”。如果我返回一个固定的字符串“Hello”表单DLL,VB6只显示第一个字符“H”,所以,我改为ANSI(LPStr)并解决它。使用OLE,BSTR工作正常,但本机DLL没有。代码更改为:

StructLayout(LayoutKind.Sequential)]
public struct StructEchoData
{
    [MarshalAs(UnmanagedType.BStr)]
    public string Str1;
    [MarshalAs(UnmanagedType.BStr)]
    public string Str2;
}

在方法'EchoTestStructureOle'声明中,更改为使用引用并传递结构intead的地址返回结构。在代码之前:

public StructEchoData EchoTestStructure(string echo1, string echo2)
{   
    // It Works only wtih OLE the return of the type StructEchoData 
    var ws = new StructEchoData
    {
        Str1 = String.Concat("[EchoTestData] Return from DLL String[1]: ", echo1),
        Str2 = String.Concat("[EchoTestData] Return from DLL String[2]: ", echo2)
    };
    return ws;
}

现在我使用interger通过引用返回状态(1 ok,-1 error)和参数结构类型,方法改为:

public static int EchoTestStructure(ref StructEchoData inOutString)
{   
    // used to test the return of values only, data 'in' not used
    var ws = new StructEchoData
    {
        Str1 = String.Concat("[EchoTestData] Return from DLL String[1]: ", inOutString.Str1),
        Str2 = String.Concat("[EchoTestData] Return from DLL String[2]: ", inOutString.Str2)
    };
    inOutString = ws;
    return 1;
}

作为参考,我可以从VB6接收值并返回VB6而没有任何问题。我认为必须有一种方法来返回一个结构,但是,这种方法解决了它。

在VB6方面:代码改为:

Private Type StructEchoData // Same type, do not change...
    Str1 As String
    Str2 As String
End Type

Private Declare Function EchoTestData Lib "C:\Temp\_run.dll\src.app.vb6\TestLib.dll" (ByRef strcData As StructEchoData) As Long

Private Sub ShowMessage_Click()
    Dim res As Long
    Dim strcData As StructEchoData

    strcData.Str1 = "Str1 from VB6..."
    strcData.Str2 = "Str2 from VB6..."
    res = EchoTestData(strcData)
    /*
     strcData.Str1 --> Data Received from DLL: 
       [EchoTestData] Return from DLL String[1]: Str1 from VB6...
     strcData.Str2 --> Data Received from DLL
       [EchoTestData] Return from DLL String[2]: Str2 from VB6...
    */
    ...
End Sub

感谢您的提示。如果您有任何建议,欢迎。