Sunday, April 14, 2013

Создание C++ DLL для получения списка допустимых разрешений экрана

Автор: Spoof
Перевод: Vadakuma
ОРИГИНАЛ: Building a C++ DLL and script class to list graphics resolutions


    Консольная команда SETRES предназначена для смены разрешения окна, а также для перехода в полноэкранный режим и обратно. Но, к сожалению, нет никакой информации, какие именно разрешения экрана поддерживает машина пользователя.

    В этом уроке я покажу вам как создается Dynamic Link Library utility с помощью C++ и классы UnrealScript для взаимодействия с dll-кой, в итоге получим список поддерживаемых разрешений от пользовательской машины, а это очень полезная информация, к примеру, для корректного отображения UI.


    Нет необходимости в глубоком понимании процесса, можно просто скопировать C++ код и скомпилировать проект. Я намеренно упростил код, переведя большую часть функциональности в UnrealScript часть.

Итог, потребуется сделать такие шаги:
    1) Создать dll проект в Microsfot Visual C++ 2010 Express
    2) Cкомпилировать код С++
    3) Создать UnrealScript класс, для взаимодействие с полученной dll-кой. С помощью обращений к dll из этого класса будет составлен набор доступных разрешений.



Шаг 1 Создаем DLL проект


1) Можно использовать любой C++ IDE/compiler, но в этом уроке буду пользоваться именно Microsoft Visual C++ 2010 Express.
    -Select File -> New -> Project... откроется окна с выбором, какой проект создать.
    -Выберете Win32 from в списке слева, затем Win32 Console Application в центре.
    -Чуть ниже введите название проекта DeviceInfo, создайте папку для проекта, если не устраивает адрес проживания по умолчанию.

2) Нажимаем на Ok, создавая тем самым пустой проект.
    -Нажимаем Next > доходим до окна с настройками.
    -Тип приложение поставьте DLL.
    -Под дополнительными опциями выберете Empty Project.

3) Жмем Finish и тем самым подтверждаем создание настроенного нами проекта.
    -В главном меню выбираем Project -> Add New Item открется окно выбора, какого рода файл вы хотите создать. Будьте уверены, что выбрали именно "add" в вашем IDE.
    -Выберете Code в настройках, и C++ File (.cpp) в центре.
    -Введите имя файла DeviceInfo.cpp.
    -Жмем на add(добавить).

4) Теперь в проекте должен появиться пустой C++ файл. Но перед тем как начнем его заполнять, необходимо сделать ряд обязательных настроек, для того чтобы наш dll файл мог работать с UDK.
    -В окне Solution Explorer жмите правой кнопкой мыши по имени проекта и выбирайте Properties(свойства).

Замечание: проект должен находиться в корне древа папок и файлов. Если в следующем шаге возникнут проблемы, возможны вы открыли не те настройки.

    -В окне настроек code generation : Configuration Properties -> C/C++ -> Code Generation
(Свойства конфигурации > С/С++ > Создание кода)
    -Справа от строки Struct Member Alignment(Выравнивание членов структур) выберете опцию "4 byte (/Zp4)"
   
    Эта опция гарантирует, что структуры, определенные в C++, будут иметь идентичное с UnrealScript байт выравнивание, кратное 4ем. Члены структуры между собой будут иметь расстояние, кратное 4 байтам , что позволит вытаскивать значения параметров по верному адресу.




Шаг 2 Пишем C++ код


Копируем этот кусок кода в DeviceInfo.cpp

/* Include the windows header file, which resolves references to DEVMODE and EnumDisplaySettings */

#include <windows.h> 
#define DLLExport __declspec(dllexport) 

