Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Создание ярлыка

243
22 декабря 2005 года
pacific_7
1.9K / / 06.09.2004
Нужно создать ярлык. Да не просто ярлык, а ярлык указывающий на VPN-подключение. Есть ссылка: http://www.rsdn.ru/article/winshell/shortcuts.xml но то, что в ней написано я не очень понимаю, т.к. совершенно не умею работать с COM. К тому же, в качестве примера, по созданию ярлыка там дан как кусок программы. Заставить его работать отдельно у меня пока не получилось.
Есть еще ссылка: http://www.sources.ru/builder/faq/015.html - но там не выполняется самое первое условие и соответственно весь код ни к чему не ведет. :(
Хочется: что бы кто-нибудь растолковал, как все же можно сделать ярлык на WIN API, пусть с COM (без него ведь ни как?). Необходимый минимум. Пожалуйста не предлагать самому разобраться с COM - всему свое время, а время COM для меня еще не настало. Времени как раз не хватает, а ярлык сделать очень нужно. Но т.к. пока работаю совсем в другом направлении, то не успеваю разобраться сам. В общем - буду благодарен за любой дельный совет.
ЗЫ: Сюда: http://www.codenet.ru/progr/delphi/quest036.php - то же желательно не посылать, т.к. там вообще темный лес для меня. Но если уж посылать, то с комментариями по поводу кода.
398
22 декабря 2005 года
Alexandoros
630 / / 21.10.2005
Код:
#include <objidl.h>
#include <shlobj.h>


#include <windows.h>


#define HOTKEY(modifier,key) ((((modifier)&0xff)<<8)|((key)&0xff))



// Создание ярлыка
// Входные параметры:
//  pwzShortCutFileName - путь и имя ярлыка, например, "C:\\Блокнот.lnk"
//  Если не указан путь, ярлык будет создан в папке, указанной в следующем параметре.
//  Прим.: Windows сама НЕ добавляет к имени расширение .lnk
//  pszPathAndFileName  - путь и имя exe-файла, например, "C:\\Windows\\NotePad.Exe"
//  pszWorkingDirectory - рабочий каталог, например, "C:\\Windows"
//  pszArguments        - аргументы командной строки, например, "C:\\Doc\\Text.Txt"
//  wHotKey             - горячая клавиша, например, для Ctrl+Alt+A     HOTKEY(HOTKEYF_ALT|HOTKEYF_CONTROL,'A')
//  iCmdShow            - начальный вид, например, SW_SHOWNORMAL
//  pszIconFileName     - путь и имя файла, содержащего иконку, например, "C:\\Windows\\NotePad.Exe"
//  int iIconIndex      - индекс иконки в файле, нумеруется с 0
bool __fastcall CreateShortCut(
                               LPWSTR pwzShortCutFileName,
                               LPTSTR pszPathAndFileName,
                               LPTSTR pszWorkingDirectory,
                               LPTSTR pszArguments,
                               WORD wHotKey,
                               int iCmdShow,
                               LPTSTR pszIconFileName,
                               int iIconIndex)
{
                       IShellLink * pSL;
    IPersistFile * pPF;
    HRESULT hRes;
    if( CoInitialize(NULL) != S_OK)
        return false;
    // Получение экземпляра компонента "Ярлык"
    hRes = CoCreateInstance(CLSID_ShellLink, 0, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&pSL);

    if( SUCCEEDED(hRes) )
    {
        hRes = pSL->SetPath(pszPathAndFileName);
        if( SUCCEEDED(hRes) )
        {
            //hRes = pSL->SetArguments(pszArguments);
            //if( SUCCEEDED(hRes) )
            {
                hRes = pSL->SetWorkingDirectory(pszWorkingDirectory);
                if( SUCCEEDED(hRes) )
                {
                    hRes = pSL->SetIconLocation(pszIconFileName,iIconIndex);
                    if( SUCCEEDED(hRes) )
                    {
                    //  hRes = pSL->SetHotkey(wHotKey);
                    //  if( SUCCEEDED(hRes) )
                        {
                            hRes = pSL->SetShowCmd(iCmdShow);
                            if( SUCCEEDED(hRes) )
                            {
                                // Получение компонента хранилища параметров
                                hRes = pSL->QueryInterface(IID_IPersistFile,(LPVOID *)&pPF);
                                if( SUCCEEDED(hRes) )
                                {
                                    // Сохранение созданного ярлыка
                                    hRes = pPF->Save(pwzShortCutFileName,TRUE);
                                    pPF->Release();
                                }
                            }
                        }
                    }
                }
            }
        }
        pSL->Release();
    }
    return SUCCEEDED(hRes);

}  //bool __fastcall CreateShortCut


