locked
Calling a Function from a Delphi DLL in VB.NET RRS feed

  • Question

  • Hello,

    I've designed a Windows Service in Visual Basic 2005 that needs to call some functions from a DLL written in Delphi 5.  If I try to add this DLL as a reference, I get this error:

    "A reference to 'C:\Projects\ServicePro\MDPUpdateService\MessageStubApplication\SPHost.dll' could not be added. Please make sure that the file is accessible, and that it is a valid assembly or COM component."

    I assume that it is just not possible to add this DLL as a reference to my VB2005 project, since the DLL was written in an old version of Delphi, correct?  This isn't a big deal, as I can just use the DllImport function from System.Runtime.InteropServices, but if it is possible, adding it as a reference would be preferred.

    My other question is, how would you be able to call a function from the DLL using DllImport, if the function you are trying to call takes a Delphi record data type as a parameter (if this is possible)?  If possible, would I need to pass each field in the record separately or would I be able to define a structure in my VB app that mirrors the record definition in the Delphi DLL and pass that? Or, would it be best if I just created another function in the Delphi DLL that took various string/integer/boolean/etc. parameters that I could call from VB that would then call the original function in Delphi?  I tried searching the internet for this specific question, but I couldn't find anything that looked useful.

    Here's an example of the layout of the Delphi record in question (just to get an idea of the data types being used, in case that matters at all).  There's going to be a few issues with some of the data types that would most likely hinder the ability to call this function from VB as well (specifically the ModuleTypes and TGLOverrideCallBack types), but I'll worry about that at another time, once I get an answer to my above questions.

    type
      ParamRec = record
        Field1	: ShortString;
        Field2	: double;
        Field3	: ShortString;
        Field4	: double;
        Field5	: ModuleTypes;
        Field6	: Integer;
        Field7	: ShortString;
        Field8	: Integer;
        Field9	: ShortString;
        Field10	: Boolean;
        Field11	: TDateTime;
        Field12	: TDateTime;
        Field13	: TGLOverrideCallBack;
        Field14	: ShortString;  
        Field15	: Boolean;    
        Field16	: double;  
        Field17	: boolean;  
        Field18	: Boolean;  
        Field19	: ShortString;  
        Field20	: Boolean;  
        Field21	: Double;
      end;

     Any insight into my questions will be greatly appreciated! 
    Thanks in advance!

    Tuesday, September 27, 2011 3:23 PM

Answers

  • It seems like the structure has other structure or object elements within it. That could make it somewhat problematic if not impossible to marshal.

    I don't know exactly what this DLL does, but is it possible re-write the functionality in Visual Basic .NET?


    Paul ~~~~ Microsoft MVP (Visual Basic)
    • Marked as answer by Mike Feng Tuesday, October 11, 2011 1:41 AM
    Wednesday, September 28, 2011 7:01 PM

