简体   繁体   English

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

[英]Passing Structures to unmanaged code from C# DLL to VB6

I have some customers that uses apps using VB6 and some others languages. 我有一些使用VB6和其他语言的应用程序的客户。 The code works fine using OLE (COM), but customers prefers to use native DLL to avoid to register the libraries and deploy them in the field. 该代码可以使用OLE(COM)正常运行,但是客户更喜欢使用本机DLL以避免注册库并将其部署在现场。

When I register the DLL and test in VB6 (OLE), it works fine. 当我注册DLL并在VB6(OLE)中进行测试时,它工作正常。 When I call a method that return a Strutc, it works fine with OLE, but, if I access in VB6 using Declare, I got fatal error in method that should return the same kind of struct (method 'EchoTestData' see bellow). 当我调用返回Strutc的方法时,它可以很好地与OLE配合使用,但是,如果我使用Declare在VB6中进行访问,则该方法应返回致命错误,该错误应返回相同类型的结构(方法“ EchoTestData”,请参见下面的内容)。

The code is compile in C# to use in unmanaged code with OLE or by entry points> I had tested with VB6. 该代码在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;
    }
  }
}

Now, in VB6 I cant test using Declare or register the TestLib.Dll as COM 现在,在VB6中,我无法使用Declare进行测试或将TestLib.Dll注册为COM

USING DECLARE in VB6: 在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

And using VB6 wiht OLE: 并使用带有OLE的VB6:

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

2nd. 2号 Add the reference in project. 在项目中添加参考。 The program runs and I got the response with one string and receive the response when has a structure too. 程序运行后,我得到了一个字符串的响应,并且在具有结构时也收到了响应。

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

So, the StructEchoData is wrapped fine using COM, but if I want to use Declare and got the access by entry point, not work. 因此,StructEchoData使用COM可以很好地包装,但是如果我想使用Declare并按入口点进行访问,则无法正常工作。 Could anybody suggest anything, please? 有人可以建议什么吗?

The VB6 Declare Lib only works for unmanged DLL exported functions. VB6 Declare Lib仅适用于未管理的DLL导出功能。 C# does not expose it's functions as unmanged functions, since it's managed code. C#不会将其功能公开为非托管功能,因为它是托管代码。 The only supported way to exporting classes from C# is to use COM. 从C#导出类的唯一受支持的方法是使用COM。 So you can't use Declare Lib to access C# methods from VB6. 因此,您不能使用Declare Lib从VB6访问C#方法。

There is a library that is supposed to create unmanged exports from your C# code; 应该有一个库可以从您的C#代码创建未管理的导出。 Robert Giesecke's Unmanaged Exports . 罗伯特·吉塞克(Robert Giesecke)的不受管制的出口 I've personally never used it; 我个人从未使用过它。 I've only seen it mentioned on Stack Overflow. 我只在堆栈溢出中看到它。

There is a supported way to export unmanged functions from a .Net assembly and that is using C++/CLR since it allows the mixing of managed and unmanged code. 有一种受支持的方法可以从.Net程序集中导出非管理函数,该方法使用C ++ / CLR,因为它允许混合托管和非管理代码。 You could create a C++/CLR wrapper that exported unmanged functions that call your C# DLL. 您可以创建一个C ++ / CLR包装器,该包装器可以导出调用C#DLL的未管理函数。 That is the way I would go. 那就是我要走的路。

You cannot create Dynamic Link Libraries with c#. 您不能使用c#创建动态链接库。

However, with a little bit of C++ you can create a bootstrapper for .Net dll's by leveraging the CLR hosting API. 但是,使用少量C ++,您可以利用CLR托管API为.Net dll创建引导程序。

CLR Hosting API CLR托管API

You can create a Dynamic Link Library in C++ with a method called something like "LoadPlugins". 您可以使用称为“ LoadPlugins”之类的方法在C ++中创建动态链接库。

Write LoadPlugins to load the CLR (or a specific version of the CLR), then use reflection to load some .net DLL's. 编写LoadPlugins来加载CLR(或CLR的特定版本),然后使用反射来加载一些.net DLL。

Also with the same C++ code, you can expose .net methods in the C++ dll as exported native functions in c++ that will work with VB6's declare... 同样使用相同的C ++代码,您可以将C ++ dll中的.net方法公开为c ++中的导出本机函数,这些函数可以与VB6的声明一起使用...

Each function in c++ would have to check to make sure the CLR is loaded, and that the .net code being called is loaded, then use reflection to call it. c ++中的每个函数都必须检查以确保已加载CLR,并且已加载了正在调用的.net代码,然后使用反射对其进行了调用。

Thanks for replies, 感谢您的答复,

The C# only will work with unmanaged DLL if there are Entry Points into the code. 如果代码中有入口点,则C#仅适用于非托管DLL。 The declarations that were used into the code with the statement 'ExportDllAttribut' generate the respectives Entry Points that are necessary to be used by unmanagement code. 语句'ExportDllAttribut'在代码中使用的声明会生成非管理代码必须使用的各个入口点。