void main(void)
{
    //WSTR
    bool b = CreateShortCut(L"C:\\Note2.lnk", _T("D:\\Windows\\NotePad.Exe"), _T("D:\\Windows"), _T(""), 0,
                    SW_SHOWNORMAL, _T("D:\\Windows\\NotePad.Exe"),  0);
}


Чуть переделаный пример
243
22 декабря 2005 года
pacific_7
1.9K / / 06.09.2004
А ларчик просто открывался оказывается. :)
Благодарю! Всего-то у нас не хватало функции CoInitialize(). Не мудрено, что ничего не работало. Я как только ее название увидел - сразу понял что она делает. Все понятно стало, во всяком случае для данного примера.
Теперь осталось научиться делать то же самое для VPN-подключения. Но это я постараюсь сам.
243
23 декабря 2005 года
pacific_7
1.9K / / 06.09.2004
Блин, ниасилил. :(
Значит так, что бы сделать ярлык на не файловый обект нам надо привязать его к объекту через IShellLink::SetIDList() метод. Я сделал перечисление сетевых подключений, дело осталось за малым - получить параметр для SetIDList(). Как я понял, получить его можно при помощи IShellFolder::BindToObject(). Вот тут-то и загвоздка! Не получается - в последний параметр BindToObject возвращается NULL, что означает ошибку. Значит опять что-то неправильно понял. В приведенном коде удален за ненадобностью цикл перечисления всех подключений. Т.е. мы останавливаемся на первом найденом.
Делаем так:
Код:
LPMALLOC pMalloc;
    LPITEMIDLIST pidConnections = NULL;
    LPITEMIDLIST pidlItems = NULL;
    IShellFolder *psfFirstFolder = NULL;
    IShellFolder *psfDeskTop = NULL;
    IShellFolder *pConnections = NULL;
    LPENUMIDLIST ppenum = NULL;
    ULONG celtFetched;
    HRESULT hr;
    STRRET strDispName;
    TCHAR pszDisplayName[MAX_PATH];
    ULONG uAttr;

   
    CoInitialize( NULL );
    hr = SHGetMalloc(&pMalloc);

    hr = SHGetFolderLocation(NULL, CSIDL_CONNECTIONS, NULL, NULL, &pidConnections);//ВЫБИРАЕМ ПАПКУ "СЕТЕВЫЕ ПОДКЛЮЧЕНИЯ"

    hr = SHGetDesktopFolder(&psfDeskTop);

    hr = psfDeskTop->BindToObject(pidConnections, NULL, IID_IShellFolder, (LPVOID *) &pConnections);

    hr = pConnections->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ppenum);

    hr = ppenum->Next(1,&pidlItems, &celtFetched);// == S_OK && (celtFetched) == 1)
    pConnections->GetDisplayNameOf(pidlItems, SHGDN_INFOLDER, &strDispName);
    StrRetToBuf(&strDispName, pidlItems, pszDisplayName, MAX_PATH);
    rus_out(pszDisplayName);//ВЫВОДИМ ИМЯ ПОДКЛЮЧЕНИЯ
    if(!psfFirstFolder)
    {
        uAttr = SFGAO_FOLDER;//МОЖЕТ ТУТ ЧТО НАДО ПОСТАВИТЬ ДРУГОЕ?
        pConnections->GetAttributesOf(1, (LPCITEMIDLIST *) &pidlItems, &uAttr);
        hr = pConnections->BindToObject(pidlItems, NULL, IID_IUnknown, (LPVOID *) &psfFirstFolder);//КАК МНЕ КАЖЕТСЯ ДОЛЖНО РАБОТАТ, НО...
        if(!psfFirstFolder)printf("Error!\n");//ВОТ ТУТ ПОЛУЧАЕМ ОШИБКУ
        else printf("Ok!\n");
    }
    pMalloc->Free(pidlItems);

    printf("\n");

    ppenum->Release();
    pMalloc->Free(pidConnections);
    pMalloc->Release();
    pConnections->Release();
