SendMessage - Passing a string from VB6 to VB.NET - vb.net

Good afternoon,
I am attempting to use SendMessage to pass a string from a VB6 EXE, to a .NET 2013 EXE. I know that the message is getting in to the .NET EXE, because I'm able to set a breakpoint on it and it comes up when I call SendMessage from the VB6 EXE. The problem I am having is retrieving the string.
This is how I am attempting to do it:
VB6 Code:
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, Source As Any, ByVal bytes As Long)
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hwnd As Long, ByVal msg As Long, wParam As Long, lParam As Any) As Long
Private Const APPVIEWER_OPEN = &H400
Private Sub Command1_Click()
Dim hwndAppViewer As Long
Dim bytBuffer(1 To 255) As Byte
Dim sParams As String
Dim lStringAddress As Long
hwndAppViewer = FindWindow(vbNullString, "DotNetReceiver")
If hwndAppViewer > 0 Then
sParams = "STRINGDATA"
CopyMemory bytBuffer(1), sParams, Len(sParams)
lStringAddress = VarPtr(bytBuffer(1))
SendMessage hwndAppViewer, APPVIEWER_OPEN, Me.hwnd, lStringAddress
End If
End Sub
Here is the .NET code:
Imports System.Runtime.InteropServices
Public Class Form1
Protected Overrides Sub WndProc(ByRef m As Message)
Dim sPolicyInformation As String
If m.Msg = &H400 Then
sPolicyInformation = Marshal.PtrToStringAnsi(m.LParam)
Else
MyBase.WndProc(m)
End If
End Sub
End Class
The problem comes when I try and retrieve the string. I am getting a blank string. I noticed that the number in the VB6 lStringAddress and the number in .NET m.lParam are completely different, so I must be missing something about how I'm passing the address through lParam.
Any ideas what I might be missing?
Thank you.

You are sending an ANSI string to VB.NET. VB6 was designed for all MS's OSs and 9x wasn't unicode. So all strings passed to API calls will be converted to ANSI. Windows will convert that ANSI string to unicode for the VB.NET program when it recieves it.
Use the sendmessagew function and send the first element of a byte array that's null terminated.
Dim MyStr() as byte
MyStr = "cat" & chrw(0)
The pass only the first element to SendMessageW ie MyStr(0). Windows API uses null terminated C strings. COM and VB6 use BStr (a size header and a non null terminated string).
When passing strings by ref you pass the address of the header. When passing by value you pass the address of the first character (making it a c string if you tack a null on the end).

Related

VB .Net - Send String to Another Application

