道可叨

Free Will

程序如何找自己

自我定位很重要,不但是对人,对程序来说也一样。如果能知道自己的路径位置,程序就可以通过相对定位找到身边的资源文件,这对于制作无需安装的绿色软件和各类插件很关键。下面列出几种程序的自我定位方法。

Windows API

可利用 Win32 API GetModuleHandleEx 来取得内存地址所在的程序路径。如果传入可执行文件自身的某个函数地址,那就能获得自己的路径。该方法如下,默认是取自身的路径:

std::wstring get_module_path(void* address=NULL)
{
    if (! address) {
        address = (void*)(&get_module_path);
    }

    HMODULE handle = NULL;
    BOOL const ret = ::GetModuleHandleExW(
        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
        //|GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
        static_cast<wchar_t*>(address),
        &handle);

    if (ret != 0 && handle != NULL) {
        wchar_t path_buffer[MAX_PATH] = {L'\0'};
        DWORD const ret = ::GetModuleFileNameW(handle, path_buffer, MAX_PATH);
        // We have to free it
        ::FreeLibrary(handle);
        if (0 != ret) {
            return path_buffer;
        }
    }

    return L""; // not found
}

这个方法也能在 DLL 中使用,让动态库也能获取自身的位置,方便插件的编写。

Unix 平台

与 Win32 类似,在 POSIX 中 dladdr 函数也可以获取某个地址所在可执行文件的路径:

std::string get_module_path(void* address=NULL)
{
    if (! address) {
        address = (void*)(&get_module_path);
    }

    ::Dl_info dl_info;
    dl_info.dli_fname = 0;
    int const ret = ::dladdr(address, &dl_info);
    if (0 != ret && dl_info.dli_fname != NULL) {
        return dl_info.dli_fname;
    }
    return "";
}

该方法同样能让 so 动态库找到自身的位置,方便插件的编写。

Python

Python 内置变量 __file__ 的值就是脚本文件位置,很方便。不过一旦脚本被 py2exe 这类程序打包后,`` __file__`` 就不准确了。这时就需要用 sys.argv[0] 甚至 sys.executable 变量来取得打包后的程序文件的位置

import imp
import os
import sys
import os.path

def is_frozen():
   return (hasattr(sys, "frozen") or # new py2exe
           hasattr(sys, "importers") # old py2exe
           or imp.is_frozen("__main__")) # tools/freeze

def get_self_path():
   if is_frozen():
       return os.path.abspath(sys.executable)
   return os.path.abspath(sys.argv[0])

Mac & iOS

Mac 和 iOS 平台上标准的 App 结构都是把可执行文件和资源文件打包在程序束 (bundle) 中,苹果还提供专门的 API 来取得 bundle 的路径,从这点上来说,苹果上的软件都是绿色的。不过如果需要,你还是可以取得自身的路径,

char path[1024];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
    printf("executable path is %s\n", path);
}
else {
    printf("buffer too small; need size %u\n", size);
}

这种方法不管是对传统的 Unix 可执行文件或是 Apple 自己标准的 App 都适用。

Mozilla Gecko

Firefox 的扩展都基于 Gecko 架构提供的插件机制。要得到扩展自身的路径应该很方便才是,但实际上并非如此:

Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID("your@addon.name", function(addon)
{
    var uri = addon.getResourceURI("relative/path/to/file");
    if (uri instanceof Components.interfaces.nsIFileURL)
    {
        // get the absolute path to the file inside Your@Addon.name
        var absolute_file_path = uri.file.path;
    }
});

这里,需要按扩展的名字去 AddonManager 中找出扩展对象,再通过扩展对象取得扩展包内相对位置的文件路径,使用起来不太方便。

其实,Gecko 中有更直接的方法。你可以在 manifest 文件里把需要引用的文件都声明成 resource,所有的 resource 都可以在扩展里通过 URI 名字直接获得路径

resource YOUR-ADDON-LIB path/to/libaddon.so ABI=Linux_x86-gcc3
resource YOUR-ADDON-LIB path/to/addon.dll ABI=WINNT_x86-msvc
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var uri = ioService.newURI('resource://YOUR-ADDON-LIB', null, null);
if (uri instanceof Components.interfaces.nsIFileURL) {
    var lib = ctypes.open(uri.file.path);
    /// ...
}

在不同平台上,同一名字还能对应不同的资源文件,方便跨平台扩展的开发。在上面例子中,Win32 平台 js-ctypes 会使用 addon.dll 而 Linux 上则会使用 libaddon.so,这一切都由 gecko 帮着选择定位。我在 chmfox 里正是使用了这个手法。