LINUX.ORG.RU

Что есть интерфейсы на Си?


0

3

Доброго всем времени суток, интересует такой вопрос, как реализовать интерфейсы на Си(например как в С++ IApplication), как они вообще реализуются и что это будет, это будет какая-либо структура или просто набор функций? К примеру:

struct IApplication {

   void (*load)(void);
   void (*shutdown)(void);

};

Объясните на пальцах :)

★★★★★

Последнее исправление: xterro (всего исправлений: 1)

>это будет какая-либо структура или просто набор функций?

ну, как сделаешь, так и будет.
хочешь полиморфизм - структура, если нет - набор функций залепил, и все.

k0l0b0k ★★
()
Ответ на: комментарий от k0l0b0k

А как тогда с объектом, который реализует этот интерфейс? Что ему делать? Если в С++ я наследуюсь от интерфейса и перегружаю его методы, то тут как быть, наследования же нет?

xterro ★★★★★
() автор топика

руки прочь от сишечки, проклятые шарперы!!

Интерфейсы - это защита от дурака. Мол, забыл какой-то метод определить - и бац, ошибка компиляции.

Никто не запрещает написать комментом

/* implements that interface */
и поналепить тот набор функций, который нужен для реализации того или иного интерфейса. Компилироваться будет так же

yoghurt ★★★★★
()
Ответ на: комментарий от xterro

Есть тут наследование, вы просто не умеете его готовить (с)

yoghurt ★★★★★
()
Ответ на: комментарий от xterro
struct Base
{
// your pointers
};

struct Derived
{
    struct Base myPrivateBase;
// + заполняешь myPrivateBase своими указателями
};

если для десктопа, то лучше возьми GLib. а вообще городить ООП на C - моветон (щас меня будут бить, но это имхо)

k0l0b0k ★★
()
Ответ на: комментарий от xterro

в смысле?

static void my_load(void) {
    ...
}

static void my_shutdown(void) {
    ...
}

struct IApplication {
   .load = my_load,
   .shutdown = my_shutdown,
} MyObject;

в лучших традициях ядра линукса же :)

arsi ★★★★★
()
Ответ на: комментарий от xterro
struct IApplication {
   void (*load)(void);
   void (*shutdown)(void);
};

struct MyApplication {
   struct IApplication;
   void (*myFunc)(void);
};
kulti ★★
()
Ответ на: комментарий от yoghurt

>и поналепить тот набор функций, который нужен для реализации того или иного интерфейса. Компилироваться будет так же

будет лучше. накладники на indirect call никто не отменял еще.

k0l0b0k ★★
()
Ответ на: комментарий от k0l0b0k

Просто работаю над сервером приложений для трёхзвенки(для своей небольшой системки), делаю на си, и решил продумать систему интерфейсов, в том числе и для поддержки плагинов, а вот как это в си будет выглядеть не представляю, посему разрабатываю этот вопрос...

xterro ★★★★★
() автор топика
Ответ на: комментарий от k0l0b0k

будет лучше

Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

unsigned ★★★★
()
Ответ на: комментарий от xterro

> а ядре линукса так сделано?

да, местами. файловый в/в, например, так реализован (struct file_operations выступает как интерфейс).

arsi ★★★★★
()
Ответ на: комментарий от xterro

не, вопрос верный, годно.
просто почему не C++? делать руками ту работу, которую делает за тебя компилятор - не самое лучшее решение.

k0l0b0k ★★
()
Ответ на: комментарий от unsigned

>Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

ну само собой. но ТС спрашивает же про оба варианта?

k0l0b0k ★★
()
Ответ на: комментарий от k0l0b0k

просто почему не C++?

А в C++ нет интерфейсов %

но ТС спрашивает же про оба варианта?

Для плагинов выбора вроде и нет.

unsigned ★★★★
()
Ответ на: комментарий от unsigned

вам видимо ключевого слова interface недостает?
абстрактные классы вполне подходят для понятия интерфейса.

k0l0b0k ★★
()
Ответ на: комментарий от k0l0b0k

Недостает. Интерфейс - не то же, что и класс, пусть даже абстрактный, а реализация - не то же, что наследование. Это работает на практике, но криво в теории )

unsigned ★★★★
()
Ответ на: комментарий от yoghurt

> просто почему не C++?

Начал уж на си, переделывать не хочется. Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си :)

xterro ★★★★★
() автор топика

Нормально сделать нельзя. В кривом^WЪ-Си стиле - см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

tailgunner ★★★★★
()
Ответ на: комментарий от unsigned

