Fogeaters, Light The World.

03

2016-Mar

[리눅스/윈도우/C/C++] 플러그인 시스템 구현

작성자: title: MoonBlonix IP ADRESS: *.148.87.98 조회 수: 1993

특정 디렉토리 안에 존재하는 모든 라이브러리를 로드해 프로그램에서 사용하는 방법에 대한 글이다.

프로그램의 섬세한 모듈화가 가능하고 확장성 또한 매우 뛰어난 구조가 된다.


윈도우 관련 출처(C++) :: http://www.cplusplus.com/articles/48TbqMoL/

리눅스 관련 출처(C) :: http://eli.thegreenplace.net/2012/08/24/plugins-in-c


리눅스에서 라이브러리 사용 참고자료 :: http://fogeaters.cafe24.com/xe/board11/13584



===================================================윈도우 버전


Making a Plugin System

Score: 3.9/5 (186 votes)
*****
Now, lets say that you are making a program, maybe a game, and you decide that you want to make it moddable without your intervention. Now, of course, you think of how that might be possible, and without forcing the users to inject code directly into your executable, or modifying the source code directly. How would you do this?

Well, of course, the answer is a plugin system. I'll briefly explain how it works: A plugin system is simply where a specified folder is searched for DLLs (or the like), and if any are found, adds the contents to the program. Of course, because the program doesn't actually know what is in the DLLs, the normal way is for the DLL's to define a set entry point and calling functions defined by the program itself, which can then use the functionality exposed in those DLLs. The way this can be done is up to you, whether defining functions to implement or maybe getting the DLL to provide an instance of a base class, and then use the functionality from that. In this article, I am briefly going to demonstrate both of those options. First, though, lets look at how to actually load libraries at all.



Loading a library


Well, lets start with the basics. To load a DLL at runtime, simply call LoadLibrary, with the parameter being the file path of the DLL to load. However, when you think about this, this isn't much help, is it? We want to load a variable number of DLLs, whose names cannot be known at compile time. So, this means that we need to find all the DLLs that are plugins, and then load them.

Now, the easiest way of doing this is using WinAPI's FindFile functions, using a file mask to collect all the .dll files. Though, this can have the problem that you suddenly try loading the DLLs that your program needs to run! This is the reason why programs normally have a 'plugins' folder: If you tried to load all the DLLs just from the directory of your program, you might start trying to load non-plugin DLLs. The seperation into a specified plugin folder helps prevent this from happening.

Now, enough talking, here is some example code of how to loop through all the files in a directory and load the values for each:
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
// A list to store our DLL handles
std::vector<HINSTANCE> modules;
// The data for each file we find.
WIN32_FIND_DATA fileData;

// Find the first DLL file in out plugins folder,
// and store the data in out fileData structure.
HANDLE fileHandle = FindFirstFile(R"(.\plugins\*.dll)", &fileData);

if (fileHandle == (void*)ERROR_INVALID_HANDLE ||
    fileHandle == (void*)ERROR_FILE_NOT_FOUND) {
    // We couldn't find any plugins, lets just
    // return for now (imagine this is in main)
    return 0;
}

// Loop over every plugin in the folder, and store
// the handle in our modules list
do {
    // Load the plugin. We need to condense the plugin
    // name and the path together to correctly load the
    // file (There are other ways, I won't get into it here)
    HINSTANCE temp = LoadLibrary((R"(.\plugins\)" + 
                         std::string(fileData.cFileName)) .c_str());

    if (!temp) {
        // Couldn't load the library, continue on
        cerr << "Couldn't load library " << fileData.cFileName << "!\n";
        continue;
    }

    // Add the loaded module to our list of modules
    modules.push_back(temp);
// Continue while there are more files to find
} while (FindNextFile(fileHandle, &fileData));