All replies

  • Sounds like an unmanaged code library. Before DLLImport or Declare can be used you must be able to identify the function signatures. Also, the functions need to have been properly exported using the proper calling convention so that they can be accessed. Perhaps Delphi does that automatically but I can't recall for certain. I'm not sure which version of Delphi was used but the DLL must be 32 (or 64-bit if available).

    If you have used this DLL in prior versions of Visual Basic or VBA you may want to post the function signatures.


    Paul ~~~~ Microsoft MVP (Visual Basic)
    • Proposed as answer by Mike Feng Thursday, September 29, 2011 7:17 AM
    • Unproposed as answer by Mike Feng Tuesday, October 11, 2011 1:41 AM
    • Proposed as answer by horngsh Thursday, January 26, 2012 12:07 PM
    Tuesday, September 27, 2011 3:51 PM
  • "Consuming Unmanaged DLL Functions":

    http://msdn.microsoft.com/en-us/library/26thfadc.aspx


    Armin
    Tuesday, September 27, 2011 5:05 PM
  • Thanks for the replys Paul and Armin.  I'm going to look through the article you posted Armin and see if I can get it work by following that.

    We haven't used this DLL in prior versions of Visual Basic or VBA.  Here's the function signature from Delphi in case it helps:

    function AdjustInv( var Params : ParamRec ) : Boolean;

    As you can see, it takes the record type defined in my first post as a parameter.  I tried defining a Structure with all of the fields from the Delphi record and making that the parameter for the function in VB, but I whenever I call the function, I get the 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' error.  But like I said before, I'll look at the article Armin linked and see if that helps.  Here's how I've got the structure and function defined in VB:

     Public Structure ParamRec
        Dim Field1 As String
        Dim Field2 As Double
        Dim Field3 As String
        Dim Field4 As Double
        Dim Field5 As Object
        Dim Field6 As Integer
        Dim Field7 As String
        Dim Field8 As Integer
        Dim Field9 As String
        Dim Field10 As Boolean
        Dim Field11 As DateTime
        Dim Field12 As DateTime
        Dim Field13 As Object
        Dim Field14 As String
        Dim Field15 As Boolean
        Dim Field16 As Double
        Dim Field17 As Boolean
        Dim Field18 As Boolean
        Dim Field19 As String
        Dim Field20 As Boolean
        Dim Field21 As Double
    End Structure
    

    Declare Auto Function AdjustInvLib "C:\Program\InvFunctions.dll" (ByRef params As ParamRec) As Boolean
    


    • Edited by WaluigiCubed Wednesday, September 28, 2011 3:22 PM
    Wednesday, September 28, 2011 3:18 PM
  • The types of the structure members don't match. As I'm not a Delphi specialist, I don't know the equivalents in .Net. You will have to apply marshalling attributes to each member so that the runtime can create the appropriate structure internally to be passed to and from the Dll. The character set/encoding has to match, too. As well the memory layout of the structure.

    Googling....

    • ShortString: A string with 1 byte length prefix, One byte per character
    • ModuleTypes: What is it?
    • TDateTime: A double value expresing the number of days since 12/30/1899 12:00 am
    • TGLOverrideCallBack: ?

    Use Double instead of DateTime. To convert from .Net's DateTime to Double use TheDate.ToOADate.

    Use the MarshalAs attribute for the ShortString members:

    <MarshalAs(UnmanagedType.AnsiBStr)> Dim Field1 As String
    

    Import required:

    Imports System.Runtime.InteropServices
    

     

    So, next step is knowing the unknown types mentioned above.

     

    EDIT: Wait, I don't know how to specify a fixed size of 256 bytes for a length-prefixed String. There is the SizeConst member, but according to the documentation it works with ByValArray and ByValTStr only. Anyone knows how to pass it? Because, if it's not declarable As String, we would have to use a different approach.

     


    Armin
    Wednesday, September 28, 2011 6:25 PM
  • It seems like the structure has other structure or object elements within it. That could make it somewhat problematic if not impossible to marshal.

    I don't know exactly what this DLL does, but is it possible re-write the functionality in Visual Basic .NET?


    Paul ~~~~ Microsoft MVP (Visual Basic)
    • Marked as answer by Mike Feng Tuesday, October 11, 2011 1:41 AM
    Wednesday, September 28, 2011 7:01 PM
  • Thanks again Armin and Paul.

    ModuleTypes is an enumeration defined in our Delphi application, as follows:

    type  ModuleTypes = (SP, RMA, SC, WO);


    I've gone ahead and defined the same Enumeration in my VB Project as follows:

    Public Enum ModuleTypes
        SP
        RMA
        SC
        WO
    End Enum
    

    I'm not sure if enumerations would actually be allowed when making a call to an unmanaged DLL, but for now, I've defined Field5 As ModuleTypes, so I can at least get it tested.

    The biggest hinderance that I see is with theTGLOverrideCallBack type.  This is a procedure that has been defined as a data type, as follows (GLCODETYPES is another enumeration defined in the Delphi application, which I have also defined in the VB app):

    type  TGLOverrideCallBack = procedure( const GLCODETYPE   : GLCODETYPES; var GLACCOUNT : String ) of object;

    I'm doing research to see if this type of declaration is possible in Visual Basic (haven't found it yet, but I'm still looking), but I'm thinking that even if it is, it probably wouldn't be usable in a function call to an unmanaged DLL like I'd like it to.

    It certainly would be possible to re-write the functionality of this DLL in VB .NET if needed, but the functions we're trying to call are fairly complex, so being able to call the existing functions from the DLL is our preferred method.  But, if it's not going to work based on the definition of some of the variables in the Record data type, we'll have to go with the re-write or some other option.

    Thanks again! I appreciate the assistance!

    Wednesday, September 28, 2011 9:36 PM
  • Hi Waluigi,

    Welcome to the MSDN Forum.

    >>your VB.net declaration is :Declare Auto Function AdjustInvLib "C:\Program\InvFunctions.dll" (ByRef params As ParamRec) As Boolean
    >> This is the delphi declaration: function AdjustInv( var Params : ParamRec ) : Boolean;

    So I suggest you try to change the VB.net declaration to this one:

    Declare Function AdjustInvLib "C:\Program\InvFunctions.dll" (ByVal params As ParamRec) As Boolean

    If you will pass the result out with the paramter params, too, please change it like this:

    Declare Function AdjustInvLib "C:\Program\InvFunctions.dll" (<Out()>ByVal params As ParamRec) As Boolean

    I hope this will be helpful.

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, September 29, 2011 7:35 AM

  • The biggest hinderance that I see is with theTGLOverrideCallBack type.  This is a procedure that has been defined as a data type, as follows (GLCODETYPES is another enumeration defined in the Delphi application, which I have also defined in the VB app):
    type  TGLOverrideCallBack = procedure( const GLCODETYPE   : GLCODETYPES; var GLACCOUNT : String ) of object;

    I'm doing research to see if this type of declaration is possible in Visual Basic (haven't found it yet, but I'm still looking), but I'm thinking that even if it is, it probably wouldn't be usable in a function call to an unmanaged DLL like I'd like it to.

    I've done a little research, and it looks like creating a delegate sub would be the closest thing to defining a procedure as a data type in VB.NET (correct me if I'm wrong though).  I also saw that one of the Unmanaged types that you can use with the MarshallAs statement is FunctionPtr, so if I defined the sub and the variable in my structure like this, it could possibly work, correct (I'm still getting that same error from before when I try to test it, but I probably just need to play around with the marshalling a bit more until I can get it to work)?

    Public Delegate Sub TGLOverrideCallBack(ByVal CodeType As GLCodeType, ByVal GLAccount As String) 
    ...
     <MarshalAs(UnmanagedType.FunctionPtr)> Dim Field13 as TGLOverrideCallBack

     


    • Edited by WaluigiCubed Thursday, September 29, 2011 5:08 PM
    Thursday, September 29, 2011 5:07 PM
  • Hi Waluigi,

    How about my suggestion?

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, October 5, 2011 6:30 AM
  • Hi Mike,

    Thank you for your suggestion.  I tried changing the function header like you suggested, but I still get the 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' error.  Of course, this could be because I still don't quite have the type marshalling done correctly (I haven't had a chance to look at this the past few days).

    I'm still wondering if I'd be able to have enumerations and a delegate sub in my structure and still have success calling the Delphi function from VB.NET.  If anyone can confirm that these should work, I'd appreciate it.

    Thanks!

    Wednesday, October 5, 2011 2:18 PM
  • Hi Waluigi,

    Please change the platform from "any CPU" to "x86" and try again.

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, October 6, 2011 7:04 AM
  • Hi Mike,

    I changed the platform from 'Mixed Platforms' (which it was currently set as) to 'x86' and still the same error appears (the 'x86' platform did not exist initially, so I had to Add it to the list.  Perhaps there's some steps I have to take to configure this properly? I didn't notice any, but they could be somewhere else).

    Thanks!

    Thursday, October 6, 2011 4:58 PM
  • Hi Waluigi,

    I just review all the replies in this thread, it seems that you have asked two questions:

    1. How to call the API AdjustInvLib.

    2. How to pass a function to API TGLOverrideCallBack.

    Am I right?

    For the first question, my reply at Thursday, September 29, 2011 7:35 AM should be OK, if not, would you like to upload your DLL here, and I will try my best to find a way. Before you upload it, please make a Delphi function to call it to test it can work fine.

    About the second question, you can declare your delegate like this:

    Public Delegate Function TGLOverrideCallBack(ByVal CodeType As GLCodeType, ByVal GLAccount As String) As Object

    I hope this will be helpful.

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.


    • Edited by Mike Feng Friday, October 7, 2011 5:06 AM
    Friday, October 7, 2011 5:06 AM
  • Yes, those have been my two questions Mike.  I appreciate your assistance very much.

    I'm done some testing with calling some simpler functions that exist in the same Delphi DLL and have been able to get them to work just fine.  I'm still having issues whenever I have to call a function that takes a Record (Structure) or a ShortString datatype as one of the parameters or as the return type though (I always get the 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.' error).

    I've tried all of your suggestions, but I still can't get it to work right for the functions that need Record or ShortString parameters (I've tried every possible MarshalAs type for the ShortStrings too and even tried using a Byte Array in its place like I saw suggested somewhere, and still, nothing works).  I've done some searching online, and I've noticed that ShortStrings in particular seem to be very difficult to get to marshall as a .NET datatype, and a lot of things I've read have suggested that they not be used.  Has anyone ever successfully gotten a .NET application to work properly with the Delphi ShortString data type?

    Also, as far as structures go, here's a simpler structure that I tried to define in my VB.NET class (the Delphi definition is first, followed by the VB definition):

    Delphi:

    RShipToRec = record
        CustID:string;
        ShipToID:string;
        ShipToName:string;
        Address:string;
        City:string;
        State:string;
        Zip:string;
        Province:string;
        Country:string;
        Phone:string;
        Ext:string;
        EMail:string;
        Fax:string;
        Contact:string;
        UseTaxCodes_B:boolean;
        TaxCode1:string;
        TaxCode2:string;
        TaxCode3:string;
        TaxSchedule:string;
        SalesPerson1:string;
        ShipViaCode:string;
        Zone:string;
      end;

     VB.NET:

    <StructLayout(LayoutKind.Sequential)> Public Structure RShipToRec
        Dim CustID As String
        Dim ShipToID As String
        Dim ShipToName As String
        Dim Address As String
        Dim City As String
        Dim State As String
        Dim Zip As String
        Dim Province As String
        Dim Country As String
        Dim Phone As String
        Dim Ext As String
        Dim EMail As String
        Dim Fax As String
        Dim Contact As String
        Dim UseTaxCodes_B As Boolean
        Dim TaxCode1 As String
        Dim TaxCode2 As String
        Dim TaxCode3 As String
        Dim TaxSchedule As String
        Dim SalesPerson1 As String
        Dim ShipViaCode As String
        Dim Zone As String
    End Structure
    

    This structure is just using a bunch of String data types and one Boolean.  I've tried defining the items in the structure in VB.NET with various Marshaling attributes, but it doesn't seem to make a difference one way or the other (the same thing keeps happening).  This is the function I'm trying to call that takes this structure as one of its parameters (again, the Delphi definition is on top).

    Delphi:

    procedure HostFillShipToRec(var ShipToRec:RShipToRec; const CustID,ShipToID:string);

     VB.NET:

    Declare Sub HostFillShipToRec Lib "C:\Program\InvFunctions.dll" (<Out()> ByVal ShipToRec As RShipToRec, ByVal CustID As String, ByVal ShipToID As String)


    Again, I've tried a few different variations of the sub declaration in VB.NET, but I get the same result with each one.

    So, at this point, I just need to know if I'm doing anything wrong in defining my structure, and how I can appropriately marshal the ShortString types (if its even possible), and then I should be good, since there are a number of functions/procedures that I can call successfully from this DLL in my VB.NET app.

    Friday, October 7, 2011 6:07 PM
  • Hi Waluig,

    The type isn't shortString as you have mentioned in your delphi code. Anyway, there is no corresponding type in VB.NET. So you can try char array in both sides.

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, October 10, 2011 10:01 AM
  • Hi Mike,

    I tried your Char() array suggestion for my original structure that I posted and it still isn't working (I know that the second structure I posted didn't have the ShortStrings, but I posted it to show that even that structure wouldn't work).  It seems like this just isn't going to work the way we want it to, so we may have to rewrite some of the code in .NET.

    Thanks!

    Monday, October 10, 2011 2:28 PM
  • Hi WaluigiCubed,

    You are welcome.

    It seems that is the last way. So good luck.

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, October 11, 2011 1:40 AM
  • For your inquiry as follows:

     

    The biggest hinderance that I see is with theTGLOverrideCallBack type.  This is a procedure that has been defined as a data type, as follows (GLCODETYPES is another enumeration defined in the Delphi application, which I have also defined in the VB app):

    type  TGLOverrideCallBack = procedure( const GLCODETYPE   : GLCODETYPES; var GLACCOUNT : String ) of object;


     

    I doubt if it is possible using Delegate in VB.Net to mimic the TGLOverrideCallBack, maybe you can give it a try.

     


    My blog: http://soho-hsh.blogspot.com
    Thursday, January 26, 2012 12:18 PM
  • As for the condition " 'Attempted to read or write protected memory", in my coding experience, this might be able to be avoided using Win32 api GlobalLock() and GlobalUnlock() parily right before and after your call to the dll function.

    Hopefully it helps.


    My blog: http://soho-hsh.blogspot.com
    Thursday, January 26, 2012 12:24 PM
  • IMHO, you mentioned ShortStrng data type in Delphi and I think you might have to consider the character set and memory layout of ShortString in Delphi. That is, IMO, WE COULD USE Byte Array in VB.NET to mimics any data type in other programming languages. 

    There are two points that must be taken into account:

    1. Data type memory layout and if it is fixed or relocatable.

    2. Character Set. (Unicode char takes two bytes, but SBCS and DBCS are not really.)

    Hopefully it helps.


    My blog: http://soho-hsh.blogspot.com
    • Edited by horngsh Thursday, January 26, 2012 12:37 PM
    Thursday, January 26, 2012 12:35 PM