//    psfFirstFolder->Release();//ПОЧЕМУ ТО ВЫСКАКИВАЕТ ОШИБКА ПРИ РАБОТЕ С НЕФАЙЛОВЫМИ ОБЪЕКТАМИ

    CoUninitialize();

Где я снова накосячил?
406
23 декабря 2005 года
vitaly2003s
481 / / 27.07.2004
Цитата:
Originally posted by pacific_7
Блин, ниасилил. :(
Значит так, что бы сделать ярлык на не файловый обект нам надо привязать его к объекту через IShellLink::SetIDList() метод. Я сделал перечисление сетевых подключений, дело осталось за малым - получить параметр для SetIDList(). Как я понял, получить его можно при помощи IShellFolder::BindToObject(). Вот тут-то и загвоздка! Не получается - в последний параметр BindToObject возвращается NULL, что означает ошибку. Значит опять что-то неправильно понял. В приведенном коде удален за ненадобностью цикл перечисления всех подключений. Т.е. мы останавливаемся на первом найденом.
Делаем так:
Код:
LPMALLOC pMalloc;
    LPITEMIDLIST pidConnections = NULL;
    LPITEMIDLIST pidlItems = NULL;
    IShellFolder *psfFirstFolder = NULL;
    IShellFolder *psfDeskTop = NULL;
    IShellFolder *pConnections = NULL;
    LPENUMIDLIST ppenum = NULL;
    ULONG celtFetched;
    HRESULT hr;
    STRRET strDispName;
    TCHAR pszDisplayName[MAX_PATH];
    ULONG uAttr;

   
    CoInitialize( NULL );
    hr = SHGetMalloc(&pMalloc);

    hr = SHGetFolderLocation(NULL, CSIDL_CONNECTIONS, NULL, NULL, &pidConnections);//ВЫБИРАЕМ ПАПКУ "СЕТЕВЫЕ ПОДКЛЮЧЕНИЯ"

    hr = SHGetDesktopFolder(&psfDeskTop);

    hr = psfDeskTop->BindToObject(pidConnections, NULL, IID_IShellFolder, (LPVOID *) &pConnections);

    hr = pConnections->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ppenum);

    hr = ppenum->Next(1,&pidlItems, &celtFetched);// == S_OK && (celtFetched) == 1)
    pConnections->GetDisplayNameOf(pidlItems, SHGDN_INFOLDER, &strDispName);
    StrRetToBuf(&strDispName, pidlItems, pszDisplayName, MAX_PATH);
    rus_out(pszDisplayName);//ВЫВОДИМ ИМЯ ПОДКЛЮЧЕНИЯ
    if(!psfFirstFolder)
    {
        uAttr = SFGAO_FOLDER;//МОЖЕТ ТУТ ЧТО НАДО ПОСТАВИТЬ ДРУГОЕ?
        pConnections->GetAttributesOf(1, (LPCITEMIDLIST *) &pidlItems, &uAttr);
        hr = pConnections->BindToObject(pidlItems, NULL, IID_IUnknown, (LPVOID *) &psfFirstFolder);//КАК МНЕ КАЖЕТСЯ ДОЛЖНО РАБОТАТ, НО...
        if(!psfFirstFolder)printf("Error!\n");//ВОТ ТУТ ПОЛУЧАЕМ ОШИБКУ
        else printf("Ok!\n");
    }
    pMalloc->Free(pidlItems);

    printf("\n");

    ppenum->Release();
    pMalloc->Free(pidConnections);
    pMalloc->Release();
    pConnections->Release();
//    psfFirstFolder->Release();//ПОЧЕМУ ТО ВЫСКАКИВАЕТ ОШИБКА ПРИ РАБОТЕ С НЕФАЙЛОВЫМИ ОБЪЕКТАМИ

    CoUninitialize();

