Windows 8 Tips & Tricks: Detectar procesador ARM

Las aplicaciones de la tienda de Windows pueden compilarse para x86, x64 y ARM. Es necesario seleccionar la arquitectura de compilación cuando hacemos uso de código nativo, pero si nuestra aplicación, como muchas otras, sólo hace uso de código administrado, podemos compilar para AnyCPU y nuestro paquete se ejecutará en las 3 plataformas sin ningún problema

Sin embargo, aunque a veces sea técnicamente posible usar AnyCPU, nos gustaría proporcionar una mejor experiencia de usuario en procesadores ARM eliminando algunas animaciones o reduciendo el número operaciones que hagan uso intensivo de la CPU. Una opción es crear un proyecto separado y compilarlo específicamente para ARM, pero empezaríamos a tener problemas de duplicación de código. Otra opción es identificar el tipo de procesador en tiempo de ejecución y si es ARM, desactivar ciertas operaciones o modificar algunos comportamientos

La API Win32 GetNativeSystemInfo de “kernel32.dll” nos permite obtener la información que necesitamos, así que lo primero que tenemos que hacer es declararla:

1
2
        [DllImport("kernel32.dll")]
        static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);

La primera línea sirve para decirle al compilador que el método está expuesto en una librería de código no manejado. En este caso la librería nativa “kernel32.dll”. También le pasamos el atributo lpSystemInfo de tipo SYSTEM_INFO mediante referencia. Esto sirve para que cualquier cambio que el método de la librería kernel32.dll haga a lpSystemInfo sean visibles desde nuestra aplicación. Básicamente le pasamos lpSystemInfo vacío y la librería nos lo rellenará de datos, pero si os fijáis el tipo de retorno es void… porque el resultado estará almacenado en lpSystemInfo.

¿Qué es SYSTEM_INFO? GetNativeSystemInfo espera recibir un tipo SYSTEM_INFO que no está definido en C#, así que tendremos que creárnoslo. La información para hacerlo está en la documentación de la API, y pasado a C# sería esto:

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        [StructLayout(LayoutKind.Sequential)]
        struct SYSTEM_INFO
        {
            public ushort wProcessorArchitecture;
            public ushort wReserved;
            public uint dwPageSize;
            public IntPtr lpMinimumApplicationAddress;
            public IntPtr lpMaximumApplicationAddress;
            public UIntPtr dwActiveProcessorMask;
            public uint dwNumberOfProcessors;
            public uint dwProcessorType;
            public uint dwAllocationGranularity;
            public ushort wProcessorLevel;
            public ushort wProcessorRevision;
        };

La línea 4 es un atributo de clase que nos deja decirle al compilador cómo queremos que maneje la memoria. Normalmente en C# el código es manejado y no necesitamos preocuparnos por cómo estamos usando la memoria, el CLR se encarga de ello por nosotros. Sin embargo, en este caso estamos interactuando con kernel32.dll que ejecuta código nativo y que encima escribirá en nuestra variable lpSystemInfo y espera encontrarse el struct escrito de forma secuencial, sin huecos ni “trucos” raros. Para evitar que a nuestro compilador se le ocurra optimizar nuestro código y movernos de sitio las cosas, le especificamos el valor LayoutKind.Sequential y todo arreglado :)

Llegados a este punto, si llamamos al método, nuestro objeto lpSystemInfo se rellenará con los datos del procesador de la máquina. El valor que nos interesa es el de wProcessorArchitecture. Según la documentación el valor que toma wProcessorArchitecture en procesadores ARM es “undefined”, en otras palabras, no hay información oficial. Según mis pruebas empíricas, los procesadores ARM retornan el valor 5, así que nos podemos crear el siguiente método:

20
21
22
23
24
25
        private static bool IsARM()
        {
            var sysInfo = new SYSTEM_INFO();
            GetNativeSystemInfo(ref sysInfo);
            return sysInfo.wProcessorArchitecture == 5;
        }

Resumiendo, ahora sólo necesitamos llamar a IsARM() para obtener true si ejecutamos la App en ARM y false en cualquier otra arquitectura. De este modo podemos modificar el comportamiento de nuestra aplicación para ser más “friendly” con micros ARM sin necesidad de renunciar a la comodidad de un paquete AnyCPU :)

Ahora todo junto para que no haya dudas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        [DllImport("kernel32.dll")]
        static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
 
        [StructLayout(LayoutKind.Sequential)]
        struct SYSTEM_INFO
        {
            public ushort wProcessorArchitecture;
            public ushort wReserved;
            public uint dwPageSize;
            public IntPtr lpMinimumApplicationAddress;
            public IntPtr lpMaximumApplicationAddress;
            public UIntPtr dwActiveProcessorMask;
            public uint dwNumberOfProcessors;
            public uint dwProcessorType;
            public uint dwAllocationGranularity;
            public ushort wProcessorLevel;
            public ushort wProcessorRevision;
        };
 
        private static bool IsARM()
        {
            var sysInfo = new SYSTEM_INFO();
            GetNativeSystemInfo(ref sysInfo);
            return sysInfo.wProcessorArchitecture == 5;
        }

About the Author

Me llamo Pablo Carballude González, soy graduado en computación y con master en HCI y Seguridad Informática. Actualmente trabajo para Microsoft como Developer Evangelist. Soy de esas personas que no saben si los textos autobiográficos deben ser en primera o tercera persona. Lo intenté en segunda, pero no le entendí nada :P