简体   繁体   中英

Passing Structures to unmanaged code from C# DLL to VB6

I have some customers that uses apps using VB6 and some others languages. 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.

When I register the DLL and test in VB6 (OLE), it works fine. 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).

The code is compile in C# to use in unmanaged code with OLE or by entry points> I had tested with 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

USING DECLARE in VB6:

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:

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

2nd. 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. Could anybody suggest anything, please?

The VB6 Declare Lib only works for unmanged DLL exported functions. C# does not expose it's functions as unmanged functions, since it's managed code. The only supported way to exporting classes from C# is to use COM. So you can't use Declare Lib to access C# methods from VB6.

There is a library that is supposed to create unmanged exports from your C# code; Robert Giesecke's Unmanaged Exports . 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. You could create a C++/CLR wrapper that exported unmanged functions that call your C# DLL. That is the way I would go.

You cannot create Dynamic Link Libraries with c#.

However, with a little bit of C++ you can create a bootstrapper for .Net dll's by leveraging the CLR hosting API.

CLR Hosting API

You can create a Dynamic Link Library in C++ with a method called something like "LoadPlugins".

Write LoadPlugins to load the CLR (or a specific version of the CLR), then use reflection to load some .net DLL's.

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...

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.

Thanks for replies,

The C# only will work with unmanaged DLL if there are Entry Points into the code. 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.

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. I used this example (TestLib.DLL) with VB6 and it works fine with "Declare" in VB6:

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. 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.

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. 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. 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.

I think thet the problem is how to the Structure was marshalling by the static method EchoTestStructure or how to was declared in 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.

PS: I will see the links in the replies to try another approaches if I can´t solve it.

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.

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".

If I set "Hello" into the Str1 in the Structure, I receive "效汬㉯映潲䉖⸶⸮" into the 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. Using OLE, the BSTR Works fine, but native DLL does not. 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. 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:

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. 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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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