Где я снова накосячил?



Ну во первых у тебя нужный PIDL уже есть после
hr = ppenum->Next(1,&pidlItems, &celtFetched);
В принципе pidlItems это и есть то что нужно то есть идентификатор нужного тебе обекта

а дальнейшие твои действия с

if(!psfFirstFolder)
{
uAttr = SFGAO_FOLDER;//МОЖЕТ ТУТ ЧТО НАДО ПОСТАВИТЬ ДРУГОЕ?
pConnections->GetAttributesOf(1, (LPCITEMIDLIST *) &pidlItems, &uAttr);
hr = pConnections->BindToObject(pidlItems, NULL, IID_IUnknown, (LPVOID *) &psfFirstFolder);//КАК МНЕ КАЖЕТСЯ ДОЛЖНО РАБОТАТ, НО...
if(!psfFirstFolder)printf("Error!\n");//ВОТ ТУТ ПОЛУЧАЕМ ОШИБКУ
else printf("Ok!\n");
}

Нужны были бы только в случае наличия подпапок в Сетевых подключениях,чего не может быть,соответсвенно и все это действовать не будет,да и ктомуже ты в
hr = pConnections->BindToObject(pidlItems, NULL, IID_IUnknown, (LPVOID *) &psfFirstFolder);
указываеш IID_IUnknown хотя должен был бы указать IID_IShellFolder.

Вобщем после перечеслинея и нахождения нужного PIDL ты можеш получить путь к обекту

hr = ppenum->Next(1,&pidlItems, &celtFetched);
pConnections-GetDisplayNameOf(pidlItems,SHGDN_FORPARSING, &strDispName);
StrRetToBuf(&strDispName, pidlItems,szDisplayName, MAX_PATH);
SFGAOF sfg=0;
int u=MultiByteToWideChar(CP_ACP,MB_COMPOSITE,pszDisplayName,-1,lpchw,MAX_PATH);
hr=SHParseDisplayName(lpchw,0,&pidlItems2,SFGAO_LINK,&sfg);//по идее здесь должны мы получить полный PIDL но почему то это не работает

243
25 декабря 2005 года
pacific_7
1.9K / / 06.09.2004
Ура осилил! Спасибо всем! Как говорится: всем миром победим.
vitaly2003s - ты был не совсем прав, надо не путь получить, или имя, а именно PIDL объекта, при чем полный. GetDisplayNameOf дает нам не путь, а только имя которое выводится на обозрение пользователю. Т.е. в оптимальном варианте - это лишь часть пути, как например для Program Files будет "Program Files", а не "C:\Program Files". А если мы имеем дело с виртуальными объектами вроде подключений, то это даже не часть пути.
Полный PIDL в моем случае получается при сложении PIDL папки "Сетевые подключения" и PIDL искомого подключения.
Но все равно большое спасибо, а то я долго наверное еще тупил бы пытаясь получить то, что у меня уже есть :).
Вот код:
Код:
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>

void rus_out(char *str)
{
char out_str[255];
CharToOem(str,out_str);
printf("%s\n",out_str);
return;
}

LPMALLOC pMalloc;

bool __fastcall CreateShortCut(LPWSTR pwzShortCutFileName, LPCITEMIDLIST pidl,
                               LPTSTR pszWorkingDirectory, WORD wHotKey, int iCmdShow);
LPITEMIDLIST Append(LPCITEMIDLIST pidlBase, LPCITEMIDLIST pidlAdd);
void short_cut_startup(char *connection_name, LPWSTR link_name);

main()
{
    short_cut_startup("connection_name",L"shortcut_file_name.lnk");
    return 0;
}