Well, that is fairly complicated. Just thought I'd mention now, you do need a C++11 compiler to be able to compile these examples, otherwise some of the things like raw string literals will not compile. Also, if you use a Unicode compiler, you will need to specify that it is using wide strings.

Now, we have loaded all our plugins, but if we don't free them when we are done, we will cause a memory leak, and that can become a real problem in bigger projects. However, because we have stored all our handles in a vector, freeing them isn't actually that hard:
1
2
for (HINSTANCE hInst : modules)
    FreeLibrary(hInst);




Actually doing something with our library


OK, no we can load the libraries. The thing is, it doesn't actually do anything yet. Lets change that. For starters, we should define a header file for the DLLs to include: This defines the functions and classes that we want them to export. I've decided to show two things here: How to export a polymorphic class and how to export a function. Once you get the idea, though, most things are fairly easy. Anyway, lets define our header file:
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
#ifndef __MAIN_HPP_INCLUDED__
#define __MAIN_HPP_INCLUDED__

// includes we need
#include <string>
#include <memory>

// Test to see if we are building a DLL.
// If we are, specify that we are exporting
// to the DLL, otherwise don't worry (we
// will manually import the functions).
#ifdef BUILD_DLL
    #define DLLAPI __declspec(dllexport)
#else
    #define DLLAPI
#endif // BUILD_DLL

// This is the base class for the class
// retrieved from the DLL. This is used simply
// so that I can show how various types should
// be retrieved from a DLL. This class is to
// show how derived classes can be taken from
// a DLL.
class Base {
public:
    // Make sure we call the derived classes destructors
    virtual ~Base() = default;

    // Pure virtual print function, effect specific to DLL
    virtual void print(void) = 0;

    // Pure virtual function to calculate something, 
    // according to an unknown set of rules.
    virtual double calc(double val) = 0;
};


// Get an instance of the derived class
// contained in the DLL.
DLLAPI std::unique_ptr<Base> getObj(void);

// Get the name of the plugin. This can
// be used in various associated messages.
DLLAPI std::string getName(void);

#endif // __MAIN_HPP_INCLUDED__ 


Now, for the complicated bit. We need to load these functions from the DLL's that we loaded earlier. The function to do this is called GetProcAddress(), which returns a pointer to the function in the DLL with the name you specify. Because it doesn't know what type of function it is getting, however, we need to explicitly cast that pointer returned to a function pointer of the appropriate type. Add this code to the earlier example:
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
do {
    ...

    modules.push_back(temp);
    
    // Typedefs for the functions. Don't worry about the
    // __cdecl, that is for the name mangling (look up
    // that if you are interested).
    typedef std::unique_ptr<Base> (__cdecl *ObjProc)(void);
    typedef std::string (__cdecl *NameProc)(void);

    // Load the functions. This may or may not work, based on
    // your compiler. If your compiler created a '.def' file
    // with your DLL, copy the function names from that to
    // these functions. Look up 'name mangling' if you want
    // to know why this happens.
    ObjProc objFunc = (ObjProc)GetProcAddress(temp, "_Z6getObjv");
    NameProc nameFunc = (NameProc)GetProcAddress(temp, "_Z7getNamev");

    // use them!
    std::cout << "Plugin " << nameFunc() << " loaded!\n";
    std::unique_ptr<Base> obj = objFunc();
    obj->print();
    std::cout << "\t" << obj->calc() << std::endl;
} while (...);


And that is it for loading and using a plugin! You probably want to store the objects / names in their own lists, but it doesn't really matter, this is just an example.



Building the plugins


Now, there is one thing left: Actually building the plugins. This is very simple, comparitively. You need to #include "main.hpp" so as to get the classes, and then simply implement the functions. The main() function is the only thing you have to watch out for: For one, it isn't actually called main anymore! Here is just a basic main function (you don't normally need more than this):
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
extern "C" DLLAPI BOOL APIENTRY DllMain(HINSTANCE hInst, 
                                        DWORD reason, 
                                        LPVOID reserved)
{
    switch (reason) {
        case DLL_PROCESS_ATTACH:
            // attach to process, return FALSE to fail
        break;

        case DLL_PROCESS_DETACH:
            // detaching from process
        break;

        case DLL_THREAD_ATTACH:
            // attach to thread within process
        break;

        case DLL_THREAD_DETACH:
            // detach from thread within process
        break;
    }

    // return success
    return TRUE;
}