/* Ensure compatibility between C and C++ code */ 
extern "C" 
{ 
    /* UDK_Resolution, a mirror of our UnrealScript struct Resolution.
     Both must have identical structure, and the C++ compiler should be set
     to pack on 4-byte boundaries (/Zp4)*/ 
    struct UDK_Resolution 
    {     
        int PixelWidth, PixelHeight;
    }; 

    /* DLLEnumDisplaySettings A wrapper for the Windows function EnumDisplaySettings. 
    UDK_Resolution * const udk_r This is a pointer to the local variable        
    'r' defined in our UnrealScript function, which allows us to modify it directly from the DLL.
    The names do not need to match with UnrealScript, and the prefix 'udk_' is only 
    to help define its role. The pointer is made const to add some safety. 
    Pointers are powerful and brutal. Learn C/C++ before tinkering with them.*/
    DLLExport int DLLEnumDisplaySettings( int modeNum, UDK_Resolution * const udk_r ) 
    {     
        DEVMODE dm;     
        dm.dmSize = sizeof(DEVMODE); 
        /* Each call to EnumDisplaySettings populates 'dm' with the width, height,
        bit-depth and refresh rate for the device mode specified by modeNum. 
        However, we're only concerned with resolution so we keep width and height,
        and discard the rest. The NULL value in the first parameter will query 
        the current display adaptor which should be fine for our purposes.*/
        if ( EnumDisplaySettings(NULL, modeNum, &amp;dm) )
        {         
            // copy the width and height properties of DEVMODE to         
            // UnrealScript's Resolution         
            udk_r-&gtPixelWidth = dm.dmPelsWidth;
            udk_r-&gtPixelHeight = dm.dmPelsHeight;

            return 1; 
        }     
        // EnumDisplaySettings returned false, meaning the specified modeNum     
        // does not exist     
        return 0;
     }
}
    Компилируем проект нажав F7. Visual Studio автоматически сохранит файл cpp и начнет сборку проекта. Если в проекте все правильно, то компилирование закончится успешно, а также появится dll файл, который расположится в папке отладки(Debug) в DeviceInfo project. Если вы используете win7, то путь до файла будет примерно таким:


C:\Users\USERNAME\Documents\Visual Studio 2010\Projects\DeviceInfo\Debug\DeviceInfo.dll


    Копируем DeviceInfo.dll в ваш udk проект UDK-version/Binaries/Win32/UserCode папку. У меня выглядит это так:


D:\UDK\UDK-2011-09\Binaries\Win32\UserCode\DeviceInfo.dll


ВАЖНО: DLL созданная нами, является win-32 версией. Если вы работаете над 64-ным проектом, вам необходимо скомпилировать библиотеку под 64-битную платформу. Для того чтобы наша созданная библиотека подключилась и работала, нужно запускать UDK в 32-битном режиме (из папки Win32).




Шаг 3 UnrealScript и тестирование


    Создаем новый класс в вашем udk проекте с названием DeviceInfo.uc. Переносим туда следующий код и сохраняем.

class DLLDeviceInfo extends Object
 DLLBind(DeviceInfo);

/* Resolution, holds the width and height of a display resolution.
*/
struct Resolution
{
    var int PixelWidth, PixelHeight;
};

/* An array of Resolutions, listing all the unique display resolutions found
*/
var array<resolution> AllResolutions;

/* DLL import declaration, matching the call signature of the function defined in 
 our DLL
*/
dllimport final function int DLLEnumDisplaySettings(int modeNum, out Resolution r);

/* EnumDeviceModes, builds a list of graphics resolutions supported by the hardware.
 Each call to DLLEnumDisplaySettings may return a new resolution, or a duplicate 
 resolution with different bit-depth and refresh rate. For SETRES and our UI options
 we only require a list of the unique resolutions, so we'll filter out all the
 duplicates.
*/
function EnumDeviceModes()
{
    local Resolution r;
    local int modeNum;
    local int key;
    local array<int> keys;

    `log("Enumerating device modes...");
    // Start with mode 0
    modeNum = 0;
    // Query device mode 0, 1, 2, ... etc. DLLEnumDisplaySettings returns 0 
    //when all modes have been reported
    while( DLLEnumDisplaySettings(modeNum, r) != 0 )
    {
        // combine width and height into a single integer 'key'
        key = r.PixelWidth &lt;&lt; 16 | r.PixelHeight;
/* This simple trick filters out duplicate resolutions by creating a 
 unique key for each combination of width and height. If our key list
 doesn't yet contain an entry for this combo then we have a new
 resolution to add to our list of AllResolutions.
 
 Reduces hundreds of reported modes down to a handful of useable options.
 Which is nice.
 */
        // Already added this combination of width and height?
        if ( keys.Find(key) == INDEX_NONE )
        {
            // Add the new resolution
            AllResolutions.Additem(r);
            // Add the key
            keys.AddItem(key);
        }
 
        // next device mode
        modeNum++;
    }

    // Report some useful info...
    `log(AllResolutions.Length@"available resolutions from"@modeNum@"device modes");
    `log("The following resolutions are supported:");

    foreach AllResolutions(r)
    {
        `log(" " $ r.PixelWidth @ "x" @ r.PixelHeight);
    }
}


    Чтобы воспользоваться возможностями класса, нужно создать его экземпляр и вызвать функцию EnumDeviceModes. В идеале все это делать во время инициализации игры, потому как вызвать нужно лишь один раз, а далее просто пользоваться полученными результатами.

     Вместо предложенного способа, вы можете добавить представленный код, к примеру, в имеющийся UI менеджер, или куда вам заблагорассудится. Для тестирования добавим событие InitGame в дочернем классе от GameInfo.