The problem is to use export with "Data Structure", that is my question. 问题是将导出与“数据结构”一起使用,这是我的问题。 I had used without problems the methods that return string or integer values, like EchoS in this post. 我已经毫无问题地使用了返回字符串或整数值的方法,例如本文中的EchoS。 I used this example (TestLib.DLL) with VB6 and it works fine with "Declare" in VB6: 我在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

I wrote a note at the begining the C# code, but could not be clear, sorry. 我在C#代码的开头写了一个便条,但是不清楚,抱歉。 Explained a bit more. 解释了更多。 After I compile the library, I use in "Project properties", "Build events" the following command: 编译库后,在“项目属性”,“构建事件”中使用以下命令:

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

This directive [ExportDllAttribute.ExportDll("NameOfEntryPoint"] disasembly the DLL (using ilasm.exe and ildasm.exe) and write the exports directives to create Entry Points, compiling and generating the DLL again. I used few years and works fine. 该指令[ExportDllAttribute.ExportDll(“ NameOfEntryPoint”]取消了DLL(使用ilasm.exe和ildasm.exe)并编写了export指令来创建Entry Point,再次编译并生成DLL。

If I apply the dumpbin command in DLL, the results are the Entry Points published, so, it is possible to use with unmanaged code like VB6, but using the correct Marshalling type or statement in Vb6. 如果我在DLL中应用dumpbin命令,则结果是发布的入口点,因此,可以与非托管代码(如VB6)一起使用,但可以在Vb6中使用正确的编组类型或语句。 Example: 例:

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

This code was tested using the method EchoS (with Declare) or EchoSOle (COM) and in both cases are fine. 使用方法EchoS(带有Delare)或EchoSOle(COM)测试了此代码,在两种情况下都可以。 When the DLL is used as OLE in app, the structure can be see as Type and the run fine, but, in VB6 with declare, I got error MarshalDirectiveException only with Structure returns, singles types like string C# or integer, I don't have problem. 当DLL在应用程序中用作OLE时,该结构可以视为Type且运行良好,但是在带有声明的VB6中,我仅在返回Structure的情况下遇到了MarshalDirectiveException错误,像字符串C#或整数之类的单打类型,我没有有问题。

I think thet the problem is how to the Structure was marshalling by the static method EchoTestStructure or how to was declared in VB6. 我认为问题是如何通过静态方法EchoTestStructure来编排Structure,或者如何在VB6中声明它。 Maybe could be a wrong way used in VB6 (that I am not any expert) or any marshalling parameter in Static Method 'EchoTestStructure', that is the real question and help. 可能是VB6中使用的错误方法(我不是专家)或静态方法“ EchoTestStructure”中的任何编组参数,这才是真正的问题和帮助。

PS: I will see the links in the replies to try another approaches if I can´t solve it. PS:如果无法解决,我会在答复中看到链接以尝试其他方法。

Thanks again, Any other idea? 再次感谢,还有其他想法吗?

It was Solved, but by another way... this can be not the state of the art, but it works. 它已解决,但是通过另一种方式...这可能不是最新的技术,但它可以工作。

First thing, it was needed to change the Marshaling type in struct, from BStr to LPStr. 首先,需要将结构中的封送处理类型从BStr更改为LPStr。

I don't know what was exactly the motive because some Microsoft helps and others links, they said: "System.String : Converts to a string terminating in a null reference or to a BSTR". 我不知道动机是什么,因为有些Microsoft帮助和其他链接,他们说:“ System.String:转换为以空引用结尾的字符串或BSTR。”

If I set "Hello" into the Str1 in the Structure, I receive "效汬㉯映潲䉖⸶⸮" into the DLL. 如果在结构的Str1中设置“ Hello”,则会在DLL中收到“效汬㉯映潲䉖⸶⸮”。 If I return a fixed string "Hello" form DLL, the VB6 shows only the first char "H", so, I change to ANSI (LPStr) and solve it. 如果我从DLL返回固定的字符串“ Hello”,则VB6仅显示第一个字符“ H”,因此,我更改为ANSI(LPStr)并解决了它。 Using OLE, the BSTR Works fine, but native DLL does not. 使用OLE,BSTR可以正常工作,但本机DLL不能正常工作。 The code was changed to: 该代码已更改为:

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

In the method 'EchoTestStructureOle' declaration, it was change to use reference and passing the address of structure intead return a structure. 在方法“ EchoTestStructureOle”的声明中,更改为使用引用并将传递结构的地址并返回一个结构。 Before the code was: 在代码之前是:

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;
}

And now I am using interger to return the status (1 ok, -1 error) and the parameter structure type by reference, the method was change to: 现在我正在使用interger通过引用返回状态(1 OK,-1错误)和参数结构类型,方法已更改为:

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;
}

With reference, I can receive the values from VB6 and return to VB6 without no problems. 通过参考,我可以毫无问题地从VB6接收值并返回到VB6。 I think that must have a way to return a structure, but, this approach solve it for this time. 我认为必须有一种返回结构的方法,但是,这种方法现在解决了。

In VB6 side: the code was changed to: 在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

Thanks for the tips for while. 感谢您的提示。 If you have any suggest, it will be welcome. 如果您有任何建议,将非常欢迎。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM