DLL Manual

(2012.06.22)

Introduction

About This Document

This document provides information about the DLL mechanism that the CTR-SDK provides.

Information about the following can be found in the referenced documentation.

This document assumes basic knowledge of CTR-SDK terminology. See CTR-SDK Terminology for a glossary of basic CTR-SDK terms.

Notation Used in This Document

This document sometimes puts content in colored boxes like the one below to indicate that it is sample source code.

nn::ro::Initialize(pCrs, crsSize);

Sample source code uses the following user-defined functions in order to make the code easier to understand. You must implement processing equivalent to these functions in your application.

s64 GetFileSize(const wchar_t* path)
Returns the size of the file specified by path.
void* AllocateMemory(size_t size, int alignment)
Allocates a memory area of size bytes, in accordance with the alignment restrictions specified by alignment.
void LoadFile(void* pBuffer, const wchar_t* path, size_t size)
Reads size bytes from the file specified by path into pBuffer.

Terminology

This section defines terminology relating to the DLL mechanism provided by the CTR-SDK.

The meanings and usage of terms used in this document and the CTR-SDK may differ from their general meanings and usage.

Executable code
Sequences of machine code interpreted and executed by the CPU, and constants and variables referenced directly from it.
Module
A chunk of execution code divided into a meaningful unit.
Static module
The main application module, which contains nnMain. This is the first module that is executed when application execution begins.
Dynamic module
A module that can be loaded into memory and used dynamically.
DLL
The mechanism for implementing a dynamic module. It can also refer to a dynamic module itself.
Symbol
A meaningful location in a module. In most cases, the symbol is named, but sometimes it is not.
See Also
Use a symbol from another module. This refers to calling a function defined in another module, or reading/writing a variable defined in another module.
Resolve
Make the symbol being referenced available. It determines the address of a symbol so that a function can be called, or a variable read/written.
Export
Make a module's symbol available for reference from another module. A symbol can only be referenced if it has been exported.
Export type
Indicates the format in which information is referenced from other modules. Three export types are available in ro: name, index, and offset.
Import
Declares a reference. The linker attempts to resolve symbols that have not been imported at link time. This normally results in an undefined symbol error.
Export
Same as "export."
ro
A format for implementing DLLs provided by the CTR-SDK.
CRO Files
The dynamic-module file format used by ro.
CRR Files
A file storing DLL registration information. It can store information for one or more CRO files.
cro.rlt
A file that stores DLL registration information. This file stores information for one CRO file. It is converted into a CRR file for use.
edit
A steering file used by the ARMCC linker.
CRS Files
A file storing information about a static module.
xrl
A file storing export/reference symbol information for a module.
uae
A command-line option file specified to the ARMCC linker.
ro library
The nn::ro namespace section of the API provided by the CTR-SDK. It is a collection of functions for using DLLs.

DLLs

Static and Dynamic Modules

If an application does not use dynamic modules, then all its executable code is collected into a single file. When an application starts, all its executable code is loaded into memory and maintained there until the application exits. The executable code is never modified while the application is running. The executable code that is loaded upon startup is called the "static module."

Meanwhile, if the application uses dynamic modules, then it can split up its executable code into multiple modules. When the application starts, only the static module is loaded into memory. The static module is then able to load the dynamic modules, thus running executable code including dynamic modules that can be loaded into and released from memory during program execution.

What You Can Do with a DLL

Conserve Memory

You can reduce the amount of memory that your code uses by breaking up your application into multiple modules, and loading them into memory only when needed, freeing them afterward.

For example, you could put single-player mode and multiplayer mode into separate DLLs, and load just one of them depending on the play mode. This will reduce your application's memory footprint by the size of the other mode.

Shorten Startup Time

When your application is launched, it starts running after all of its static modules have been loaded into memory. Thus the time it takes from the application's launch instruction until nnMain starts executing is proportional to the size of the static modules. You can shorten the time it takes for your application's first screen to appear by putting just the minimum code needed to show the first screen in your static module, and putting the other portions of your application into DLLs, and loading them after the first screen appears.

Features and Limitations of the ro Format

The DLL mechanism provided by the CTR-SDK is called "ro."

Features

The ro format features the following differences from other DLL implementations.

Upper Limit

The limitations of the ro format are as follows.

Restrictions

The ro format has the following restrictions:

In the ro format, you can make and use any static library (.a) as a DLL. But because of these restrictions, if you intend to use a static library as a DLL, be sure to confirm with the library's creator that it can be made and used as a DLL.

The static libraries (.a) provided in the SDK must not be made and used as DLLs. That is prohibited.

ro File Structure

If you did not use DLLs, you would develop CTR applications by creating CXI and CCI files from AXF files. If you use DLLs, you must additionally create CRS, CRR, and CRO files.

CRS Files

A CRS file stores reference and export information for static modules. It is used to resolve symbols between DLLs. It does not contain executable code.

CRR Files

A CRR file contains CRO management information. It also contains information necessary for debugging DLLs.

CRR files have an important limitation: they must be stored on the ROM-FS, directly under the ".crr" directory, which is in turn directly under the ROM-FS root directory (if the ROM-FS root directory is /, this directory will be /.crr). The .crr directory must not contain any file other than the CRR file.

CRO Files

A CRO file is a dynamic module. It stores executable code and information necessary to resolve symbols between the application's various modules.

Creating Dynamic Modules

Creating the Source Code

There are no special requirements for the source code of a dynamic module, with the exception of a few limitations. You can create them in the same manner as static modules. As with ordinary code, the only things you need to do in order to call functions and reference variables across modules are to share headers and code the calls and references.

To read about the restrictions on dynamic modules, see Restrictions.

You must add extra code to use DLL features. See Special Uses for details.

Building DLLs

See CTR-SDK Build System Manual (for DLLs) if you will be using the SDK's build system. If you will be developing your own build system, see Guide to Developing a Build System (for DLLs).

Selecting the Export Type

The ro format has three export types. You must select which export types to use when you create your CRO file. The table below shows the differences between the various export types.

NameIndexOffset
Size of referencing CRO fileLargeSmallSmall
Size of exporting CRO fileLargeSmall0
Time required by LoadModuleLongMediumShort
Getting pointer manuallyPossiblePossibleImpossible
Creating referencing module firstPossibleImpossibleImpossible
Build stepsNormalNormalMany

Name has the greatest functionality, followed by index and then offset, and greater functionality comes at a correspondingly greater cost.

Normally, it is fine to choose offset as the export type. If you want to get pointers manually (using GetPointer) or use a DLL that functions as a library, you will choose index or name, depending on your needs.

Using Static Libraries

Static libraries can be embedded into dynamic modules. However, in addition to the ro restrictions , you should also note the following precaution:

Creating Static Modules

Creating the Source Code

Some variables require special treatment. For more information, see Handling Symbols with Multiple Definitions.
Other than symbols with multiple definitions, there is nothing special that must be done to reference DLL functions and variables. You simply share headers and reference the normal way.

In the ro format, the management of DLLs is left entirely up to the application, so to prepare memory for use by DLLs and to load DLLs you need to implement everything with code in the application. For details, see Using DLLs.

In addition, you need to add extra code to use DLL features. See Special Uses for details.

Building

See CTR-SDK Build System Manual (for DLLs) if you will be using the SDK's build system. If you will be developing your own build system, see Guide to Developing a Build System (for DLLs).

Selecting the Export Type

You can select the export type, so the static module also exports symbols for the dynamic module.
Selecting export types for static modules is the same as selecting export types for dynamic modules. For more information, see the dynamic modules section.

Handling of Symbols with Multiple Definitions

Symbols with multiple definitions require special treatment.

Symbols with Multiple Definitions in C++

Usually, having multiple instances of the same symbol in a single program is not permitted in the C/C++ programming languages. However, for the following elements in C++, the same symbol can be included in multiple object files and used normally.

Symbols for these are merged appropriately by the linker during linking, and an adjustment is made so that all the symbols become one in the overall program.

Handling of Symbols with Multiple Definitions in ro

Because the symbols are merged by a linker, they must be merged in each module. Also, because plug-in type use is supported in ro, multiple modules are permitted to contain the same symbol. As a result, an instance of the symbols for the elements listed above exists in each module that is referenced.

However, because usually symbols that represent variables among the elements listed above are used with the assumption that all references are made to a unique instance, unintended results may occur if different instances are referenced in each module.

Processes for variables and elements other than variables are separated in ro in order to handle both situations. Among symbols that are defined in multiple modules, for symbols that represent variables an instance in the static module is always referenced, and all other symbols can be contained in every module.

Support for Symbols with Multiple Definitions

As described above, when symbols that represent variables are defined in multiple modules, ro assumes that those symbol definitions are in the static module and then references them in the static module.

If a definition that is referenced is not in the static module, an error will occur when linking the static module. In such cases, the code must be revised so that the static module contains a definition for the referenced symbol.

If multiple definitions are not created (intentionally or accidentally), support will be necessary for the following.

The definitions of symbols for the above are created in object files created from the source files that referenced those symbols. So, reference these symbols from the static module source files to make sure that the definitions are contained in the static module. Often these symbols cannot be directly referenced. The definitions can be included in the static module by using indirect methods such as referencing the function by using the variable represented by the symbol.
NN_UTIL_REFER_SYMBOL can be used for this purpose.

For example, when a header such as the following is used, the conditions described above apply because ms_pInstance is a template class class member variable. If the static module is not using a class S at all and the S class is used in multiple dynamic modules, Singleton<S>::ms_pInstance in the dynamic modules will cause an error at build time.

   Header
(Code from the very beginning)

... (omitted) ...

template <class T>
class Singleton
{
public:
    static T& GetInstance() { return *ms_pInstance; }

protected:
    Singleton()  { ms_pInstance = static_cast<T*>(this); }
    ~Singleton() { ms_pInstance = NULL; }

private:
    static T* ms_pInstance;
};

template <class T> T* Singleton <T>::ms_pInstance = NULL;

class S : public Singleton<S>
{
};

... (omitted) ...

In this case, the following functions are added to source files and other files that include nnMain. These functions do not actually need to be called.

   Any source files that are used in static modules
(code added for ro support)

... (omitted) ...

void DummyReference()
{
    NN_UTIL_REFER_SYMBOL(S::GetInstance);
}

Using DLLs

Basic Sequence

The list below shows the sequence from loading a single CRO file, to using it, and then freeing all memory.

  1. Load CRS file into memory
  2. nn::ro::Initialize(crs)
  3. Load CRR file into memory
  4. nn::ro::RegisterList(crr)
  5. Load CRO file into memory
  6. nn::ro::LoadModule(cro)
  7. pModule->DoInitialize()

    Execute process using CRO file
  8. pModule->DoFinalize()
  9. pModule->Unload()
  10. Free memory for CRO file
  11. pRr->Unregister()
  12. Free memory for CRR file
  13. nn::ro::Finalize()
  14. Free memory for CRS file

If you use multiple CRO files at the same time, simply repeat steps 5 to 7 as many times as necessary. If you want to load and use another CRO file after freeing the first, continue to step 10, and then return to step 5.

Normally, you will perform up to step 4 when the application starts, and 11 and beyond when it exits. You will only perform steps 5 to 10 as appropriate when the application is running.

Below, each of these steps is described in detail.

Loading CRS File and Initializing ro Library

You must first load the CRS file into memory (e.g., from ROM-FS), and then call nn::ro::Initialize with this as the argument.

const wchar_t* crsPath = L"rom:/static.crs";

size_t crsSize  = GetFileSize(crsPath);
void* pCrs      = AllocateMemory(crsSize, 0x1000);

LoadFile(pCrs, crsPath, crsSize);
nn::ro::Initialize(pCrs, crsSize);

There is a one-to-one relationship between CRS files and static modules; there can only be one of them per static module. Similarly, the CRS file of one static module will be different from the CRS file of another. Therefore, a single application will never have more than one CRS file, and applications never choose which CRS file to load.

Registering CRR Files

You must first load the CRR file into memory (e.g., from ROM-FS), and then call nn::ro::RegisterList with this as the argument.

const wchar_t* crrPath = L"rom:/.crr/static.crr";

size_t crrSize  = GetFileSize(crrPath);
void* pCrr      = AllocateMemory(crrSize, 0x1000);

LoadFile(pCrr, crrPath, crrSize);
nn::ro::RegistrationList* pList = nn::ro::RegisterList(pCrr, crrSize);

A CRR file contains information about multiple CRO files. Before loading a CRO file, you must register the CRR file that contains the information about it. Ordinarily, you will create a single CRR file containing the information for all CRO files that the application will use. It is therefore normally sufficient to register just that one file. Use multiple CRR files if you want to also optimize the memory used by CRR files, or if you will be distributing additional programs.

Loading the CRO File

Load the CRO file, which is the actual dynamic module. Load the CRO file into memory (e.g., from ROM-FS), and then pass that as an argument to nn::ro::LoadModule. You must pass to LoadModule a memory area for the buffer required separately by the CRO file. Pass the CRO object as an argument to nn::ro::GetSizeInfo to get the size of the buffer required by the CRO file.

After LoadModule succeeds, you must call nn::ro::Module::DoInitialize on the value returned by LoadModule before using the module.

const wchar_t* croPath = L"rom:/module1.cro";

size_t croSize  = GetFileSize(croPath);
void* pCro      = AllocateMemory(croSize, 0x1000);

LoadFile(pCro, croPath, croSize);

nn::ro::SizeInfo sizeInfo;
nn::ro::GetSizeInfo(&sizeInfo, pCro);

void* pBuffer   = AllocateMemory(sizeInfo.bufferSize, 8);
nn::ro::Module* pModule = nn::ro::LoadModule(
                            pCro, croSize, pBuffer, sizeInfo.bufferSize);

pModule->DoInitialize();

DoInitialize builds global objects. If two modules reference each other in the global-objects building stage, first perform the steps up to LoadModule for each module, and then call DoInitialize after both calls to LoadModule complete.

Using CRO Files

You have now resolved the references to the CRO file, and it is possible to call functions and read/write variables defined in it. However, this does not necessarily mean that all the references by the loaded CRO file to other modules have been resolved. The other CRO modules that this one references may not have been loaded yet, or the symbols that it references may not have been defined or exported at all.
You can use the nn::ro::Module::IsAllSymbolResolved function to determine whether all references to other modules by the CRO file you've loaded are resolved.

Although references are resolved automatically, you can also obtain pointers manually. To get a pointer to a function or variable manually, use nn::ro::GetPointer or nn::ro::Module::GetPointer.

// Get a pointer to a function manually, and call it
int (*pFunc)(int) = pModule->GetPointer<void (*)(int)>("TestFunc");
int ret = pFunc(0x1234);

// Get a pointer to a variable manually, and read/write it
int& var = *pModule->GetPointer<int*>("g_Variable");
NN_LOG("%d\n", var);
var = ret;

Releasing CRO Files

To release the memory used by a CRO object, call nn::ro::Module::DoFinalize and nn::ro::Module::Unload on the value returned by LoadModule.

pModule->DoFinalize();
pModule->Unload();

FreeMemory(pBuffer);
FreeMemory(pCro);

Under certain conditions, you can call LoadModule again for the CRO object in the freed memory area, and use it again. See Reusing CRO Objects for details.

Releasing CRR Files

To release the memory used by a CRR object, call nn::ro::RegistrationList::Unregister on the value returned by RegisterList.

pList->Unregister();

FreeMemory(pCrr);

Releasing CRS Files

To release the memory area used by a CRS object, call nn::ro::Finalize.

nn::ro::Finalize();

FreeMemory(pCrs);

Calling nn::ro::Finalize enables you to free the memory for the CRS object as well as all CRR and CRO objects.

Debug

A CRO object can be used to debug source code in the debugger. You must include debugging information in the CRR object to debug source code.

See CTR-SDK Build System Manual (for DLLs) or Guide to Developing a Build System (for DLLs) for information about including debugging information in a CRR object.

Special Uses

Explicit Imports and Exports

Explicit imports and exports are mainly used in combination to enable use of plug-in type DLLs.

Explicit Imports

To explicitly import a symbol, add NN_DLL_IMPORT to the beginning of the function or variable declaration.

extern NN_DLL_IMPORT int g_Variable;

extern NN_DLL_IMPORT void Function();

An explicitly imported symbol does not generate an error, even if this definition cannot be found at link time by the linker.
An explicitly exported symbol is not automatically imported, so you need to either explicitly import the symbol or get the pointer manually.

Explicit Exports

To explicitly export a symbol, add NN_DLL_EXPORT to the beginning of the function or variable declaration.

NN_DLL_EXPORT int g_Variable;

NN_DLL_EXPORT void Function()
{
    ... (omitted) ...
}

Explicitly exported symbols will be exported. If explicit exports are used in a dynamic module, dead-code stripping will not remove the symbols, even if they are not referenced in the module.

When variables are defined in multiple modules, processing is performed as if the variables are defined in static modules (see Handling of Symbols with Multiple Definitions). However, variables exported explicitly in any module will not be included in that processing. In short, they will be treated as separate variables that have their own separate memory regions in each module.

Special Functions

Some of the names exported by a DLL are handled in a special way. There are three such names: nnroProlog, nnroEpilog, and nnroUnresolved. If a function with one of these names is defined in a DLL and exported, it will be handled as follows.

Note: If it is a C++ function, you must add extern "C" to the definition. In order to export it, you must do so explicitly.

nnroProlog

If you define a function with the name nnroProlog in a CRO file, then nnroProlog will be called when nn::ro::Module::DoInitialize is called on that CRO file. This enables you to implement initialization unique to that DLL.

nnroProlog is called after DoInitialize has completed all initialization steps, including building global objects.

extern "C" NN_DLL_EXPORT void nnroProlog()
{
    ... (omitted) ...
}

nnroEpilog

If you define a function with the name nnroEpilog in a CRO file and export it, then nnroProlog will be called when nn::ro::Module::DoFinalize is called on that CRO file. This enables you to implement finalization unique to that DLL.

nnroEpilog is called before DoFinalize does any finalization, such as freeing global objects.

extern "C" NN_DLL_EXPORT void nnroEpilog()
{
    ... (omitted) ...
}

nnroUnresolved

If you define a function named nnroUnresolved in your CRO file and export it, then unresolved references from this file will be linked to this function. If you do so, nnroUnresolved will be called when a function is called, and the DLL in which it is defined has not been loaded.

extern "C" NN_DLL_EXPORT void nnroUnresolved()
{
    ... (omitted) ...
}

This particular function can also be used in static modules. However, there is a limitation. It will stop working if references, once they have been resolved, are reverted to the unresolved state.

Freeing Unneeded Memory

In addition to executable code, a CRO file contains information about symbols referenced from other modules, and symbols exported to other modules. If you know that this information will not be needed after the CRO file is loaded, then you can free it and use it for other purposes after LoadModule by specifying a fixLevel argument to nn::ro::LoadModule. This can greatly reduce your memory usage, particularly when the module references and/or exports a large number of symbols. Conversely, if the module does not have much of this information, then freeing it may not increase the available memory size at all.

const wchar_t* croPath = L"rom:/module1.cro";

size_t croSize  = GetFileSize(croPath);
void* pCro      = AllocateMemory(croSize, 0x1000);

LoadFile(pCro, croPath, croSize);

nn::ro::SizeInfo sizeInfo;
nn::ro::GetSizeInfo(&sizeInfo, pCro);

void* pBuffer   = AllocateMemory(sizeInfo.bufferSize, 8);
nn::ro::Module* pModule = nn::ro::LoadModule(
                            pCro, croSize, pBuffer, sizeInfo.bufferSize,
                            true, nn::ro::FIX_LEVEL_3);

void* pFreeBuffer = reinterpret_cast(sizeInfo.fix3End);
size_t freeSize   = croSize - (sizeInfo.fix3End - reinterpret_cast(pCro));

The code above made the freeSize portion of pFreeBuffer available.

Reusing CRO Objects

Normally, it is not possible to reuse a CRO module you passed to nn::ro::LoadModule after you have called nn::ro::Module::Unload on it. However, it is possible to reuse them if the following conditions are met.

  1. You use FIX_LEVEL_0
  2. It does not use initial values for static variables

Below are some ways you can satisfy condition 2 in your implementation:

const wchar_t* croPath = L"rom:/module1.cro";

size_t croSize  = GetFileSize(croPath);
void* pCro      = AllocateMemory(croSize, 0x1000);

LoadFile(pCro, croPath, croSize);

nn::ro::SizeInfo sizeInfo;
nn::ro::GetSizeInfo(&sizeInfo, pCro);

void* pBuffer   = AllocateMemory(sizeInfo.bufferSize, 8);

// Loaded first time
nn::ro::Module* pModule = nn::ro::LoadModule(
                            pCro, croSize, pBuffer, sizeInfo.bufferSize,
                            true, nn::ro::FIX_LEVEL_0);
pModule->DoInitialize();

    ... (use cro module) ...

// Free it
pModule->DoFinalize();
pModule->Unload();

// It can be reloaded without reading it from memory again
pModule = nn::ro::LoadModule(pCro, croSize, pBuffer, sizeInfo.bufferSize);
pModule->DoInitialize();

Revision History

2012/06/22
Added a link to Guide to Developing a Build System (for High-Level DLLs).
2012/04/23
Added the "Handling of Symbols with Multiple Definitions" section.
Updated description of handling of static variables in Restrictions section.
Added description about support for multiple definitions in "Creating the Source Code" in the "Creating Static Modules" section.
Added relationship with multiple definitions in the "Explicit Imports and Exports" section.
2012/02/27
Added specific descriptions to the Restrictions section regarding standard C functions and similar functions.
2012/01/23
Added note to the Restrictions section that static variables cannot be properly handled.
2011/12/12
Added the item "Restrictions" to the section "Features and Limitations of the ro format" and moved the information about this topic to this item.
Clarified in "Restrictions" that the SDK's static libraries must not be made and used as DLLs.
Added the section "Using Static Libraries".
Added the section "Creating Static Modules".
Updated the section "Special Functions" to reflect fact that nnroUnresolved can be used in static modules.
Touched up the section "Explicit Imports and Exports".
Corrected the "Explicit Imports" example.
2011/09/26
Updated the "Debugging" section to reflect debugging source code. Added the Revision History section.
2011/08/03
Initial version.

CTR-06-0201-002-G
CONFIDENTIAL