>Недостает. Интерфейс - не то же, что и класс, пусть даже абстрактный, а реализация - не то же, что наследование. Это работает на практике, но криво в теории )

не то же. но и не хуже. тот же indirect call. короче мужики пишут, и не смущаются :)

k0l0b0k ★★
()
Ответ на: комментарий от xterro

> Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си :)

Попытка троллинга не засчитана - твоя квалификация очевидно слишком низка, чтобы троллить на такую тему ;)

tailgunner ★★★★★
()

Сторонники ООП меня заплюют сейчас, но я скажу, что интерфейсы нужны только в 10% случаев, в которых их применяют. Так что если тебе хочется их сделать, подумай ещё раз.

Может быть они тебе таки не нужны.

P.S. хорошая подробная докуменация к написанию плагина (с указанием того, что и как надо писать, чтобы работало) гораздо эффективнее, чем ругань компилятора. Всё равно без документации/просмотра кода никто не полезет писать эти плагины. А если и напишет, то у него оно работать не будет.

anonymous
()
Ответ на: комментарий от tailgunner

Тролить даже не пытался и не собираюсь, да и имел ввиду реализацию в рамках моей задачи...

Нормально сделать нельзя. В кривом^WЪ-Си стиле - см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует... хммм ???

xterro ★★★★★
() автор топика

Давно волнует вопрос, как на си сделать реалиацию двух и более интерфейсов в одном типе. Кто-нибудь извра^W занимался?

unsigned ★★★★
()
Ответ на: комментарий от xterro

да и имел ввиду реализацию в рамках моей задачи...

Неважно, в рамках чего. Си++ позволяет решить задачу прозрачным и типобезопасным способом.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует... хммм ???

У тебя и его нет. Но вообще, если уж хочешь ООП на Си, то это делается так:

struct ops {
  void (*foo)(void *obj, int arg1 /* итд */);
};

struct obj {
   void *data;
   struct ops *ops;
};

obj->ops->foo(obj->data);
tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Как же реализовать модульную структуру без «интерфейсов»? Думаю здесь они как раз нужны...

xterro ★★★★★
() автор топика

Говорят, что интерфейсы это *.h файлы, тогда как в *.c - реализация. Получается КИС модель с деревом включения вроде:

        act.h           -- общий итерфейс
        /  \
       /    \
    anim.h   \          -- интерфейс между
     /  \     \            cat & dog
    /    \     \
cat.c  dog.c   voice.c  -- реализация
quasimoto ★★★★
()
Ответ на: комментарий от xterro

Если ABI сломать боишься ты и инкапсуляцию нарушить страшно тебе, не пиши код на C

ACR
()
Ответ на: комментарий от quasimoto

Наверно эта модель узкоспецифична, либо обобщена, например у нас кошка не может лаять, а собака наоборот, мяукать, тогда если в anim.h определить два метода: bark() и mew() к примеру, тогда получается что их надо реализовать и в cat.c и в dog.c получится что собака сможет мяукать... хотя модель интересная :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

Наверно эта модель узкоспецифична

Наоборот, это то что играет роль интерфейсов в си. Структуры содержащие функции в качестве полей это немного другое - интерфейс в свете наследования. У меня был абстрактный пример на тему КИС (клиент-интерфейс-сервер), если его немного переделать, то получится так:

                     main.h          // системный интерфейс
                     /   \
                    /     main.c     // entry point
                   /
           animals.h                 // звериный интерфейс, объекты, аккесоры, алокаторы
           /   |   \
          /    |    dog.c            // реализация - конструкторы,
         /   cat.c                   //              статические методы
        /
  actions.h                          // интерфейс действий
     |
   voice.c                           // полиморфный метод

Опять дерево - узлы это файлы интерфейса, листы - файлы реализаций, в каждый файл включается (include) только один предок. Получается модель проектирования сверху вниз - если сразу сделать эту схему неправильно, то как ни старайся - не поедет (т.к. нужно следовать правилам включения интерфейсов - будут ошибки), а если сделать правильно и следовать правилу включения - всё будет нормально. В принципе тут даже header guards не нужен - но чтобы привести пример работы я подключил в main.c интерфейсы (т.е. уже граф), ну и сделал #pragma once.

/*
 * animals.h -- interface for animals
 */

#pragma once

#include "main.h"

typedef char*  String;

/* objects */

typedef enum {
  CAT,
  DOG
} Animals;

typedef struct {
  Animals type;
  String  name;
} Animal;

/* cats */

typedef enum {
  WHITE,
  BLACK,
  GRAY
} Colors;

typedef struct {
  Animals type;
  String  name;
  Colors  color;
} Cat;

/* dogs */

typedef enum {
  MASTIFF,
  BULLTERRIER,
  MONGREL
} Breeds;

typedef struct {
  Animals type;
  String  name;
  Breeds  breed;
} Dog;

/* accessors */

#define type(X)   (((Animal*) (X))->type)
#define name(X)   (((Animal*) (X))->name)

#define color(X)  (((Cat*)    (X))->color)

#define breed(X)  (((Dog*)    (X))->breed)

/* macro routins for constructors */

#define ALLOCATE_IT_ANIMAL(TYPE)       \
  TYPE *IT;                            \
  IT = (TYPE*)malloc(sizeof(TYPE))

#define RETURN_IT_ANIMAL               \
  return (Animal*) IT

/* predefinitions */

extern Animal *make_cat(String name, Colors color);
extern Animal *make_dog(String name, Breeds breed);

extern String breed_string(Dog *dog);
extern String color_string(Cat *cat);
/*
 * actions.h -- actions interface
 */

#pragma once

#include "animals.h"

extern void voice(Animal *animal);
/*
 * main.h -- system interface
 */

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <error.h>
/*
 * cat.c -- cat's close friends
 */

#include "animals.h"

Animal*
make_cat(String name, Colors color) {
  ALLOCATE_IT_ANIMAL(Cat);
  IT->type  = CAT;
  IT->name  = name;
  IT->color = color;
  RETURN_IT_ANIMAL;
}

String
color_string(Cat *cat) {
  switch (color(cat)) {
    case WHITE: return "White";
    case BLACK: return "Black";
    case GRAY:  return "Gray";
  }
  error(-1, 1, "Bad Cat in color_string");
  return NULL;
}
/*
 * dog.c -- dog's close personal friends
 */

#include "animals.h"

Animal*
make_dog(String name, Breeds breed) {
  ALLOCATE_IT_ANIMAL(Dog);
  IT->type  = DOG;
  IT->name  = name;
  IT->breed = breed;
  RETURN_IT_ANIMAL;
}

String
breed_string(Dog *dog) {
  switch (breed(dog)) {
    case MASTIFF:     return "Mastiff";
    case BULLTERRIER: return "Bullterrier";
    case MONGREL:     return "Mongrel";
  }
  error(-1, 2, "Bad Dog in breed_string");
  return NULL;
}
/*
 * voice.c -- voice (poly) method
 */

#include "actions.h"

void
voice(Animal *animal) {
  switch (type(animal)) {
    case CAT:
      printf("%s cat %s say mew ^_^\n",
             color_string((Cat*)animal),
             name(animal));
      break;
    case DOG:
      printf("%s dog %s say bark >.<\n",
             breed_string((Dog*)animal),
             name(animal));
      break;
    default:
      error(-1, 1, "Bad animal!");
  }
}
/*
 * main.c -- the `main' entry point
 */

#include "main.h"

#include "animals.h"
#include "actions.h"

int
main() {

  Animal *cat = make_cat("Barz", GRAY);
  Animal *dog = make_dog("Barboz", MASTIFF);

  voice(cat);
  voice(dog);

  free(cat);
  free(dog);

  return 0;
}
#
# Makefile for the `c-iface' example
#

.PHONY: all clean

TARGET=c-iface

CC        = gcc
LINKFLAGS = -g
CFLAGS    = -c -g -Wall -O3

SRCS = cat.c dog.c voice.c main.c

OBJS = $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) ${LINKFLAGS} -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) $^

clean:
	-rm -f *.o *~ $(TARGET)
$ make all
gcc -c -g -Wall -O3 cat.c
gcc -c -g -Wall -O3 dog.c
gcc -c -g -Wall -O3 voice.c
gcc -c -g -Wall -O3 main.c
gcc -g -o c-iface cat.o dog.o voice.o main.o

$ ./c-iface
Gray cat Barz say mew ^_^
Mastiff dog Barboz say bark >.<
quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Интересный пример, спасибо :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от quasimoto

Знатный изврат!

Надо делать примерно так, как предложил arsi: объект - экземпляр структуры с указателями на функции, принимающими указатель на этот объект в качестве аргумента.

Sorcerer ★★★★★
()
Ответ на: комментарий от quasimoto

> это то что играет роль интерфейсов в си.

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си, которые я встречал. а интерфейсы — это более абстрактная сущность. в с++, например, «настоящих» интерфейсов нет. в c++/qt уже что-то похожее прорезается (но с++ удачно пихает палки в колёса qt, так что это ещё далеко не то, чего хотелось бы). более-менее юзабильные интерфейсы есть в жабе, но и там много чего не хватает «для полного счастья».

arsi ★★★★★
()
Ответ на: комментарий от tailgunner

Давно не встречал такого хорошего примера, как не надо делать.

Рад стараться.

quasimoto ★★★★
()
Ответ на: комментарий от arsi

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си

Велосипед не мой, но всё равно не понятно что тут страшного - ещё немного и будет tagged pointers, которые довольно часто используются. Не говоря уже о том, что если не следовать правилу включения заголовков - вообще нифига не получится написать. В общем, это другие интерфейсы (тоже, не я их так назвал).

quasimoto ★★★★
()

На лоре куча тредов «на C сделать как в [язык более высокого уровня]». Читаю и поражаюсь что с людьми современное образование делает. Вдолбили вам ООП, но забыли сказать что это набор ОГРАНИЧЕНИЙ которые повышают(иногда) производительность труда програмиста. Эти ограничени придумали другие програмисты, до вас, и воплотили в виде всяких языков. Чтоб язык говорил error когда вы идеологию нарушаете. Все программерские техники/идеологии это договоренность програмиста с самим собой/коллективом/сообщесвом. C дает свободу. Вы работаете один? Выдумайте свою идеологию/набор ограничений и, для простоты, запихайте ее в имена функций/переменных. Конечно компилятор за вас ваши штаны поддерживать не будет когда вы свои ограничения нарушите. Ну напишите препроцессор как Qt'шники. Пока вы этим будете заниматься поймете зачем нужны интерфейсы, чтоб не говорить «хочу как там» и поймете что реализация интерфейсов, для вашего проекта не нужна и что они хороши(иногда) когда их за вас сочинили.

Плагин - вот ведь мерзкое слово. Все кругом бегают «хотим плагинов, хотим плагинов». А зачем они? Чтоб придать расширяемость закрытому коду. А что они такое? Какоето API реализованое в ста местах сотней способов. Слово плагин ничего не говорит, кроме того, что продукт будет продаваться, и покупатель будет иметь возможность продукт расширять не видя кода. Плагины не нужны(вредны) если есть исходники и документация. Судя по тому что вы пишете "(для своей небольшой системки)" ...

Структуры в C надо применять только в одном случае - когда надо кудато передать кучу переменных скопом. Когда вы спрашиваете «может из структур залудить как в том ООП фреймворке» вам дают советы как. Советы правильные. Ну теперь залудите чтонибудь вроде QString/GString и быстро поймете что stdlib рулит.

На C чем проще тем лучше. Если можно избежать longjump'а, указателя на функцию, malloc'а, пойнтерной арифметики, структуры - избегать.

imatveev13
()
Ответ на: комментарий от Sorcerer

Надо делать примерно так, как предложил arsi

Ну так это вообще простая репа - подумаешь, использовать сахар для инициализации структур, что-то вроде

struct Soo {
  int a;
  int b;
};

int
main () {
  ...
  struct Soo foo = {
    .a = 1,
    .b = 2,
  };
  ...
}

только поля - указатели на функции, обычная пачка указателей. Конечно помогает абстрагироваться от байтикоф, но дальше что? Интерфейс это несколько более общая вещь (да, если говорить о Си - то чем является интерфейс в C++ или Java - несколько параллельно).

quasimoto ★★★★
()

Вклинюсь в разговор.

Нашёл вот такую идею по реализации ООП на чистом Си (автор использовал там оператор ::, но его без проблем можно заменить сишным, в данном случае это непринципиально, важна идея).

http://www.codenet.ru/progr/cpp/oopc.php

Для Ъ:

Создаём описание структур-«классов», создаём внутри них ссылки на функции, которые будут «методами». «Методы» принимают в качестве аргумента ссылку на экземпляр класса-«объекта», с которым производится действие.

Далее делаем полиморфный «метод», выполняющий разные операции в зависимости от того, ссылку на «объект» какого типа он получил. А определяет он тип объекта следующим образом: сперва приводит объект по полученной ссылке к типу int, дальше смотрит что находится в int'е, в зависимости от этого снова приводит ссылку, уже к типу «объект_1», «объект_2» или «объект_N» и выполняет соответствующее действие.

Фишка в том, что каждая структура-«класс» первым членом обязательно должна содержать int, в котором записано уникальное значение, и по которому будет происходить преобразование ссылки, которая передаётся полиморфному «методу».

Внимание, вопрос - это будет работать? Т.е. если сделать int первым членом структуры, преобразование ссылки на структуру к типу int действительно всегда будет давать ссылку на этот член?

Внимание, вопрос 2: а если сделать первым членом указатель на функцию, присваивать этому члену адреса требуемых реализаций полиморфного «метода», после чего приводить ссылку на структуру к типу «указатель на функцию» и выполнять?

typedef void (*polymorphic)(*some_object p);

void method(*some_object p)
{
   polymorphic a = (polymorphic)p;
   (a)(p); // calling method at specific address
}
prischeyadro ★★★☆☆
()
Ответ на: комментарий от imatveev13

Хорошо, давайте назовём это не плагином, а модулем. Если некая система должна поддерживать к примеру несколько драйверов БД, чтобы уметь работать с Postgres, Oracle, Firebird, MySql и т.д для каждой из этих СУБД есть свой API, и свои библиотеки для доступа к ним. Можно все эти библиотеки включить в код и в коде смотреть, с какой базой работаем, те функции и вызывать... А можно сделать единый интерфейс доступа к данным и обернуть его в модуль, модуль для postgres, модуль для oracle и т.д, мне кажется это более логичным, нежели собирать это всё вместе. Если понадобится добавить поддержку базы XXX, просто делаем для неё модуль и подгружаем в систему. Поэтому интересуюсь интерфейсами, для того чтобы представлять что и как будет, может конечно, в чём-то не туда копаю, но в процессе надеюсь разобраться в этом вопросе :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

> Хорошо, давайте назовём это не плагином, а модулем.

один фиг) и то и другое проще всего [на С] строить на таких структурах, как вы предложили в начальном посте. тем более, что это уже обкатанный способ (тем же ядром, например).

интерфейс:

struct dbd_interface {
	const char * name;
	int (*init)();
	void* (*connect)(const char *host, int port, const char *dbname, const char *user, const char *password);
	void (*close)(void *handle);
	int (*exec)(void *handle, const char *query);
};

пару встроенных драйверов:

struct dbd_interface dbd_postres = {
	.name = "PostgreSQL Driver",
	.init = my_pg_init,
	.connect = my_pg_connect,
	.close = my_pg_close,
	.exec = my_pg_exec,
};

struct dbd_interface dbd_mysql = {
	.name = "MySQL Driver",
	.connect = my_mysql_connect,
	.close = my_mysql_close,
	.exec = my_mysql_exec,
};

const struct dbd_interface * const builtin_dbd[] = {
	&dbd_postgres,
	&dbd_mysql,
	NULL
};

подключаемый драйвер для оракла:

struct dbd_interface dbd_oracle = {
	.name = "Oracle DB Driver",
	.connect = my_oracle_connect,
	.close = my_oracle_close,
	.exec = my_oracle_exec,
};

const struct dbd_interface * const loadable_dbd[] = {
	&dbd_oracle,
	NULL
};

подключаемый драйвер для огнептички:

struct dbd_interface dbd_firebird = {
	.name = "Firebird DB Driver",
	.init = my_fb_init,
	.connect = my_fb_connect,
	.close = my_fb_close,
	.exec = my_fb_exec,
};

const struct dbd_interface * const loadable_dbd[] = {
	&dbd_firebird,
	NULL
};

регистрация драйверов:

const struct dbd_interface *dbd_list[DBD_MAX];
int dbd_count = 0;
const char *plugin_files[] = {
	/* но лучше таки через opendir/readdir/closedir */
	"plugins/oracle.so",
	"plugins/firebird.so",
	NULL
};

static void register_dbd(const struct dbd_interface *list[]) {
	if (list)
		for (int i = 0; list[i]; ++i)
			if (list[i].init == NULL || list[i].init() != -1)
				dbd_list[dbd_count++] = list[i];
}

void *so;
register_dbd(builtin_dbd);
for (int i = 0; plugin_files[i]; ++i)
	if ((so = dlopen(plugin_files[i], RTLD_LAZY)) != NULL)
		register_dbd(dlsym(so, "loadable_dbd"));

примерно так.

arsi ★★★★★
()

На тему Си и интерфейсов есть книжка «C Interfaces and Implementations: Techniques for Creating Reusable Software»

По поводу ООП на Си тоже есть книжка «Object Oriented programming in C»

mukoh
()
Ответ на: комментарий от arsi

Теперь многое стало понятней, начал представлять себе структуру, спасибо :)

xterro ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.