Title: Dynamic Invoke a Native DLL on Windows Mobile/CE Author: Michaël De Vos, http://vozzie.be Member ID: 838365 Language: ARM ASM, VB.Net, C# Platform: Windows Mobile, CE Technology: .Net, Windows Mobile, CE Level: Intermediate Description: Tutorial on how to P-Invoke native(unmanaged) libraries from .Net on Windows Mobile. Section Mobile Development SubSection Threads, Processes and IPC License: See the license paragraph at the end of this document.
At first the .Net Framework for Windows didn't support Dynamic P-Invoke. Now there is Marshal.GetDelegateForFunctionPtr that made it possible. But on Windows Mobile there still isn't a way provided in the Framework. There is an article on the web that shows one or two ways to go,... Here is a third. Assumed to be a bit faster...
To create this code it took some time. Based on the article by Richard Birkby of how it can be done in x86 assembly for a normal Windows platform it started. But on a Windows Mobile device with a ARM CPU it's a different game...
When a method is called on a platform with a x86 CPU the arguments are passed on the stack. Last argument first, first last... (In other words: the stack is a FIFO.) When adding a extra parameter that is the function address that needs to be called and creating a function that pops the function address of the stack, and calls that address, we're done... But that's for x86 CPU's.
Most Windows Mobile devices run on other CPU's, mostly ARM. The way functions are called on ARM is slightly different. The first four arguments are stored in the first four registers, and if there are more they are on the stack. So for calling a function with less then 4 arguments it was pretty easy as this code shows.
mov r12, r0 ; move function ptr in register 0 to register 12
mov r0, r1 ; move first argument from register 1 to register 0
mov r1, r2 ; move second argument from register 2 to register 1
mov r2, r3 ; move third argument from register 3 to register 2
mov pc, r12 ; move register 12 to the program counter(pc)
But what about more arguments....?
The fourth argument is on the stack and must be placed into register 3. But then if the called function takes more then 4 arguments , they are on the stack. But the stack still points to argument 4. So the stack pointer needs to be changed to point to argument 5. Doing this the stack would be unbalanced so there is a need to restore the stack. For doing this it is neciserry to return to this procedure, so it can restore the stack pointer(sp). But,....! When changing the return address what is in the link register(lr) it is lost,... So the link register needs to be stored first into memory,... That is, the one and only memory location i needed to make it possible,...
Putting it toghether
ldr r12, =dwLR ; load the address of dwLR into r12
str lr, [r12] ; store link register(lr) in dwLR location
mov r12, r0 ; move function ptr in register 0 to register 12
mov r0, r1 ; move first argument from register 1 to register 0
mov r1, r2 ; move second argument from register 2 to register 1
mov r2, r3 ; move third argument from register 3 to register 2
ldr r3, [sp] ; load the fourth argument from the stack into to register 3
add sp, sp, #4 ; add 4 to the stack pointer, let it point to the fifth argument
add lr, pc, #0 ; add 8 to the program counter(pc) and store in the link register(lr)
mov pc, r12 ; mov function address from register 12 into the program counter(pc)
sub sp, sp, #4 ; subtract 4 from the stack pointer(sp)
ldr r1, =dwLR ; load the address of dwLR into r1
ldr lr, [r1] ; load the value from register 1 into the link register(lr)
mov pc, lr ; move the link register(lr) to the program counter(pc) = return
Note: the return value is inside regiter 0 and is not touched after the call of the function
resources
To call a function in a native library, a handle to that library is needed. If the library is allready loaded it is possible to use GetModuleHandle to get a handle to the library. If this function returns NULL(Nothing,null) it is not loaded yet, then use LoadLibrary. Keep in mind that calling LoadLibrary increments the library it's reference count, so when freeing the library it is not freed until that counter reaches 0 or the process exits. So it is possible to use only LoadLibrary and FreeLibrary.
Passing the handle that GetModuleHandle or LoadLibrary returns and the name of the function to GetProcAddress returns a pointer to the function. That pointer can be used to call the function.
Now all that there is left to do is to declare the function to call, but! Set the dll name to "CeCall.dll" and entrypoint to "invoke". When calling pass the function pointer as first argument. The normal arguments after...
...That's all there is to it...
See these code snippets.
VB.Net
Imports System.Runtime.InteropServices
Class Test
_
Shared Function GetModuleHandle(ByVal fileName As String) As IntPtr
End Function
_
Shared Function GetProcAddress(ByVal library As IntPtr, ByVal procName As String) As IntPtr
End Function
' NOTE: DllName is "CeCall.dll", the EntryPoint is set to "invoke", and the first argument is the pointer of the function.
_
Shared Function MessageBox(IntPtr pMessageBox, IntPtr hWndParent, String text, String title, UInt32 options) As Integer
End Function
Sub LoadLibraryAndGetProcAddress()
Dim hModule As IntPtr = GetModuleHandle("coredll.dll")
Dim pProcedure As IntPtr = GetProcAddress(hModule, "MessageBoxW")
MessageBox(pProcedure, IntPtr.Zero, "Dynamic invoke of a native function in VB.Net for Windows Mobile...", "CeCall.dll", &h40)
End Sub
End Class
C#
using System;
using System.Runtime.InteropServices;
namespace Test
{
class Program
{
[DllImport("Coredll.dll", SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr module, String procName);
[DllImport("Coredll.dll", SetLastError = true)]
static extern IntPtr GetModuleHandle(String fileName);
// NOTE: DllName is "CeCall.dll", the EntryPoint is set to "invoke", and the first argument is the pointer of the function.
[DllImport("CeCall.dll", EntryPoint = "invoke", SetLastError = true)]
static extern Int32 MessageBox(IntPtr pMessageBox, IntPtr hWndParent, String text, String title, UInt32 options);
static void Main(string[] args)
{
IntPtr hModule = GetModuleHandle("Coredll.dll");
IntPtr pProcedure = GetProcAddress(hModule, "MessageBoxW");
MessageBox(pProcedure, IntPtr.Zero, "Dynamic invoke of a native function in CSharp for Windows Mobile...", "CeCall.dll", 0x40);
}
}
}
In the download there is the source of CeCall.dll and a bat file that tries to build it. This with one of the ARM Assemblers shipped with eVC4. There are ARM Assemblers shipped with Visual Studio(or when installing one or another Windows Mobile SDK). But when i've tried to use that assembler he was missing mspdb80.dll. When i placed that dll(there were different versions on my system) into the same directory it worked, but after usage it must be removed or Visual Studio will not build certain projects anymore. I did not add a build with these Assemblers in the bat file, only with the eVC4 assemblers.
To use the dll, place it in the same directory of the application or the windows directory. Or declare the DllImport with a full path and filename.
In the download there are also 2 UnitTest source files. One in C# and one in VB.Net. There is also a bat file that tries to build them.
There is a class NativeLibrary
that can be build into a library that makes it easy to load/unload libraries. Where the user doesn't needs to think about freeing them.
The ARM Assembly code falls under "The Code Project Open License" (CPOL) with this exception. Fair game: When changing the work "CeCall.s", in case there are bugfixes or optimizations of the ARM Assembly part in this article, one must notify the Author of this article. This thru vozzie.be or word.vozzie.be.
All the other files fall under "The Code Project Open License" (CPOL) with no exception.
05/09/2010: Version 0.9.1