var DLLDeviceInfo DeviceInfo;
event InitGame( string options, out string errorMessage )
{
    super.InitGame(options, errorMessage);
    DeviceInfo = new class'DLLDeviceInfo';
    DeviceInfo.EnumDeviceModes();
}


Наконец компилируем UDK проект и смотрим логи. Должно появиться нечто вроде:

[0002.60] ScriptLog: Enumerating device modes...
[0002.62] ScriptLog: 16 available resolutions from 225 device modes.
[0002.62] ScriptLog: The following resolutions are supported:
[0002.62] ScriptLog: 640 x 480
[0002.62] ScriptLog: 720 x 480
[0002.62] ScriptLog: 720 x 576
[0002.62] ScriptLog: 800 x 600
[0002.62] ScriptLog: 1024 x 768
[0002.62] ScriptLog: 1152 x 864
[0002.62] ScriptLog: 1280 x 720
[0002.62] ScriptLog: 1280 x 768
[0002.62] ScriptLog: 1280 x 800
[0002.62] ScriptLog: 1280 x 960
[0002.62] ScriptLog: 1280 x 1024
[0002.62] ScriptLog: 1360 x 768
[0002.62] ScriptLog: 1366 x 768
[0002.62] ScriptLog: 1600 x 900
[0002.62] ScriptLog: 1600 x 1024
[0002.62] ScriptLog: 1680 x 1050


Заключение 


    Вы можете вместо Debug версии сделать Releaze версию. Для этого в диспетчере конфигураций выберете Releaze и перекомпилируйте - появится папка Releaze с заветной dll-кой. 

Дополнительная информация: 

EnumDisplaySettings: http://msdn.microsoft.com/en-us/library/dd162611(v=VS.85).aspx 
DLLBind: http://udn.epicgames.com/Three/DLLBind.html

UPDATE Вариант, в котором вытаскивается максимально возможное разрешения девайса(эт уже от себя). Функция EnumDeviceModes немного переписана:

function vector2d EnumDeviceModes()
{
    local Resolution r;
    local Vector2D BestRes;
    local int modeNum, BestmodeNum;
    local int key;
    local array<int> keys;

    `log("Enumerating device modes...");

    // Start with mode 0
    modeNum = 0;

    // Query device mode 0, 1, 2, ... etc. DLLEnumDisplaySettings
     //returns 0 when all modes have been reported
    while( DLLEnumDisplaySettings(modeNum, r) != 0 )
    {
        // combine width and height into a single integer 'key'
        key = r.PixelWidth << 16 | r.PixelHeight;
        // Already added this combination of width and height?
        if ( keys.Find(key) == INDEX_NONE )
        {
            // Add the new resolution
            AllResolutions.Additem(r);
            // Add the key
            keys.AddItem(key);
        }
        // next device mode
        modeNum++;
    }

    // Report some useful info...
    `log(AllResolutions.Length@"available resolutions from"@modeNum@"device modes");
    `log("The following resolutions are supported:");
    foreach AllResolutions(r)
    {
    `log(" " $ r.PixelWidth @ "x" @ r.PixelHeight);
    }

    // Start with mode 0
    modeNum = 0;
    while (modeNum < AllResolutions.Length)
    {
        if(AllResolutions[BestmodeNum].PixelWidth<AllResolutions[modeNum].PixelWidth)
        {
            BestmodeNum = modeNum;
        }
        else if(AllResolutions[BestmodeNum].PixelHeight<AllResolutions[modeNum].PixelHeight 
             && AllResolutions[BestmodeNum].PixelWidth == AllResolutions[modeNum].PixelWidth)  
        {
            BestmodeNum = modeNum;
        }
        // next device mode
        modeNum++;
    }
}


    Т.е. теперь EnumDeviceModes будет возвращать вектор BestRes, в котором содержится максимально высокое значение разрешения девайса.

1 comment:

  1. Class Database.DB_DLLAPI cant bind to DLL E:\UDK\Binares\Win32\Usercode\UDKProjectDLL.DLL
    C чем может быть связана эта ошибка?

    ReplyDelete