Getting the source code

Here I have posted a link to the source code for your convenience. This was compiled using the MinGW-w64 toolchain, but it should work for most Windows compilers. The examples require C++11 support (mostly for raw string literals and std::unique_ptr), and are developed with ANSI encoding in mind (rather than Unicode). This shouldn't make much of a difference, just go through and change the string literals to be long string literals, and use wide strings instead (std::wstring).

By way of project files, unfortunately I can't provide them all, there is merely a GCC makefile. To find out how to compile the project using your compiler, look at the documentation for that compiler. Probably the bit of information that you are least likely to know is how to generate a DLL, and how to define symbols when compiling. 

Download the source code 


Final Remarks

If you don't understand anything in this article, first check out MSDN (the MicroSoft Developer Network). In fact, I would have to quote MSDN as my main source of information for this article. Here are some links to relevant pages that you may be interested in looking at:

DLL loading functions:
File related functins:
Please let me know if there is anything unclear in this article, an error that you find, or you have any suggestions for updates to this article!



Update - Using the plugin system across different compilers

Due to this issue that has been encountered, I have decided to update this article slightly. The above has been kept the same, however. Basically, I will put how to fix issues such as using DLLs built by other compilers.

The Problem

If you use different compilers, different versions of the compiler, or even different settings on the same compiler, the DLL will be generated differently and may cause crashes with the application it is linked to. This is because C++ is not binary standardized - i.e. There is no requirement for the same source code on different compilers to behave in the same way. This is especially true of things like the C++ standard library, were different compilers can have different implementations, which can cause problems with the program.

Another thing that can change between compilers (and as a general rule, WILL change) is the name mangling of functions. In the example above, the function getObj was replaced with the following name: _Z6getObjv. However, this particular name is dependent on the compiler that produces it: This name was from a MinGW compiler, an MSVS compiler will produce something different, and an Intel compiler something else again. This can also cause problems.

Some Solutions

For the above problems, there are a few solutions. The first (non-solution) is just to always use the same compiler. This is useful for if you or your company are the only people providing plugins for this application, so you can ensure that you are using the same compiler settings as when you exported your primary application.

Another solution is to avoid using the standard library. The standard library is very useful, but due to different implementations it can cause problems with the usage of the objects: My compiler's std::string and another compilersstd::string might look and behave the same, but in reality can be very different on the inside, so using one instead of the other can cause problems. Possible workarounds are to pass the raw data associated with the object, rather than the object itself.

For example, you can still use std::string and std::vector<int> in your programs, but for the exported interface you would pass a const char* or an int* instead, and just convert to and from in the process. 

Which brings me to the final problem: The name mangling. C++ compilers will often mangle the names of functions and variables differently if the compiler has different options set (such as level of optimization or debug/release builds). However, C compilers do no name mangling, which means that the functions name will not change based on the compiler options. Here is how to say that we are exporting the functions with 'C' linkage:
1
2
3
4
5
6
7
8
9
10
#ifdef __cplusplus  // if we are compiling C++
extern "C" {        // export the functions with C linkage
#endif

// ... your DLL exported functions, e.g.
const char* getName(void);

#ifdef __cplusplus
}
#endif 

Then, when you implement the functions, you need to specify that you are using C linkage for them as well:
1
2
3
4
5
6
7
8
9
10
11
// ...

const std::string pluginName = "TestPlugin";

extern "C"
const char* getName(void) {
    // just extrapolating on information given above: we can still
    // use the C++ Standard Library within functions, just you can't
    // pass them as the return value or function arguments
    return pluginName.c_str();
}