//ОСНОВНАЯ ФУНКЦИЯ СОЗДАНИЯ ЯРЛЫЧКА НА РАБОЧЕМ СТОЛЕ
void short_cut_startup(char *connection_name, LPWSTR link_name)
{
    LPITEMIDLIST pidConnections = NULL;
    LPITEMIDLIST pidlItems = NULL;
    LPITEMIDLIST pidlDesk = NULL;

    IShellFolder *psfFirstFolder = NULL;
    IShellFolder *psfDeskTop = NULL;
    IShellFolder *pConnections = NULL;
    LPENUMIDLIST ppenum = NULL;
    ULONG celtFetched;
    HRESULT hr;
    STRRET str_curr_connection_name;
    TCHAR curr_connection_name[MAX_PATH]="";//ИМЯ ПОДКЛЮЧЕНИЯ
    TCHAR desktop_path[MAX_PATH]="";//ПУТЬ К РАБОЧЕМУ СТОЛУ
    TCHAR full_link_name[MAX_PATH]="";
    LPITEMIDLIST full_pid;

   
    CoInitialize( NULL );
    //ВЫДЕЛЯЕМ ПАМЯТЬ ДЛЯ РАБОТЫ С ОБЕКТАМИ Namespace
    hr = SHGetMalloc(&pMalloc);
    hr = SHGetFolderLocation(NULL, CSIDL_CONNECTIONS, NULL, NULL, &pidConnections);

    //УЗНАЕМ ПУТЬ К РАБОЧЕМУ СТОЛУ
    SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktop_path);


    hr = SHGetDesktopFolder(&psfDeskTop);
    hr = psfDeskTop->BindToObject(pidConnections, NULL, IID_IShellFolder, (LPVOID *) &pConnections);
    hr = pConnections->EnumObjects(NULL,SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ppenum);

    //ВОТ ТУТ МОЖНО ЗАМУТИТ ЦИКЛ
    while(hr = ppenum->Next(1,&pidlItems, &celtFetched) == S_OK && (celtFetched) == 1)
    {
        pConnections->GetDisplayNameOf(pidlItems, SHGDN_INFOLDER, &str_curr_connection_name);
        StrRetToBuf(&str_curr_connection_name, pidlItems, curr_connection_name, MAX_PATH);
        if(!strcmp(curr_connection_name,connection_name))break;
    }
    //СКЛАДЫВАЕМ ПОЛУЧЕННЫЕ PIDLs
    full_pid=Append(pidConnections,pidlItems);
    SetCurrentDirectory(desktop_path);
    CreateShortCut(link_name,full_pid,"C:\\windows",0,SW_SHOWNORMAL);
    printf("\n");
    ppenum->Release();
    pMalloc->Free(pidlItems);
    pMalloc->Free(pidConnections);
    pMalloc->Release();
    pConnections->Release();
    CoUninitialize();
}


bool __fastcall CreateShortCut(
   LPWSTR pwzShortCutFileName,
   LPCITEMIDLIST pidl,
   LPTSTR pszWorkingDirectory,
   WORD wHotKey,
   int iCmdShow
)
{
    IShellLink * pSL;
    IPersistFile * pPF;
    HRESULT hRes;
    // Получение экземпляра компонента "Ярлык"
    hRes = CoCreateInstance(CLSID_ShellLink, 0,CLSCTX_INPROC_SERVER,
                            IID_IShellLink, (LPVOID *)&pSL);
    if( SUCCEEDED(hRes) )
    {
        hRes=pSL->SetIDList(pidl);
        if(SUCCEEDED(hRes))
        {
            hRes = pSL->SetHotkey(wHotKey);
            if( SUCCEEDED(hRes) )
            {
                hRes = pSL->SetShowCmd(iCmdShow);
                if( SUCCEEDED(hRes) )
                {
                    // Получение компонента хранилища параметров
                    hRes = pSL->QueryInterface(IID_IPersistFile,(LPVOID *)&pPF);
                    if( SUCCEEDED(hRes) )
                    {
                        // Сохранение созданного ярлыка
                        hRes = pPF->Save(pwzShortCutFileName,TRUE);
                        if( SUCCEEDED(hRes) ) printf("Save successed!\n");
                        else printf("Save error!\n");
                        pPF->Release();
                    }// else printf("Error 4\n");
                }// else printf("Error 3\n");
            }// else printf("Error 2\n");
        }// else printf("Error 1\n");
        pSL->Release();
    }// else printf("Error 0\n");
    return SUCCEEDED(hRes);
}

//************************************************************
//ФУНКЦИИ ДЛЯ РАБОТЫ С PIDLs - тупо скопированы с http://msdn.microsoft.com