From VB .Net, I'm trying to send a string to a textbox of another application but I cannot make it work. I'm able to get the handle and even set the focus to the textbox but my SendMessage function doesn't seem to be correct as I get the error message "SendMessage' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature."
Here is my code:
Module Module1
Private Const WM_SETTEXT As Int32 = &HC
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long
Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Private Declare Auto Function FindWindowEx Lib "user32" (ByVal hwndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
Private Declare Auto Function SendMessage Lib "user32" (ByVal hwnd As IntPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As IntPtr
Sub Main()
Dim AppHwnd As IntPtr = FindWindow(vbNullString, "Test Application"
Dim WinHwnd1 As IntPtr = FindWindowEx(AppHwnd, 0&, "SWT_Window0", vbNullString)
Dim WinHwnd2 As IntPtr = FindWindowEx(WinHwnd1, 0&, "SWT_Window0", vbNullString)
Dim WinHwnd3 As IntPtr = FindWindowEx(WinHwnd2, 0&, "SWT_Window0", vbNullString)
Dim TextBoxHwnd1 As IntPtr = FindWindowEx(WinHwnd3, 0&, "Edit", vbNullString)
Dim TextBoxHwnd2 As IntPtr = FindWindowEx(WinHwnd3, TextBoxHwnd1, "Edit", vbNullString)
MsgBox(TextBoxHwnd2)
SetForegroundWindow(TextBoxHwnd2)
SendMessage(TextBoxHwnd2, WM_SETTEXT, 0&, "text")
End Sub
End Module
The line "MsgBox(TextBoxHwnd2)" returns the handle number I found using Window Detective so I'm assuming the code is correct up to this point. Also, I tested "SetForegroundWindow(TextBoxHwnd1)" and the cursor is on the first textbox whereas "SetForegroundWindow(TextBoxHwnd1) sets the cursor on the second textbox.
Your declaration looks like it might have been migrated from legacy VB code, where Long was a 32-bit integer and Integer was only 16-bit (dating to legacy VB's origination in 16-bit Windows). The length of the data types changed in .NET which was created after the shift to universal 32-bit Windows, so Integer is 32-bit and Long is 64-bit.
Moreover, the Windows API itself needed to be updated for 64-bit Windows. SendMessage is one of the functions that has some parameters that are longer in 64-bit because they are expected to hold pointers.
I checked on the native header file declaration of SendMessage and found that:
The parameter Msg is declared as UINT which would correspond to Integer in VB. This needs to be changed regardless of whether you have a 32-bit or 64-bit build.
The parameter wParam is declared as UINT_PTR so it should in fact be Long for a 64-bit build, but Integer for a 32-bit build; if it's possible to declare it as IntPtr and then pass 0 to it, that would probably be best, because it should automatically adjust the length depending on whether you're building for 32-bit or 64-bit.

How to get non-ansi string from external application displayed correctly?

I want to obtain the text from a chat room. And to do that I use the Marshal class to get the string pointer and convert back to a string using Marshal.PtrToStringUni. My target string is written in Vietnamese (UTF-8, codepage Windows-1258). And I could not get it displayed correctly (result shows weird Chinese and symbols). What should I change in the code below to get it right?. Thank you ~
'API Declaration
Declare Auto Function SendMessage Lib "user32.dll"(ByVal hWnd As IntPtr, ByVal msg As Integer, _
ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
'Sub to get chat room text
' I already got the handle of the chat text area (RoomText)
Private Sub GetText()
'get text length
length = SendMessage(RoomText, WM_GETTEXTLENGTH, 0, 0) + 1
'Alloc memory for the buffer that receives the text
Dim Handle As IntPtr = Marshal.AllocHGlobal(length)
'send WM_GETTEXT message to the chat window
Dim NumText As Integer = SendMessage(Hwnd, WM_GETTEXT, length, Handle)
'copy the characters from the unmanaged memory to a managed string
Dim Text As String = Marshal.PtrToStringUni(Handle)
'Display the string using a textbox
TextBox1.AppendText(Text)
End Sub
Here is the result of the above code:
P/S: In other efforts, I tried the SendMessageW, and SendMessageA functions, and only SendMessageA results the string with mix of English and question mark (something like ng?y de?p...). SendMessageW returns weird characters.
'API Declaration
Public Declare Function SendMessage Lib "user32.dll" Alias "SendMessageW" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Int32
Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
'Sub to get chat room text
' I already got the handle of the chat text area (RoomText)
Private Sub GetText()
'get text length
length = SendMessage(RoomText, WM_GETTEXTLENGTH, 0, 0) + 1
'Alloc memory for the buffer that receives the text
'Be noted that Length is the string character count, but Marshal.AllocHGlobal need byte count.
'in VB.net string, a character use 2 byte so I put *2
Dim Handle As IntPtr = Marshal.AllocHGlobal(length*2)
'send WM_GETTEXT message to the chat window
Dim NumText As Integer = SendMessage(Hwnd, WM_GETTEXT, length, Handle)
'copy the characters from the unmanaged memory to a managed string
Dim Text As String = Marshal.PtrToStringUni(Handle)
'Display the string using a textbox
TextBox1.AppendText(Text)
End Sub

Is there a way to call: Protected Override void WndProc(ref Message m) in VB6?

I am implementing an application in C#.net that is passing a message to an VB6 application.
For testing I created 2 applications both in C#.NET:- One sends message and 2nd receives the message.
The receiving application (C#.NET) makes use of the following function to catch the message:-
Protected Override void WndProc(ref Message m)
I now need to implement this receiver app in VB6.. How do we implement Protected Override void WndProc(ref Message m) in VB6? Or is there any other alternative?
Overriding the default windows procedure is possible in VB6 and is called Subclassing.
In a module:
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const GWL_WNDPROC As Long = (-4)
Private originalWindowProcAddr As Long
Public Sub subclassForm(hwnd As Long)
'// replace existing windows procedure save its address
originalWindowProcAddr = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf NewWindowProc)
End Sub
Public Function NewWindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Debug.Print "received message for:", hwnd, "message:", uMsg
'// forward message to default
NewWindowProc = CallWindowProc(originalWindowProcAddr, hwnd, uMsg, wParam, lParam)
End Function
Public Sub unSubclassForm(hwnd As Long)
'// must tidy up by restoring the original window proc
SetWindowLong hwnd, GWL_WNDPROC, originalWindowProcAddr
End Sub
In the form
Private Sub Form_Load()
subclassForm Me.hwnd
End Sub
Private Sub form_Unload(Cancel As Integer)
unSubclassForm Me.hwnd
End Sub
Failing to call unSubclassForm will crash the VB IDE, as will breaking into debug mode.
I have followed this method:-
http://support.microsoft.com/kb/176058/en-us
Alternate link: https://web.archive.org/web/20150118054920/http://support.microsoft.com:80/kb/176058
How To Pass String Data Between Applications Using SendMessage
SUMMARY
There are many ways to achieve inter-process communication using Visual Basic. Unless you establish an OLE Automation client server relationship, string data is difficult to handle cleanly. The main reason is that 32-bit applications run in a separate address space, so the address of a string in one application is not meaningful to another application in a different address space. Using the SendMessage() API function to pass a WM_COPYDATA message avoids this problem.
This article demonstrates how to pass string data from one application to another by using the SendMessage API function with the WM_COPYDATA message.
WARNING: One or more of the following functions are discussed in this article; VarPtr, VarPtrArray, VarPtrStringArray, StrPtr, ObjPtr. These functions are not supported by Microsoft Technical Support. They are not documented in the Visual Basic documentation and are provided in this Knowledge Base article "as is." Microsoft does not guarantee that they will be available in future releases of Visual Basic.
Visual Basic does not support pointers and castings in the manner of Visual C++. In order to pass string data from one Visual Basic application to another, the Unicode string must be converted to ASCII prior to passing it to the other application. The other application must then convert the ASCII string back to Unicode.
The following summarizes how to pass string data from one application to another.
Step-by-Step Example
Convert the string to a byte array using the CopyMemory() API.
Obtain the address of the byte array using the VarPtr() intrinsic function and copy the address and length of the byte array into a COPYDATASTRUCT structure.
Pass the COPYDATASTRUCT to another application using the WM_COPYDATA message, setting up the other application to receive the message.
Unpack the structure on the target system using CopyMemory(), and convert the byte array back to a string using the StrConv() intrinsic function.
The next section shows you how to create a sample program that demonstrates passing string data from one application to another.
Steps to Create the Sample
To create this sample, you create two separate projects; a sending project and a target project.
Create the target application:
Start a new Standard EXE project in Visual Basic. Form1 is created by default. This project will be your target application.
Add a Label control to Form1.
Copy the following code to the Code window of Form1:
Private Sub Form_Load()
gHW = Me.hWnd
Hook
Me.Caption = "Target"
Me.Show
Label1.Caption = Hex$(gHW)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Unhook
End Sub
Add a module to the project and paste the following code in the Module1 code window:
Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Public Const GWL_WNDPROC = (-4)
Public Const WM_COPYDATA = &H4A
Global lpPrevWndProc As Long
Global gHW As Long
'Copies a block of memory from one location to another.
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As _
Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As _
Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As _
Long) As Long
Public Sub Hook()
lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
AddressOf WindowProc)
Debug.Print lpPrevWndProc
End Sub
Public Sub Unhook()
Dim temp As Long
temp = SetWindowLong(gHW, GWL_WNDPROC, lpPrevWndProc)
End Sub
Function WindowProc(ByVal hw As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
If uMsg = WM_COPYDATA Then
Call mySub(lParam)
End If
WindowProc = CallWindowProc(lpPrevWndProc, hw, uMsg, wParam, _
lParam)
End Function
Sub mySub(lParam As Long)
Dim cds As COPYDATASTRUCT
Dim buf(1 To 255) As Byte
Call CopyMemory(cds, ByVal lParam, Len(cds))
Select Case cds.dwData
Case 1
Debug.Print "got a 1"
Case 2
Debug.Print "got a 2"
Case 3
Call CopyMemory(buf(1), ByVal cds.lpData, cds.cbData)
a$ = StrConv(buf, vbUnicode)
a$ = Left$(a$, InStr(1, a$, Chr$(0)) - 1)
Form1.Print a$
End Select
End Sub
Save the project and minimize the Visual Basic IDE.
Create the Sending Application
Start a second instance of the Visual Basic IDE and create a new Standard EXE project in Visual Basic. Form1 is created by default.
Add a CommandButton to Form1.
Copy the following code to the Code window of Form1:
Private Type COPYDATASTRUCT
dwData As Long
cbData As Long
lpData As Long
End Type
Private Const WM_COPYDATA = &H4A
Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName _
As String) As Long
Private Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal _
wParam As Long, lParam As Any) As Long
'Copies a block of memory from one location to another.
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long)
Private Sub Command1_Click()
Dim cds As COPYDATASTRUCT
Dim ThWnd As Long
Dim buf(1 To 255) As Byte
' Get the hWnd of the target application
ThWnd = FindWindow(vbNullString, "Target")
a$ = "It Works!"
' Copy the string into a byte array, converting it to ASCII
Call CopyMemory(buf(1), ByVal a$, Len(a$))
cds.dwData = 3
cds.cbData = Len(a$) + 1
cds.lpData = VarPtr(buf(1))
i = SendMessage(ThWnd, WM_COPYDATA, Me.hwnd, cds)
End Sub
Private Sub Form_Load()
' This gives you visibility that the target app is running
' and you are pointing to the correct hWnd
Me.Caption = Hex$(FindWindow(vbNullString, "Target"))
End Sub
Save the project.
Running the Sample
Restore the target application and press the F5 key to run the project. Note that the value of the hWnd displayed in the label.
Restore the sending application and press the F5 key to run the project. Verify that the hWnd in the form caption matches the hWnd in the label on the target application. Click the CommandButton and the text message should be displayed on the form of the target application.

Program throws errors at random (vb.net)

I am using this sample as a basis for a program I'm making. After approximately 618 keystrokes, the program throws this error:
CallbackOnCollectedDelegate was detected
Message: A callback was made on a garbage collected delegate of type 'KeyLogger!KeyLogger.CallBackFunction+DelegateCallBack::Invoke'. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.
The error is thrown most times the application is run, but not every time and not at the same keystroke count. From the error message, I think it sounds like the garbage collector is collecting the delegate, how can I prevent this?
The program I made is essentially a modified version of that vb.net project, but it does not actually store the keystrokes.
Thank you for your help!
Code within CallBack.vb:
Option Strict Off
Option Explicit On
Module CallBackFunction
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'CallBack function
Delegate Sub DelegateCallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
Sub CallBack(ByVal msg As Integer, ByVal wParam As Integer, ByVal lParam As Integer)
'here we track only WM_CHAR and WM_KEYDOWN
If msg = WM_KEYDOWN Then
...
End If
End Sub
End Module
Code in Declarations.vb:
Option Strict Off
Option Explicit On
Module Declarations
'******************************************************************************************
' Sample for retrieving keystrokes by use of the "kbLog32.dll"
' (c) 2004 by Nadeem Afanah.
'******************************************************************************************
'******************************************************************************************
'DLL declarations
Public Declare Function StartLog Lib "kbLog32" (ByVal hWnd As Integer, ByVal lpFuncAddress As DelegateCallBack) As Integer
Public Declare Sub EndLog Lib "kbLog32" ()
'----------------------------------------------------------------------------------------
Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
Declare Function FindWindow Lib "user32" Alias "FindWindowA"(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA"(ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer
'****************************************************************************************
' Keyboard messages
Public Const WM_KEYUP As Short = &H101s
Public Const WM_KEYDOWN As Short = &H100s
Public Const WM_CHAR As Short = &H102s
Public Const WM_SYSKEYDOWN As Short = &H104s
Public Const WM_SYSKEYUP As Short = &H105s
'SetWindowPos messages
Public Const SWP_NOSIZE As Short = &H1s
Public Const SWP_NOMOVE As Short = &H2s
Public Const HWND_TOPMOST As Short = -1
Public Const SWP_SHOWWINDOW As Short = &H40s
'******************************************************************************************
End Module
Look at the code in Form1.vb where it does this:
StartLog(nhWnd_text, AddressOf CallBack)
This is where it's saying, take the location of the Callback function an use it to hanle messages I receive regarding keyboard events.
Try something like this:
Friend Class Form1
Inherits System.Windows.Forms.Form
''Add this ----------------------------
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private DelSub as New DelegateCallBack(AdressOf CallBack)
''-------------------------------------
''In the sub Sub Command1_Click
''Change this -------------------------
StartLog(nhWnd_text, AddressOf CallBack)
''To this -----------------------------
StartLog(nhWnd_text, DelSub)
''-------------------------------------
End Class
What we're doing here is creating a local "delegate sub" (think of it as a variable which points at a sub). We're pointing this at the Callback sub. We're then using this delegate sub instead of passing in a reference directly to the Callback sub.
The difference is that the .Net framework now knows that there is something pointing at that sub so won't garbage collect it (clear it from memory)
The MarshallAs bit is a little superfluous as that's the default marshalling but it simply means we're explicitly telling .Net that we're using the delegate to access unmanaged code (something outside the .Net framework)
Just for the record, I still had to download the code as it was actually the bit in Form1.vb that was relevant - But thanks for trying :)

How can I call ActivateKeyboardLayout from 64bit Windows Vista using VBA

Running VBA under XP I was able to call ActivateKeyboardLayout to switch my input language from English to another language. However, this no longer works under Vista64.
Any suggestions or workarounds?
The code that used to work under XP was similar to the following:
Private Declare Function ActivateKeyboardLayout Lib "user32" ( _
ByVal HKL As Long, ByVal flags As Integer) As Integer
Const aklPUNJABI As Long = &H4460446
ActivateKeyboardLayout aklPUNJABI, 0
There was a suggestion to try
Public Declare Function ActivateKeyboardLayout Lib "user32" ( _
ByVal nkl As IntPtr, ByVal Flags As uint) As Integer
When I try this I get the error message:
Variable uses an Automation type not supported in Visual Basic
Your declaration for the ActivateKeyboardLayout is actually incorrect. For 32-bit systems your code should be something like this:
Private Declare Function ActivateKeyboardLayout Lib "user32" (ByVal HKL As Long, _
ByVal flags As Long) As Long
Const aklPUNJABI As Long = &H4460446
Dim oldLayout as Long
oldLayout = ActivateKeyboardLayout(aklPUNJABI, 0)
If oldLayout = 0 Then
'Oops an error'
Else
'Save old layout for later restore?'
End If
The 64-bitness of the operating system is a bit of a red herring in this case. Since you are running a VBA app it must be running as a 32-bit app regardless of OS. I suspect your problem may be that on your Vista system the Punjabi keyboard layout that you want is not loaded. ActivateKeyboardLayout will only work to activate a keyboard layout that is already loaded. For some reason the designers of this API felt that failure due to the keyboard layout not existing was not an error so the LastDllError is not set. You may want to look into using LoadKeyboardLayout for this type of situation.
EDIT: To double check that the keyboard layout you are trying to get is actually loaded you can use this:
Private Declare Function GetKeyboardLayoutList Lib "user32" (ByVal size As Long, _
ByRef layouts As Long) As Long
Dim numLayouts As Long
Dim i As Long
Dim layouts() As Long
numLayouts = GetKeyboardLayoutList(0, ByVal 0&)
ReDim layouts(numLayouts - 1)
GetKeyboardLayoutList numLayouts, layouts(0)
Dim msg As String
msg = "Loaded keyboard layouts: " & vbCrLf & vbCrLf
For i = 0 To numLayouts - 1
msg = msg & Hex(layouts(i)) & vbCrLf
Next
MsgBox msg
This is just a blind guess, but have you tried running your app as elevated administrator to see if it makes a difference? What's the error code / value of GetLastError?
Did you try a .Net line (as in VB.Net script or those snippets) like:
InputLanguage.CurrentInputLanguage =
InputLanguage.FromCulture(New System.Globalization.CultureInfo("ar-EG"))
InputLanguage should be supported for Vista64 with a .Net3.5
VB.Net code:
Public Sub ChangeInputLanguage(ByVal InputLang As InputLanguage)
If InputLanguage.InstalledInputLanguages.IndexOf(InputLang) = -1 Then
Throw New ArgumentOutOfRangeException()
End If
InputLanguage.CurrentInputLanguage = InputLang
End Sub
For 64-bit portability you may need to use IntPtr. Can you give this a shot?
Public Declare Function ActivateKeyboardLayout Lib "user32" (ByVal nkl As IntPtr, ByVal Flags As uint) As Integer
In 64-bit editions of Office apps, VBA is indeed 64-bit. See Office 2010 documentation for details of the changes. For the example given in Stephen Martin's answer, you will need to change the code as follows to add the PtrSafe attribute and fixup the parameters that have a HKL type in the Win32 API:
Private Declare PtrSafe Function ActivateKeyboardLayout Lib "user32" (ByVal HKL As LongPtr, _
ByVal flags As Long) As LongPtr
Const aklPUNJABI As LongPtr = &H4460446
Dim oldLayout as LongPtr
oldLayout = ActivateKeyboardLayout(aklPUNJABI, 0)
If oldLayout = 0 Then
'Oops an error'
Else
'Save old layout for later restore?'
End If
and
Private Declare PtrSafe Function GetKeyboardLayoutList Lib "user32" (ByVal size As Long, _
ByRef layouts As LongPtr) As Long
Dim numLayouts As Long
Dim i As Long
Dim layouts() As LongPtr
numLayouts = GetKeyboardLayoutList(0, ByVal 0&)
ReDim layouts(numLayouts - 1)
GetKeyboardLayoutList numLayouts, layouts(0)
Dim msg As String
msg = "Loaded keyboard layouts: " & vbCrLf & vbCrLf
For i = 0 To numLayouts - 1
msg = msg & Hex(layouts(i)) & vbCrLf
Next
MsgBox msg
The thing that everyone seems to overlook here is that you are working in VBA, not in .NET. IntPtr is a .NET type which represents an integer which is native to the platform. On a 32-bit platform it is 32 bits, on a 64 bit platform, it is 64 bits.
Given that an HKL is a typedef for a handle, which is a typedef for PVOID which is a typedef for VOID *, it's exactly what you need, if you were using .NET.
VBA doesn't have anything for 64-bit numbers, so you have to take a different approach.
On a 64-bit machine, you will have to do something like this:
Public Type HKL64
High As Long
Low As Long
End Type
Private Declare Function ActivateKeyboardLayout Lib "user32" ( _
Byval HklHigh As Long, Byval HklLow As Long, _
ByVal flags As Integer) As HKL64
This should allow you to pass a 64 bit value on the stack to the API function (across two variables). However, if you are going to use this code on 64 bit and 32 bit machines, you are going to have to make two declarations of the API and then determine which one to call.
Also, any other code in VBA that calls APIs that deal with pointers or handles will have to be changed appropriately to handle 64 bit input (not 32).
On a side note, the original declaration of ActivateKeyboardLayout is wrong, as it had a return type of Integer, which is a 16-bit value, while the API returns a type of HKL, which is 32 or 64 bits, depending on the platform.