Porting to OS/2: Case #19 Windows and Odin

OS/2 and Windows are based on similar architectures. Because of these, when porting programs for other platforms to OS/2, Windows codes are very helpful. Then how about porting from Windows to OS/2 directly ?

As I said above, architectures used on OS/2 and Windows are similar. So it's possible to think that porting is relatively easy. However, implementing and replacing Windows APIs are hard and boring task. Especially, porting GUI APIs is peak.

Nevertheless, there is no need to be afraid beforehand. Find the way to go slowly.

Implementing Windows APIs


First way is to implement Windows APIs directly. When you try to this, MSDN is your friend. You should implement those APIs in according to specifications of MSDN. If you have no clues, then try googling or refer to Odin sources. Many people already implemented many Windows APIs in Odin proejct. And Odin project is being maintained at netlabs.

Window Coordinate Systems


When implementing GUI APIs, there is one thing which you should remember. It is that window coordinate systems of Windows are different from ones of OS/2.

Origin(0,0) on Windows is top-left corner. But origin(0,0) on OS/2 is bottom-left corner. That is, coordinate systems are same in X direction, but inverted in Y direction. In addition, when you specify a rectangular box, on Windows top is inclusive and bottom is exclusive. But on OS/2 bottom is inclusive and top is exclusive.

For examples, if a screen size is 1024x768, then (0, 0) on Windows corresponds to (0, 767) on OS/2. That is, Y coordinates between OS/2 and Windows have the following relation.

Y2 = ( HEIGHT - 1 ) - Y1


How about a window geometry ? A window whose geometry is (100, 100) - (200, 200) on Windows corresponds to (100, 568) - (200, 668) on OS/2. Remember that a geometry on Windows is (left, top) - (right, bottom) but on OS/2 is (left, bottom) - (right, top). In this case, Y coordinates can be converted by this.

Y2 = HEIGHT - Y1


Here are helper functions for these.

1
2
3
4
5
6
7
8
9
10
11
12
13
// invert Y of a point against the given height
int invertPointY( int y, int height ) const
{
    return ( height - 1 ) - y;
}

// invert Y of a rectangular against the given height
int invertRectY( int y, int height ) const
{
    // bottom is inclusive, top is exclusive
    // the following is the short of ( height - 1 ) - ( y - 1 )
    return height - y;
}


y is a Y position relative to a window whose height is height.

When porting GUI programs, you should consider these differences of coordinate systems on both OSes.

But this is inconvenient because you should invert all the Y coordinates manually. Fortunately, OS/2 provides APIs for this. They are GpiEnableYInversion() and GpiQueryYInversion(). See the wiki of netlabs for details.


Odin


However, this way is more or less boring and hard. In addition, you should repeat these tasks every time unless you create libraries for them. Fortunately, those libraries exist on OS/2. It is Odin. As I said above, many people implemented Windows APIs in Odin. And Odin is still maintained. So you don't have to repeat the same tasks if using Odin. In addition, larger and larger projects, Odin is more and more useful. In practice, Odin is used to port OpenJDK6 and Flash11. And maybe SWT.

Building Odin


To use Odin32 APIs, you need to download Odin sources and build it yourself because development-kits of Odin are not distributed as a package. To do this, you need


Or, you can install those tools using rpm/yum except OS/2 toolkit 4.5.

  • yum install subversion gcc kbuild

If you prepared those tools, download sources.

  • svn co http://svn.netlabs.org/repos/odin32/trunk Odin

Then Odin sources will be placed in Odin sub directory.

Now enter into Odin sub directory. Before building Odin, you should customize configuration in according to your build environments. First, copy env.tpl.cmd to setenv.cmd. Then add setup statements for your gcc after [rem call gcc446.cmd] at line 4. Second, copy LocalConfig.kmk.tpl to LocalConfig.kmk. Then set [PATH_SDK_OS2TK4] at line 27 to your OS/2 toolkit path and remove first '#' at same line.

Finally, execute kmk. This builds a release version. If you want to build a debug version, then execute [kmk BUILD_TYPE=debug] instead. A debug build is recommended for later debugging.

If you use kLIBC 0.6.6 and encounter [undefined symbol errors] when building pe.exe, then to fix this, add (2015/03/18) the following to src/peldr/Makefile.kmk (//)


  • pe_CFLAGS           = -DNO_INCL_SAFE_HIMEM_WRAPPERS

before

  • pe_LDFLAGS          = -nostdlib -llibos2

And

  • pec_CFLAGS          = -DNO_INCL_SAFE_HIMEM_WRAPPERS

before

  • pec_LDFLAGS         = -nostdlib -llibos2

Building Odin-based programs


Setting build environments up for Odin-based programs


As always, you need to setup build env before building something. For Odin, use this script as a template.

1
2
3
4
5
6
7
8
9
10
11
@echo off
@YOUR_GCC_SETUP_SCRIPT@
set ODIN_SDK=@YOUR_ODIN_SVN_WORKING_DIRECTORY@
set ODIN_INC=%ODIN_SDK%/include
set ODIN_LIBS=%ODIN_SDK%/out/os2.x86/@YOUR_ODIN_BUILD_TYPE@/stage/lib
set C_INCLUDE_PATH=%ODIN_INC%;%ODIN_INC%/win;%C_INCLUDE_PATH%
set CPLUS_INCLUDE_PATH=%ODIN_INC%;%ODIN_INC%/win;%CPLUS_INCLUDE_PATH%
set LIBRARY_PATH=%ODIN_LIBS%;%LIBRARY_PATH%
set ODIN_SDK=
set ODIN_INC=
set ODIN_LIBS=


Save this as setenv.cmd in your working directory for Odin-based programs. At line 2, replace @YOUR_GCC_SETUP_SCRIPT@ to your gcc setup script such as gcc4.cmd. At line 3 replace @YOUR_ODIN_SVN_WORKING_DIRECTORY@ to your top directory of Odin. That is, the fully qualified path to the directory where env.tpl.cmd exists. At line 5, replace @YOUR_ODIN_BUILD_TYPE@ to your odin build type. That is [debug] or [release].

Building the executables


When you build the executables, you need Odin's entrypoint wrapper, odinexe.cpp. It is in src/Odin32API. Copy it to your working directory. And add it to your source list for the executables. Now build. That's all. ^^

For practice, get a famous [Hello, World] sources for Win32 here,


Save those codes in a box at bottom to [hello.cpp] with Copy and Paste.

And do this,

  • g++ -Zomf -D__WIN32OS2__ -D__i386__ hello.cpp odinexe.cpp -lkernel32 -luser32 -lgdi32 -lodincrt

However, you'll encounter this linker error,

weakld: error: Unresolved symbol (UNDEF) '__Resource_PEResTab'.
weakld: info: The symbol is referenced by:
    P:\tmp\ccUppvuq.o
Ignoring unresolved externals reported from weak prelinker.
Error! E2028: __Resource_PEResTab is an undefined reference
file P:\tmp\ccUppvuq.o(ccUppvuq.o): undefined symbol __Resource_PEResTab

This is because our example does not have any resources. Modify RegisterLxExe() parts at bottom.

  • RegisterLxExe(WinMain, (PVOID)&_Resource_PEResTab);

to

  • RegisterLxExe(WinMain, NULL/*(PVOID)&_Resource_PEResTab*/);

Again build. Then you'll get [hello.exe]. Run it. You can see a window of your first Odin-based programs. Good job!!!

Here, there are things to remember. You should add

  • [-Zomf] to your linker flags
  • [-D__WIN32OS2__ -D__i386__] to your compiler flags
  • [-lkernel32 -luser32 -lgdi32 -lodincrt] to your library list


Building DLLs


Building DLls is similar to building the executables. But you should use odindll.cpp instead of odinexe.cpp. odindll.cpp is also in src/Odin32API.

For practice, get simple examples here,


Required files are SampleDLL.cpp, SampleDLL.h and SampleApp.cpp. If you got those files, then replace [#include "stdafx.h"] in SampleDLL.cpp and SampleApp.cpp with [#include <windows.h>]. [stdafx.h] is for pre-compiled headers.

Before building, you should modify odindll.cpp. Maybe, it has not been maintained for a while.

  1. Append [#include <exitlist.h>] to the list of [#include]
  2. Replace [LibMain] with [DllMain]
  3. Replace [ctordtorInit()] with [__CRT_init(); __ctordtorInit();] in _DLL_InitTerm()
  4. Replace [ctordtorTerm()] with [__ctordtorTerm(); __CRT_term();] in cleanup()
  5. Replace [dllHandle = RegisterLxDll(hModule, LibMain,(PVOID)&_Resource_PEResTab);] with [dllHandle = RegisterLxDll(hModule, DllMain/*LibMain*/, NULL/*(PVOID)&_Resource_PEResTab*/);] in _DLL_InitTerm()

Here is the modified odindll.cpp.

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* $Id: odindll.cpp,v 1.4 2001-02-14 15:14:41 sandervl Exp $ */

/*
 * DLL entry point
 *
 * Copyright 1998-1999 Sander van Leeuwen
 * Copyright 1998 Peter Fitzsimmons
 *
 *
 * Project Odin Software License can be found in LICENSE.TXT
 *
 */


/*-------------------------------------------------------------*/
/* INITERM.C -- Source for a custom dynamic link library       */
/*              initialization and termination (_DLL_InitTerm) */
/*              function.                                      */
/*                                                             */
/* When called to perform initialization, this sample function */
/* gets storage for an array of integers, and initializes its  */
/* elements with random integers.  At termination time, it     */
/* frees the array.  Substitute your own special processing.   */
/*-------------------------------------------------------------*/


/* Include files */
#define  INCL_DOSMODULEMGR
#define  INCL_DOSPROCESS
#include <os2.h>    //Odin32 OS/2 api wrappers
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <odin.h>
#include <win32type.h>
#include <odinlx.h>
#include <misc.h>       /*PLF Wed  98-03-18 23:18:15*/
#include <initdll.h>
#include <exitlist.h>

extern "C" {

//Win32 resource table (produced by wrc)
extern DWORD _Resource_PEResTab;
}
static HMODULE dllHandle = 0;

//BOOL WINAPI LibMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD,LPVOID);


/*-------------------------------------------------------------------*/
/* A clean up routine registered with DosExitList must be used if    */
/* runtime calls are required and the runtime is dynamically linked. */
/* This will guarantee that this clean up routine is run before the  */
/* library DLL is terminated.                                        */
/*-------------------------------------------------------------------*/
static void APIENTRY cleanup(ULONG reason);

/****************************************************************************/
/* _DLL_InitTerm is the function that gets called by the operating system   */
/* loader when it loads and frees this DLL for each process that accesses   */
/* this DLL.  However, it only gets called the first time the DLL is loaded */
/* and the last time it is freed for a particular process.  The system      */
/* linkage convention MUST be used because the operating system loader is   */
/* calling this function.                                                   */
/****************************************************************************/
unsigned long SYSTEM _DLL_InitTerm(unsigned long hModule, unsigned long
                                   ulFlag)
{
   size_t i;
   APIRET rc;

   /*-------------------------------------------------------------------------*/
   /* If ulFlag is zero then the DLL is being loaded so initialization should */
   /* be performed.  If ulFlag is 1 then the DLL is being freed so            */
   /* termination should be performed.                                        */
   /*-------------------------------------------------------------------------*/

   switch (ulFlag) {
      case 0 :
         _CRT_init();
         __ctordtorInit();
         //ctordtorInit();

         CheckVersionFromHMOD(PE2LX_VERSION, hModule); /*PLF Wed  98-03-18 05:28:48*/

         /*******************************************************************/
         /* A DosExitList routine must be used to clean up if runtime calls */
         /* are required and the runtime is dynamically linked.             */
         /*******************************************************************/

         // Uncomment this to enable proper __try/__except support:
#ifdef ODIN_FORCE_WIN32_TIB
         ForceWin32TIB();
#endif
         dllHandle = RegisterLxDll(hModule, DllMain/*LibMain*/, NULL/*(PVOID)&_Resource_PEResTab*/);
         if(dllHandle == 0)
                return 0UL;

         rc = DosExitList(EXITLIST_APPDLL|EXLST_ADD, cleanup);
         if(rc)
                return 0UL;

         break;
      case 1 :
         if(dllHandle) {
                UnregisterLxDll(dllHandle);
         }
         break;
      default  :
         return 0UL;
   }

   /***********************************************************/
   /* A non-zero value must be returned to indicate success.  */
   /***********************************************************/
   return 1UL;
}


static void APIENTRY cleanup(ULONG ulReason)
{
   //ctordtorTerm();
   __ctordtorTerm();
   _CRT_term();
   DosExitList(EXLST_EXIT, cleanup);
   return ;
}


If you use resources, then you should use [(PVOID)&_Resource_PEResTab ] instead of [NULL].

Now build with this script.

1
2
3
4
5
6
7
8
9
10
@echo off
setlocal

set ODIN_CFLAGS=-D__WIN32OS2__ -D__i386__
set ODIN_LIBS=-lkernel32 -luser32 -lgdi32 -lodincrt
g++ -Zdll -Zomf %ODIN_CFLAGS% -o Sample.dll SampleDLL.cpp odindll.cpp %ODIN_LIBS%
g++ -Zomf %ODIN_CFLAGS% -o SampleApp.exe SampleApp.cpp odinexe.cpp Sample.dll %ODIN_LIBS%
g++ -Zomf %ODIN_CFLAGS% -o SampleApp2.exe SampleApp2.cpp odinexe.cpp %ODIN_LIBS%

endlocal


Note that to use __declspec effectively, -Zomf should be used when linking DLLs, and executables should be linked to those DLLs directly like at line 7, or import libs created from DLLs directly not from .DEFs.

SampleApp2.exe at line 8 is an example for a runtime dynamic linking. Here is the source.

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
26
27
28
29
// SampleApp2.cpp
//

//#include "stdafx.h"
#include <windows.h>

typedef VOID (*DLLPROC) ();

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    HINSTANCE hinstDLL;
    DLLPROC HelloWorld;
    BOOL fFreeDLL;

    hinstDLL = LoadLibrary("Sample");
    if (hinstDLL != NULL)
    {
        HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "__Z10HelloWorldv");
        if (HelloWorld != NULL)
            HelloWorld();

        fFreeDLL = FreeLibrary(hinstDLL);
    }

    return 0;
}


At line 21, [__Z10HelloWorldv] is a name mangled by g++ compiler for HelloWorld(). You can disable those mangling with [extern "C"] or [_System] on declaration.

Now, done. For deatils, see doc/Readme.txt and doc/Porting.txt

Good luck to you. ^_________^

댓글

이 블로그의 인기 게시물

토렌트: < 왕좌의 게임 > 시즌 1 ~ 시즌 8 완결편 마그넷

토렌트: NGC < 코스모스 > 우리말 더빙 전편(1편~13편) 마그넷

토렌트: < 스타워즈 > Ep.1 ~ Ep.6 마그넷