This is to tell the compiler to use C linkage, which normally guarantees that all compilers will see the functions in the same way, and also has the bonus side effect of getting rid of a lot of the strange symbols that may otherwise appear. Of course, this means that you can only export C-style functions and structures, but that is the price to pay for the compatibility that you receive.

Attachments: [plugin-src.zip]













===================================================리눅스 버전



This is the second article in the series about plugin infrastructures.

In the kickoff article of this series, I defined some fundamental concepts we can use when talking about plugins. I also showed an example of a simple but complete plugin system for an application, all written in Python. But see, Python has the unfortunate (?) habit of making everything look too easy. To really show the guts of a plugin infrastructure, we'll switch to C.

C is the perfect "other extreme" to Python. It's the most low level of the mainstream programming languages, and almost universally serves as glue between other languages and systems. Understanding how plugins may work in C will help us understand how to implement cross-language plugins in the future.

Getting started - the task

I've re-implemented the htmlize program from the previous article entirely in C, including a plugin mechanism and the same two sample plugins. The full code is far too large to fit in an article; you can download it along with aMakefile for Linux from here [1].

Basics of plugins in C

Plugins in C are almost always implemented as DSOs (Dynamic Shared Objects, aka. shared libraries, or DLLs on Windows). While C itself is a relatively rigid language, DSOs provide it with a degree of dynamism that helps a lot for developing plugins [2]. Namely, the dynamic loading mechanism provided by the OS allows us to add and execute new code to our programs at runtime. The basic idea is:

  • The main application can load additional DSOs which represent plugins.
  • Each plugin has a well-known symbol (function and/or global variable) the application knows about and thus can load it dynamically from the DSO.
  • From here on, it's like any shared libary - the plugin can call into application code, and the application can call into plugin code.

The rest of the article will explain these topics in detail.

The fundamental concepts

The fundamental concepts of plugin infrastructures will help me explain how the C implementation of htmlizeworks. A quick reminder of the concepts:

  1. Discovery
  2. Registration
  3. Application hooks to which plugins attach
  4. Exposing application capabilities back to plugins

What follows is a detailed examination of how each concept is implemented in this example.

Discovery & registration

The main application has a known directory in which it looks for plugin DSOs. In my implementation this directory's location is relative to the working directory, but it could be anywhere, really. It can also be specified in some kind of configuration file - many applications follow this route.

Once it knows the directory, the application goes over all files in it and looks for files that appear to be plugins - files ending with the .so extension, the convention for DSOs on Linux. It then tries to load these files with dlopen. Here's the relevant portion of the code [3]:

// Make sure the path to dlopen has a slash, for it to consider it
// an actual filesystem path and not just a lookup name.
dstring slashedpath = dstring_format("./%s", dstring_cstr(fullpath));

// Attempt to open the plugin DSO
void* libhandle = dlopen(dstring_cstr(slashedpath), RTLD_NOW);
dstring_free(slashedpath);
if (!libhandle) {
    printf("Error loading DSO: %s\n", dlerror());
    return NULL;
}

The story doesn't end here, however. To register itself with the application, a valid plugin is expected to have an initialization function which the application will call. The function's name must be init_<pluginname> wherepluginname is the name of the plugin file without the .so extension. Take the tt.so plugin, for example. Its (non-static) initialization function must be named init_tt. This is the code that looks for the init function in the DSO:

// Attempt to find the init function and then call it
dstring initfunc_name = dstring_format("init_%s", dstring_cstr(name));
// dlsym returns void*, but we obviously need to cast it to a function
// pointer to be able to call it. Since void* and function pointers are
// mutually inconvertible in the eyes of C99, and -pedantic complains about
// a plain cast, we cast through a pointer-sized integer.
PluginInitFunc initfunc = (PluginInitFunc)
    (intptr_t) dlsym(libhandle, dstring_cstr(initfunc_name));