LPITEMIDLIST GetNextItemID(LPCITEMIDLIST pidl)
{
   // Check for valid pidl.
   if(pidl == NULL)
      return NULL;

   // Get the size of the specified item identifier.
   int cb = pidl->mkid.cb;

   // If the size is zero, it is the end of the list.
   if (cb == 0)
      return NULL;

   // Add cb to pidl (casting to increment by bytes).
   pidl = (LPITEMIDLIST) (((LPBYTE) pidl) + cb);

   // Return NULL if it is null-terminating, or a pidl otherwise.
   return (pidl->mkid.cb == 0) ? NULL : (LPITEMIDLIST) pidl;
}
//УЗНАЕМ РАЗМЕР ЗАДАННОЙ PIDL
UINT GetSize(LPCITEMIDLIST pidl)
{
    UINT cbTotal = 0;
    if (pidl)
    {
        cbTotal += sizeof(pidl->mkid.cb);    // Terminating null character
        while (pidl)
        {
            cbTotal += pidl->mkid.cb;
            pidl = GetNextItemID(pidl);
        }
    }
    return cbTotal;
}
//СКЛАДЫВАЕМ PIDLs
LPITEMIDLIST Append(LPCITEMIDLIST pidlBase, LPCITEMIDLIST pidlAdd)
{
    if(pidlBase == NULL)
        return NULL;
    if(pidlAdd == NULL)
        return (LPITEMIDLIST)pidlBase;
   
    LPITEMIDLIST pidlNew;

    UINT cb1 = GetSize(pidlBase) - sizeof(pidlBase->mkid.cb);
    UINT cb2 = GetSize(pidlAdd);

    pidlNew = (LPITEMIDLIST)pMalloc->Alloc(cb1 + cb2);
    if (pidlNew)
    {
        CopyMemory(pidlNew, pidlBase, cb1);
        CopyMemory(((LPSTR)pidlNew) + cb1, pidlAdd, cb2);
    }
    return pidlNew;
}

Если в нем что криво и неправильно, и не лень разбираться, то любые предложения и исправления будут восприняты с благодарностью. Я щас уже не в состоянии.
Главное сейчас - он работает!
406
25 декабря 2005 года
vitaly2003s
481 / / 27.07.2004
Я впринципе и делал то же что и ты только другими путями пытался получить этот полный PIDL,но стандартными функциями эт не выходило. А так у тебя щя код ниче,вполне приемлем в данной ситуации. Мог бы впринципе ты выделить все это в класс,думаю позднее он бы те еще не раз пригодился,и сделать его более универсальным.
243
25 декабря 2005 года
pacific_7
1.9K / / 06.09.2004
Цитата:
Originally posted by vitaly2003s
А так у тебя щя код ниче,вполне приемлем в данной ситуации. Мог бы впринципе ты выделить все это в класс,думаю позднее он бы те еще не раз пригодился,и сделать его более универсальным.


Да наверное, надо причесать его и попросить в FAQ выложить, или еще куда, а то я весь инет перерыл (по крайней мере русский) пытаясь найти как это делается. В итоге пришлось самому делать :)

15K
14 февраля 2006 года
GrayWolf
4 / / 11.01.2006
Сразу скажу, что этот код не работает на Visual Studio 6 SP5. Система "ругается" на отсутствие в заголовке идентификатора CSIDL_CONNECTIONS, что легко лечится включением соответствующей константы, но при входе в цикл перебора имеющихся подключений, система (WXP SP2) начинает дико "глючить". Может под SP6 код будет работать, но он мне пока недоступен. Появится - обязательно протестирую. Под Delphi 2005 проходит на ура!
243
14 февраля 2006 года
pacific_7
1.9K / / 06.09.2004
Цитата:
Originally posted by GrayWolf
Сразу скажу, что этот код не работает на Visual Studio 6 SP5.


Ну, ХЗ, я в VC 7.0 делал и XP SP2. Хотя работает нормально во всех виндах начиная с 2000-ной - точно. Может и просто в NT нормально. Не знаю, не где проверить, да и не актуально уже.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог