From a little bit higher altitude and considering the problem rather more open-minded than as the OOP mainstream may suggest, Object-Oriented Programming means thinking about objects as of data with associated functions. It does not necessarily mean an function has to be physically attached to an object as it is in popular languages which support paradigm of OOP, for instance in C++:
struct T
{
int data;
int get_data() const { return data; }
};
I would suggest to take a closer look at GTK+ Object and Type System. It is a brilliant example of OOP realised in C programming language:
GTK+ implements its own custom object
system, which offers standard
object-oriented features such as
inheritance and virtual function
The association can also be contractual and conventional.
Regarding encapsulation and data hiding techniques, popular and simple one may be Opaque Pointer (or Opaque Data Type) - you can pass it around but in order to load or store any information, you have to call associated function which knows how to talk to the object hidden behind that opaque pointer.
Another one, similar but different is Shadow Data type - check this link where Jon Jagger gives excellent explanation of this not-so-well-known-technique.