dstring_free(initfunc_name);
if (!initfunc) {
    printf("Error loading init function: %s\n", dlerror());
    dlclose(libhandle);
    return NULL;
}

The type PluginInitFunc is:

typedef int (*PluginInitFunc)(PluginManager*);

PluginManager is a central piece of the infrastructure; I will discuss it in more detail later. For now, it suffices to say that it is the interface between the application and plugins.

Anyhow, once the init function is successfully found in the plugin DSO, the application calls it, passing it a pointer to PluginManager. The init function is expected to return a non-negative value if everything is OK:

int rc = initfunc(pm);
if (rc < 0) {
    printf("Error: Plugin init function returned %d\n", rc);
    dlclose(libhandle);
    return NULL;
}

At this point, the plugin was discovered and has registered itself with the application - it was loaded from a shared library, and the initialization function was found and executed successfully.

All of the above is implemented in the plugin_discovery module (a pair of .h and .c files).

Application hooks

This is the place to discuss PluginManager. It's an object in the C sense of the word - the interface exposes an opaque data type and some functions that operate on it (it's all in plugin_manager.h/c).

PluginManager is used both by the application and by plugins. Plugins use it to register hooks. The application uses it to find all registered hooks and execute them. Similarly to the Python version of htmlize, there are two kinds of hooks - a hook for specific roles, and a hook for the whole contents. Here are the relevant callback function prototypes:

// Role hook. Will be called with: the role contents, DB and Post objects.
//
typedef dstring (*PluginRoleHook)(dstring, DB*, Post*);

// Contents hook. Will be called with: post contents, DB and Post objects.
//
typedef dstring (*PluginContentsHook)(dstring, DB*, Post*);

Note the DB and Post arguments - we'll discuss them later. These are the registration functions plugins can use to add hooks:

// Register a hook for a specific role.
// Note: rolename is copied to an internal data structure.
//
void PluginManager_register_role_hook(PluginManager* pm, dstring rolename,
                                      PluginRoleHook hook);

// Register a hook for contents.
//
void PluginManager_register_contents_hook(PluginManager* pm,
                                          PluginContentsHook hook);

This is the right time to show the full code of the tt.so plugin, which registers itself for the tt role, wrapping its contents in <tt>...</tt> tags:

static dstring tt_role_hook(dstring str, DB* db, Post* post) {
    return dstring_format("<tt>%s</tt>", dstring_cstr(str));
}


int init_tt(PluginManager* pm) {
    dstring rolename = dstring_new("tt");
    PluginManager_register_role_hook(pm, rolename, tt_role_hook);
    dstring_free(rolename);
    return 1;
}

The initialization function of the plugin (which, recall, must be called init_tt to be found) registers a role hook for the tt role with the plugin manager, and returns 1 for success. The hook itself is a simple function that performs the required transformation [4].

For completeness, this is the "application side" of the plugin manager API:

// Apply the registered role hooks to the given rolename/rolecontents,
// returning the string that should replace the role.
// The first plugin that agrees to handle this role is used. If no such plugin
// is found, NULL is returned.
//
dstring PluginManager_apply_role_hooks(PluginManager* pm,
                                       dstring rolename, dstring rolecontents,
                                       DB* db, Post* post);

// Apply the registered contents hooks to the given contents, returning
// the transformed contents.
// All registered hooks are composed:
//
//  while (has_plugins)
//      contents = apply_next_plugin(contents)
//
// If no contents plugin exists, NULL is returned.
//
dstring PluginManager_apply_contents_hooks(PluginManager* pm, dstring contents,
                                           DB* db, Post* post);

If you look into plugin_manager.c, you'll see that the implementation of these functions is pretty simple.PluginManager holds lists of registered hooks, and the PluginManager_apply_* functions simply walk these lists applying the hooks, when the application requests it.

Exposing application capabilities back to plugins

We've already seen an example of this above. The PluginManager API has a plugin-facing component for registering hooks, which technically is an application capability exposed to plugins. But there's more; I want to reimplement the same mock "database" API I used in the Python example, since it provides a very realistic example and is applicable in many situations.

At this point it's interesting to highlight an important difference between Python and C. In Python, due to duck typing, one module can just pass an object to another and the other module doesn't have to have any type information about this object - it can just call its methods. In C, things are not that easy. Therefore, to use the DBand Post objects, plugins need to include the application header file defining them (db.h). Note that due to the nature of dynamic linking on Linux, plugins don't have to actually link with the db.o object. More on this later.

To demonstrate this in code, here's a part of the narcissist.so plugin which turns all occurrences of "I" to "<b>I (username)</b>":

#include "db.h"

static dstring narcissist_contents_hook(dstring str, DB* db, Post* post) {
    dstring replacement = dstring_format("<b>I (%s)</b>",
                            dstring_cstr(Post_get_author(post)));
    ...
    ...
}

int init_narcissist(PluginManager* pm) {
    PluginManager_register_contents_hook(pm, narcissist_contents_hook);
    return 1;
}

All hooks get passed pointers to DB and Post objects. The plugin then uses the db.h API to access the Post object - in this case the Post_get_author function, which extracts the username from the post.

Some details of plugin implementation in C

The above concludes the description of how the htmlize application with its plugins is implemented in C. Here, I want to complete a few low-level implementation details that may be interesting to readers. These are things that make plugin implementation in C trickier than in Python, since in C you have to manually deal with much more details.

Exporting symbols from the application to plugins

Here are the compiler invocations required to build the tt.so plugin:

gcc -c plugins/tt.c -o plugins/tt.o -pedantic -g -Wall -std=c99 -fpic -I.
gcc -o plugins/tt.so plugins/tt.o -shared

This is a standard Linux DSO build: the sources are compiled with -fpic to generate position-independent code, and the DSO itself is built with -shared which tells the linker to create a shared library.

When creating a DSO with -shared, we don't have to link in object files that will be found in the application that loads the DSO. The plugin uses symbols from a number of object files - dstring.odb.oplugin_manager.o. Let's see how this looks in the symbol table:

$ readelf --dyn-syms plugins/narcissist.so

Symbol table '.dynsym' contains 23 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000798     0 SECTION LOCAL  DEFAULT    9
     2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND dstring_len
     3: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND dstring_new_len
    <snip>
     8: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND Post_get_author
    <snip>

This is the dynamic symbol table, a section used by the dynamic linker on Linux for symbol management. Here it says that the symbols dstring_lenPost_get_author and others and undefined. The dynamic linker will expect to find them in the application loading the DSO. Otherwise, we'll get a symbol resolution error at runtime.

There's an important gotcha here. The linker will not export symbols from an application to plugins by default. It has to be explicitly told to do so by means of the --export-dynamic linker flag. Here's a portion of the ld manual page that describes this flag very well:

--export-dynamic
--no-export-dynamic
    When creating a dynamically linked executable, using the -E
    option or the --export-dynamic option causes the linker to add
    all symbols to the dynamic symbol table.  The dynamic symbol
    table is the set of symbols which are visible from dynamic
    objects at run time.

    If you do not use either of these options (or use the
    --no-export-dynamic option to restore the default behavior),
    the dynamic symbol table will normally contain only those
    symbols which are referenced by some dynamic object mentioned
    in the link.

    If you use "dlopen" to load a dynamic object which needs to
    refer back to the symbols defined by the program, rather
    than some other dynamic object, then you will probably need
    to use this option when linking the program itself.

    You can also use the dynamic list to control what symbols
    should be added to the dynamic symbol table if the output
    format supports it. See the description of --dynamic-list.

This behavior is easy to observe in our example, if you're interested. The main application htmlize_main is currently compiled with the --export-dynamic flag. If you look at its dynamic symbol table (readelf --dyn-syms), you'll see all global symbols exported. Recompile it without the flag, and you can check that the dynamic symbol table won't contain these symbols, and the dlopen call in plugin_discovery.c will fail with "undefined symbol" errors.

Symbol visibility between plugins

We've seen that special provision is required for the application's symbols to be visible inside pligins. The same is true about symbol visibility between plugins, though here the mechanism is different.

When the application loads a plugin with dlopen, the plugin's symbols can be found by calling dlsym in the application. However, what if other plugins need to use these symbols as well? By default, that won't work. To make it work, it's possible to pass the RTLD_GLOBAL flag to dlopen when opening the plugin we want to expose the symbols from. Symbols in this plugin DSO will be made available to resolve references in subsequently loaded DSOs.

Cross-DSO memory allocation

It's not hard to see in the htmlize example that some memory allocated in one DSO (or the main application), is released in another. Especially if you come from a Windows background, this may raise an eyebrow.

Cross-DSO memory allocation is most likely wrong when the C library is linked statically. This is because each DSO gets its own version of the C library, with its own book-keeping for malloc et al, so memory allocated in one DSO can't be released in another.

However, on Linux it's customary to link the C library dynamically. This is what happens by default, unless youexplicitly request static linking. When linked dynamically, only a single version of the C library symbols exists in the process's address space during execution, and cross-DSO memory allocations and releases are safe.


profile
List of Articles
번호 제목 글쓴이 날짜 조회 수
공지 [Web] 클라우드 IDE + 2 title: MoonBlonix 2017-06-25 15125
52 OP Amp - 연산 증폭기 title: MoonBlonix 2016-03-07 1744
51 [아두이노] RC용 ESC(변속기) 작동방법 title: MoonBlonix 2016-03-06 1764
50 [AVR/아두이노] 수광 센서 file title: MoonBlonix 2016-03-06 1760
49 [리눅스] 정적, 공유, 동적 라이브러리 사용 title: MoonBlonix 2016-03-05 1328
48 [C/C++] Makefile 만들기 title: MoonBlonix 2016-03-05 1780
47 [C/C++] OS 구분 전처리기 매크로 title: MoonBlonix 2016-03-05 1564
46 [C++ STL] std::unique_ptr + 1 title: MoonBlonix 2016-03-05 1757
45 [AVR] 퓨즈비트 옵션 file + 1 title: MoonBlonix 2016-03-04 1456
44 [AVR] 외부클럭, 리셋회로 설계 file + 1 title: MoonBlonix 2016-03-04 1742
43 [AVR] 소프트웨어 UART 통신(Software Serial) file + 1 title: MoonBlonix 2016-03-04 1740
42 [AVR/아두이노] PIR 인체감지 센서 file title: MoonBlonix 2016-03-03 1914
41 [리눅스] 파일관리 라이브러리 함수 title: MoonBlonix 2016-03-03 1652
40 [리눅스] 리눅스 커널 API 모음 title: MoonBlonix 2016-03-03 1952
» [리눅스/윈도우/C/C++] 플러그인 시스템 구현 title: MoonBlonix 2016-03-03 1993
38 [라즈베리파이] UART 통신 file title: MoonBlonix 2016-02-25 1623
37 [리눅스] vi 편집기 명령어 title: MoonBlonix 2016-02-25 1523
36 [AVR] 서보모터 제어 file title: MoonBlonix 2016-02-21 1671
35 [AVR] 16MHz 클럭의 연산자별 연산속도 측정 title: MoonBlonix 2016-02-16 1689
34 [AVR] 입력받기 (풀업저항, 외부인터럽트) file + 1 title: MoonBlonix 2016-02-13 1996
33 [AVR] ADC (아두이노 analogread 사용) file + 2 title: MoonBlonix 2016-02-13 1706