TLeq TLeq - 6 months ago 53
Vb.net Question

calling winAPI functions in vb.net

I have to "translate" / up-date a program that was written in C about 20 years ago to VB.net due to its old SQL connection style that isn't compatible anymore. However, I have minimal experience with C and even less with the winAPI(which the C application uses)... I was wondering, can the same functions from the API be use in VB.net?

I have been able to add a declaration like this:

<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
Public Shared Function SetWindowText(hWnd As IntPtr, lpString As String) As Boolean
End Function


to get a window Handle as IntPtr ( in vb.net). However the C program uses functions such as:

BOOL NEAR GoModal( HINSTANCE hInstance, LPCSTR lpszTemplate,HWND hWnd, DLGPROC lpDlgProc, LPARAM lParam )


By importing the user32.dll I can't get the hWnd handle but I can get the IntPtr ( If I understood well, is a point to a window as an integer right? Kinda similar to hWnd --> I could be TOTALLY wrong, but this is what I understood)

As I go to the definition of each type (i.e: NEAR , HINSTANCE, LPCSTR, etc...) I try to find an equivalent call in VB but ... its useless. For instance, I was looking at NEAR and I guess it was to decide if two pointers were close in memory ? ( again might be wrong, it's just what I gathered).

Ultimately, my question is, would such call exist in vb.net / even be useful with the more modern framework and such?

Answer

Let's break down the parameters in the function call:

BOOL NEAR GoModal(HINSTANCE hInstance, 
                  LPCSTR lpszTemplate,
                  HWND hWnd, 
                  DLGPROC lpDlgProc, 
                  LPARAM lParam )
  • BOOL is a 32 bit integer. You can use the System.Boolean type in your code, but it needs the MarshalAsAttribute applied to it, specifying the type as UnmanagedType.Bool
  • NEAR specifies information to the compiler. When translating to .net, you can ignore it.
  • HINSTANCE is a Handle to an Instance. Translating to .net, handles are typically defined as IntPtr values. This doesn't mean that it's a pointer to memory. (ie- Marshal.Read can't be used.)
  • LPCSTR is a Long Pointer to a C style STRing. This is an ANSI string, so it means that the parameter needs the MarshalAsAttribute with UnmanagedType.LPStr. If this is was an out parameter, things would be more complicated, and you'd either need to pass a StringBuilder or use Marshal.AllocHGlobal/Marshal.AllocCoTaskMem to get unmanaged memory allocated.
  • HWND is a Handle to a Window. Like the HINSTANCE it is typically marshalled as an IntPtr
  • DLGPROC is a bit more complex, so I'll address it below.
  • LPARAM is a Long Parameter. The standard implementation that Microsoft used for this type when they created the Message class, is IntPtr. You should follow suit.

As I said above DLGPROC is more complex. It is actually a pointer (reference) to a function. Specifically one with the signature:

INT_PTR CALLBACK DialogProc(
  _In_ HWND   hwndDlg,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

We've already seen HWND and LPARAM, so let's take a quick look at the other things in this signature.

  • INT_PTR is an Integer Pointer. If that sounds familiar, it should. It's just an IntPtr.
  • CALLBACK is another bit of information that the compiler uses to determine how the function should be compiled, and how people using it should send their parameters to the function. By default the .net marshal handles this, so no additional configuration is needed.
  • WPARAM stands for Word Param. In this case, it's old terminology, and you will want to use IntPtr to receive the value.

To marshal a pointer to a DLGPROC, you will need to create a delegate with the appropriate signature:

Delegate Function DLGPROC(ByVal hWnd As IntPtr, 
                          ByVal uMsg As UInt32, 
                          ByVal wParam As IntPtr, 
                          ByVal lParam As IntPtr) As IntPtr

You then need to create a function somewhere in your code, with the same signature, to receive the call from the native code. Something like:

Private Function MyDialogProc(ByVal hWnd As IntPtr, 
                              ByVal uMsg As UInt32, 
                              ByVal wParam As IntPtr, 
                              ByVal lParam As IntPtr) As IntPtr
    Return IntPtr.Zero ' Put the appropriate code here.
End Function

You then can add a class member to hold that delegate:

Private DialogProc As DLGPROC = New DLGPROC(AddressOf Me.MyDialogProc)

After all of that, you're finally ready to import the GoModal function prototype. (YAY!) That looks like:

<DllImport("DLL_NAME")>
Private Shared Function GoModal(ByVal hInstance As IntPtr,
                                <MarshalAs(UnmanagedType.LPStr)> ByVal lpszTemplate As String,
                                ByVal hWnd As IntPtr,
                                ByVal lpDlgProc As IntPtr, ' More on this below
                                ByVal lParam As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

We can now call the function from our code:

Dim dialogProcedure As IntPtr = Marshal.GetFunctionPointerForDelegate(Me.DialogProc)
Dim result As Boolean = GoModal(IntPtr.Zero, 
                                "Template", 
                                Me.Handle, 
                                dialogProcedure, 
                                New IntPtr(100))

Two notes about the function call:

  • IntPtr.Zero is the same as NULL
  • New IntPtr(100) creates an IntPtr with the specified value. (Sometimes the LPARAM is just a number)

And if you made it this far, Congratulations! There may be a couple bugs in the code, but this should get you going. For other Windows API types, you can find a lot of information at pinvoke.net.

If you have a lot of interop to do, it would be easier to just add a C++/CLI dll to your project and call these functions natively. It's much, much easier, and you can define the functions exactly how you want them. In addition, C++/CLI works with any of the .net languages.